Best way to update docker images

Hello there.

I got stuck with the bug described on Custom Print Format Gives weasyprint libcairo2 error (on Docker Installation) while printing a custom sales invoice, and it seems that the issue has been solved in the following fix.

Error description:

OSError: no library called "cairo" was found
no library called "libcairo-2" was found
cannot load library 'libcairo.so.2': libcairo.so.2: cannot open shared object file: No such file or directory
cannot load library 'libcairo.2.dylib': libcairo.2.dylib: cannot open shared object file: No such file or directory
cannot load library 'libcairo-2.dll': libcairo-2.dll: cannot open shared object file: No such file or directory

What is the best way to update the images I am using on my environment? I followed the single server example
Wierd thing is that I already rebuild some of the images with the latest version available on dockehub, but it did not solve the problem, what makes me think that the fix on github did not went to upstream yet:

frappe@5eac7ced2070:~/frappe-bench/sites$ bench version
erpnext 14.12.1
frappe 14.22.0
payments 0.0.1

Thanks in advance for your time.

@Fillipe_Feitosa Have you found a fix for this issue? Experiencing the same.

libcairo2 was not added

these dependencies were added

can you list the packages needed?

Hello there, and sorry for the late response. I was travelling.

I partially solved this by building the following Dockerfile:

FROM frappe/erpnext-worker:v14.12.1

USER root

RUN mkdir -p /var/lib/apt/lists/partial && chmod 777 /var/lib/apt/lists/partial

RUN apt-get update && \
    apt-get install -y \
    libcairo2-dev \
    libpango1.0-0 \
    libpangocairo-1.0-0 \
    libgdk-pixbuf2.0-0

USER frappe


If you need help to build and use it let me know.

I said partially because I can create the PDF now, but I cannot print it. And I really need this invoice being printed.
The print button gives me the following error:

Traceback (most recent call last):
  File "apps/frappe/frappe/website/serve.py", line 18, in get_response
    response = renderer_instance.render()
  File "apps/frappe/frappe/website/page_renderers/template_page.py", line 78, in render
    html = self.get_html()
  File "apps/frappe/frappe/website/utils.py", line 510, in cache_html_decorator
    html = func(*args, **kwargs)
  File "apps/frappe/frappe/website/page_renderers/template_page.py", line 89, in get_html
    self.update_context()
  File "apps/frappe/frappe/website/page_renderers/template_page.py", line 157, in update_context
    data = self.run_pymodule_method("get_context")
  File "apps/frappe/frappe/website/page_renderers/template_page.py", line 219, in run_pymodule_method
    return method(self.context)
  File "apps/frappe/frappe/www/printview.py", line 51, in get_context
    body = get_rendered_template(
  File "apps/frappe/frappe/www/printview.py", line 153, in get_rendered_template
    format_data_map[df.get("fieldname")] = df
AttributeError: 'str' object has no attribute 'get'

Hey @revant_one , thanks for sharing. I am following this issue,

But even with that, it turns out that it still no possible to print the custom print format, even though the pdf is being generated.

Any ideas? The github discussion tells us to do commit the new builder from the app until it is solved.

And by the way: I followed the guide on FAQ, but I still had to manually place the required libraries on a new Dockerfile. Am I missing something?

F.F.

This is old image.

Use single image now. frappe_docker/docs/migrate-from-multi-image-setup.md at main · frappe/frappe_docker · GitHub

Also Docker hub images may be deleted after they make changes to their account policies for repo owners.

follow container registry migration proposal · Issue #1103 · frappe/frappe_docker · GitHub

New way of building custom image doesn’t depend on erpnext image

1 Like

Thanks for your answer.

I tried to change all the docker images as you suggested and followed your docs. My yaml docker compose file looks like this after direct migration.


name: erpnext-one
services:
  backend:
    depends_on:
      configurator:
        condition: service_completed_successfully
    image: frappe/erpnext:v14.19.0
    container_name: backend
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
    - type: volume
      source: assets
      target: /home/frappe/frappe-bench/sites/assets
      read_only: true
      volume: {}
  configurator:
    depends_on:
      redis:
        condition: service_started
    environment:
      DB_HOST: mariadb-database
      DB_PORT: "3306"
      REDIS_CACHE: redis:6379/0
      REDIS_QUEUE: redis:6379/1
      REDIS_SOCKETIO: redis:6379/2
      SOCKETIO_PORT: "9000"
    image: frappe/erpnext:v14.19.0
    restart: "no"
    entrypoint:
      - bash
      - -c
    command:
      - >
        bench set-config -g db_host $$DB_HOST;
        bench set-config -gp db_port $$DB_PORT;
        bench set-config -g redis_cache "redis://$$REDIS_CACHE";
        bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
        bench set-config -g redis_socketio "redis://$$REDIS_SOCKETIO";
        bench set-config -gp socketio_port $$SOCKETIO_PORT;
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  frontend:
    depends_on:
      backend:
        condition: service_started
      websocket:
        condition: service_started
    environment:
      BACKEND: backend:8000
      FRAPPE_SITE_NAME_HEADER: $$host
      SOCKETIO: websocket:9000
      UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
      UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
      UPSTREAM_REAL_IP_RECURSIVE: "off"
    image: frappe/erpnext:v14.19.0
    command:
      - nginx-entrypoint.sh
    environment:
      BACKEND: backend:8000
      SOCKETIO: websocket:9000
    labels:
      traefik.docker.network: traefik-public
      traefik.enable: "true"
      traefik.http.routers.erpnext-one-http.entrypoints: http
      traefik.http.routers.erpnext-one-http.middlewares: https-redirect
      traefik.http.routers.erpnext-one-http.rule: Host(`me.bizsuite.pt`,`petvet.bizsuite.pt`)
      traefik.http.routers.erpnext-one-http.service: erpnext-one
      traefik.http.routers.erpnext-one-https.entrypoints: https
      traefik.http.routers.erpnext-one-https.rule: Host(`me.bizsuite.pt`,`petvet.bizsuite.pt`)
      traefik.http.routers.erpnext-one-https.service: erpnext-one
      traefik.http.routers.erpnext-one-https.tls: "true"
      traefik.http.routers.erpnext-one-https.tls.certresolver: le
      traefik.http.services.erpnext-one.loadbalancer.server.port: "8080"
    networks:
      bench-network: null
      traefik-public: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  queue-default:
    command:
    - bench
    - worker
    - --queue
    - default
    depends_on:
      configurator:
        condition: service_completed_successfully
    image: frappe/erpnext:v14.19.0
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  queue-long:
    command:
    - bench
    - worker
    - --queue
    - long
    depends_on:
      configurator:
        condition: service_completed_successfully
    image: frappe/erpnext:v14.19.0
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  queue-short:
    command:
    - bench
    - worker
    - --queue
    - short
    depends_on:
      configurator:
        condition: service_completed_successfully
    image: frappe/erpnext:v14.19.0
    networks:
      bench-network: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  redis:
    image: redis:6.2-alpine
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
    - type: volume
      source: redis-data
      target: /data
      volume: {}
  scheduler:
    command:
    - bench
    - schedule
    depends_on:
      configurator:
        condition: service_completed_successfully
    image: frappe/erpnext:v14.19.0
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  websocket:
    depends_on:
      configurator:
        condition: service_completed_successfully
    image: frappe/erpnext:v14.19.0
    command:
      - node
      - /home/frappe/frappe-bench/apps/frappe/socketio.js
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
networks:
  bench-network:
    name: erpnext-one
  mariadb-network:
    name: mariadb-network
    external: true
  traefik-public:
    name: traefik-public
    external: true
volumes:
  assets:
    name: erpnext-one_assets
  redis-data:
    name: erpnext-one_redis-data
  sites:
    name: erpnext-one_sites
x-backend-defaults:
  depends_on:
    configurator:
      condition: service_completed_successfully
  image: frappe/erpnext:v14.19.0
  volumes:
  - sites:/home/frappe/frappe-bench/sites
x-depends-on-configurator:
  depends_on:
    configurator:
      condition: service_completed_successfully
x-erpnext-backend-image:
  image: frappe/erpnext:v14.19.0

I rebuild all the conttainers using the following command:

docker compose -f erpnext-one.yaml up -d --build --force-recreate

But I get ** Internal Server Error **. Backend logs shows that I am missing payments module, but this wierd because my website was already running.

Traceback (most recent call last):
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/gunicorn/workers/gthread.py", line 271, in handle
    keepalive = self.handle_request(req, conn)
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/gunicorn/workers/gthread.py", line 323, in handle_request
    respiter = self.wsgi(environ, resp.start_response)
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/werkzeug/local.py", line 237, in application
    return ClosingIterator(app(environ, start_response), self.cleanup)
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/werkzeug/wrappers/request.py", line 194, in application
    resp = f(*args[:-2] + (request,))
  File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 84, in application
    response = handle_exception(e)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 324, in handle_exception
    response = get_response("message", http_status_code=http_status_code)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/serve.py", line 27, in get_response
    response = ErrorPage(exception=e).render()
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/error_page.py", line 7, in __init__
    super().__init__(path=path, http_status_code=http_status_code)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 45, in __init__
    self.set_template_path()
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 54, in set_template_path
    app_path = frappe.get_app_path(app)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1356, in get_app_path
    return get_pymodule_path(app_name, *joins)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1373, in get_pymodule_path
    return os.path.join(os.path.dirname(get_module(scrub(modulename)).__file__ or ""), *joins)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1327, in get_module
    return importlib.import_module(modulename)
  File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'payments'

I also tried to to install the payments module, but bench returns me that I already have the folder:

frappe@e8867c4980aa:~/frappe-bench$ bench get-app payments
A directory for the application 'payments' already exists. Do you want to continue and overwrite it? [y/N]:

Would you have some insights? I am tinkering with this, the only thing I made different from the tutorial was the sites:/home/frappe/frappe-bench/sites, but this is on Frontend. and if I try to force the payments installation, I get a permission error:

PermissionError: [Errno 13] Permission denied: './sites/apps.txt'

Read this: Frequently Asked Questions · frappe/frappe_docker Wiki · GitHub

In case of production setup you need to build your custom image for installing apps. This repository only publishes frappe/erpnext image with no additional app. You CANNOT bench get-app in running containers.

Isn´t it weird that I need to install the payments module, even though my environment was working before, and the directory for the application already exists?

A directory for the application 'payments' already exists. Do you want to continue and overwrite it? [y/N]:

Sorry to insist on this. The directions are not totally clear. It is not clear to me that I need to build a custom image to install the payments, or what changed in the infrastructure that implies that I need to do that.

Once again, thanks for your time and atention.

payments is now optional dependency.

newer images will not have payments, old ones had it.

ls -1 apps > sites/apps.txt will remove it from apps.txt

you’ll need to uninstall app from site, or bulid custom image with it.

Thanks for your answer.

Could you be more specific on how to uninstall the app from the site? I could not find docs about it and just removing the app from apps.txt in the docker image did not solve the issue. Should I do it while rebuilding the image?

try

# to update apps.txt from sites volume
ls -1 apps > sites/apps.txt
# to remove app from site
bench --site {site-name} uninstall-app payments

Hello there!

I was able to remove payments from sites/apps.txt using root access to the container.
docker exec -it -u root backend bash

But the uninstall app did not work, because it seems that is already uninstalled from the site.

$ bench --site {site-name} uninstall-app payments
Could not find app payments:
No module named payments` 
    return importlib.import_module(modulename)
  File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'payments'

As far as I know, I am not using a custom image for erpnext. I followed this documentation later last year, when the images were still splited:
Single Server Example

Even though I updated the images for the one you suggested and rebuild using the aforementioned command, there is still a lot of payment references to the payments on backend code:

$ grep -r "payments" apps/erpnext apps/frappe

# Returns a lot of stuff, this is the end

, click) {\n\t\tthis.$wrapper.find(\".modal-footer\").removeClass(\"hidden\");\n\t\treturn super.set_primary_action(label, click).removeClass(\"hidden\");\n\t}\n\n\tset_secondary_action(click) {\n\t\treturn super.set_secondary_action(click).removeClass(\"hidden\");\n\t}\n\n\tmake() {\n\t\tsuper.make();\n\t\tif (this.fields) {\n\t\t\tthis.$wrapper.find(\".section-body\").addClass(\"w-100\");\n\t\t}\n\t}\n};\n"],
apps/frappe/frappe/public/js/frappe/web_form/web_form.js:               // TODO: remove this (used for payments app)
apps/frappe/frappe/public/js/frappe/web_form/web_form.js:               // TODO: remove this (used for payments app)
apps/frappe/frappe/patches/v14_0/delete_payment_gateways.py:    if "payments" in frappe.get_installed_apps():


you need the app to be in image and apps.txt to uninstall it.

after you uninstall it, remove it from image and apps.txt

or try this Removing "ERPNext Support" App - #2 by revant_one

If I try to uninstall the app before removing, I get the same error:

frappe@a3771f2df979:~/frappe-bench$ bench --site petvet.bizsuite.pt uninstall-app payments
Could not find app "payments":
No module named 'payments'
Traceback (most recent call last):
  File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/caching.py", line 55, in wrapper
    return frappe.local.request_cache[func][args_key]
KeyError: -937959627806109227

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 109, in <module>
    main()
  File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 18, in main
    click.Group(commands=commands)(prog_name="bench")
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/frappe/frappe-bench/env/lib/python3.10/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/commands/__init__.py", line 29, in _func
    ret = f(frappe._dict(ctx.obj), *args, **kwargs)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/commands/site.py", line 803, in uninstall
    remove_app(app_name=app, dry_run=dry_run, yes=yes, no_backup=no_backup, force=force)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/installer.py", line 344, in remove_app
    app_hooks = frappe.get_hooks(app_name=app_name)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1493, in get_hooks
    hooks = _dict(_load_app_hooks(app_name))
  File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/caching.py", line 57, in wrapper
    return_val = func(*args, **kwargs)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1465, in _load_app_hooks
    app_hooks = get_module(f"{app}.hooks")
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1327, in get_module
    return importlib.import_module(modulename)
  File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'payments'

backend logs still gives me the same error, even using your second tip:

# BACKEND SHELL
$ bench --site petvet.bizsuite.pt remove-from-installed-apps erpnext_support


# BACKEND LOGS
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'payments'
  1. list of apps in ~/frappe-bench/apps must be in sync with ~/frappe-bench/sites/apps.txt. Execute ls -1 apps sites/apps.txt from ~/frappe-bench to sync. ModuleNotFoundError: No module named 'payments' may be cause of apps and apps.txt not in sync.
  2. If you wish to do bench --site {site} uninstall-app {app} then {app} should be present in the image and in apps.txt (remember sync)
  3. If by any chance you can’t access the app code to get it in image, e.g. erpnext_support then you can do remove-from-installed-apps

I believe they are already sync:

root@cdd9b05b61dd:/home/frappe/frappe-bench# ls apps/
erpnext  frappe
root@cdd9b05b61dd:/home/frappe/frappe-bench# cat sites/apps.txt
erpnext
frappe

Nevertheless, backend logs still shows the error. The thing is that I just followed the old tutorial as mentioned, I never installed any other modules, besides the instructions made.

  File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'payments'

you can install payments in the custom image again, re-deploy image with payments, uninstall payments, remove payments from custom image, re-deploy image without payments.

you can confirm this locally, restore the backup locally, uninstall payments, check for errors.

Thanks very much for your patience. I am really trying to follow your line of thinking.

What do you mean by custom image? I never changed anything, added any module or created another image from the erpnext ones. I am using the old images without changing anything, followed the tutorial aforementioned.

Would you care to explain? Tbh, this update process is rather complicated, docs are not clear about the breaking changes.

You will need to build a custom image with payments

[
  {
    "url": "https://github.com/frappe/payments",
    "branch": "version-14"
  },
  {
    "url": "https://github.com/frappe/erpnext",
    "branch": "version-14"
  }
]

use that image and uninstall payments

after uninstallation use the default image back again.

Reason for the complication: payments was mandatory dependency when version 14 was released. So the older images of version 14 had payments. After a point payments became optional so it was removed from image. People who installed payments are stuck. People who are using custom images are fine because they controlled the applications that went in their image from beginning.

You have 2 options

  1. remove payments (multiple “complex” ways to remove app) and continue using official images.
  2. build your custom image with payments in it and start using the custom image.