NGINX on Host for No-proxy ERPNext Docker Setup

Hello,

For different reasons, we need to deploy ERPNext without reverse proxy and use an existing NGINX setup on the host server.

The used docker compose file is:

name: example
services:
  backend:
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.4.0
    networks:
      default: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  configurator:
    command:
    - |
      ls -1 apps > sites/apps.txt; 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_QUEUE"; bench set-config -gp socketio_port $$SOCKETIO_PORT;
    depends_on:
      db:
        condition: service_healthy
        required: true
      redis-cache:
        condition: service_started
        required: true
      redis-queue:
        condition: service_started
        required: true
    entrypoint:
    - bash
    - -c
    environment:
      DB_HOST: db
      DB_PORT: "3306"
      REDIS_CACHE: redis-cache:6379
      REDIS_QUEUE: redis-queue:6379
      SOCKETIO_PORT: "9000"
    image: frappe/erpnext:v15.4.0
    networks:
      default: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  db:
    command:
    - --character-set-server=utf8mb4
    - --collation-server=utf8mb4_unicode_ci
    - --skip-character-set-client-handshake
    - --skip-innodb-read-only-compressed
    environment:
      MYSQL_ROOT_PASSWORD: thePassword
    healthcheck:
      test:
      - CMD-SHELL
      - mysqladmin ping -h localhost --password=thePassword
      interval: 1s
      retries: 15
    image: mariadb:10.6
    networks:
      default: null
    volumes:
    - type: volume
      source: db-data
      target: /var/lib/mysql
      volume: {}
  frontend:
    command:
    - nginx-entrypoint.sh
    depends_on:
      backend:
        condition: service_started
        required: true
      websocket:
        condition: service_started
        required: true
    environment:
      BACKEND: backend:8000
      CLIENT_MAX_BODY_SIZE: 50m
      FRAPPE_SITE_NAME_HEADER: $$host
      PROXY_READ_TIMOUT: "120"
      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:v15.4.0
    networks:
      default: null
    ports:
    - mode: ingress
      host_ip: 127.0.0.1
      target: 8080
      published: "8080"
      protocol: tcp
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  queue-long:
    command:
    - bench
    - worker
    - --queue
    - long,default,short
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.4.0
    networks:
      default: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  queue-short:
    command:
    - bench
    - worker
    - --queue
    - short,default
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.4.0
    networks:
      default: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  redis-cache:
    image: redis:6.2-alpine
    networks:
      default: null
    volumes:
    - type: volume
      source: redis-cache-data
      target: /data
      volume: {}
  redis-queue:
    image: redis:6.2-alpine
    networks:
      default: null
    volumes:
    - type: volume
      source: redis-queue-data
      target: /data
      volume: {}
  scheduler:
    command:
    - bench
    - schedule
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.4.0
    networks:
      default: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
  websocket:
    command:
    - node
    - /home/frappe/frappe-bench/apps/frappe/socketio.js
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.4.0
    networks:
      default: null
    volumes:
    - type: volume
      source: sites
      target: /home/frappe/frappe-bench/sites
      volume: {}
networks:
  default:
    name: openly_default
volumes:
  db-data:
    name: openly_db-data
  redis-cache-data:
    name: openly_redis-cache-data
  redis-queue-data:
    name: openly_redis-queue-data
  sites:
    name: openly_sites
x-backend-defaults:
  depends_on:
    configurator:
      condition: service_completed_successfully
  image: frappe/erpnext:v15.4.0
  volumes:
  - sites:/home/frappe/frappe-bench/sites
x-customizable-image:
  image: frappe/erpnext:v15.4.0
x-depends-on-configurator:
  depends_on:
    configurator:
      condition: service_completed_successfully

.
.
For No-proxy Override:

services:
  frontend:
    ports:
      - 127.0.0.1:8080:8080

.
.
We used easy-install.py script for setup as follows:

python3 easy-install.py -n example -p -s v15.example.com --email email@example.com

.
.
For NGINX Conf:

server {
	
	listen 1.2.3.4:443 ssl;
	listen [::]:443 ssl;

	server_name v15.example.com;

	proxy_buffer_size 128k;
	proxy_buffers 4 256k;
	proxy_busy_buffers_size 256k;

	
	ssl_certificate      /etc/letsencrypt/live/v15.example.com/fullchain.pem;
	ssl_certificate_key  /etc/letsencrypt/live/v15.example.com/privkey.pem;
	ssl_session_timeout  5m;
	ssl_session_cache shared:SSL:10m;
	ssl_session_tickets off;
	ssl_stapling on;
	ssl_stapling_verify on;
	ssl_protocols TLSv1.2 TLSv1.3;
	ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
	ssl_ecdh_curve secp384r1;
	ssl_prefer_server_ciphers on;
	

	add_header X-Frame-Options "SAMEORIGIN";
	add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
	add_header X-Content-Type-Options nosniff;
	add_header X-XSS-Protection "1; mode=block";
	add_header Referrer-Policy "same-origin, strict-origin-when-cross-origin";

	location / {
		proxy_buffering off;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-Host v15.example.com;
		proxy_set_header X-Forwarded-Port 8080;
		proxy_pass http://127.0.0.1:8080;
	}

	access_log  /var/log/nginx/v15_access.log main;
	error_log  /var/log/nginx/v15_error.log;

	# enable gzip compresion
	# based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge
	gzip on;
	gzip_http_version 1.1;
	gzip_comp_level 5;
	gzip_min_length 256;
	gzip_proxied any;
	gzip_vary on;
	gzip_types
		application/atom+xml
		application/javascript
		application/json
		application/rss+xml
		application/vnd.ms-fontobject
		application/x-font-ttf
		application/font-woff
		application/x-web-app-manifest+json
		application/xhtml+xml
		application/xml
		font/opentype
		image/svg+xml
		image/x-icon
		text/css
		text/plain
		text/x-component
		;
		# text/html is always compressed by HttpGzipModule
}

# http to https redirect
server {
	listen 1.2.3.4:80;
	server_name v15.example.com;
	return 301 https://$host$request_uri;
	}

.
.

Will appreciate any hints or guidance.

Thanks

1 Like

Hello @revant_one

As the expert in Docker/Frappe… can you take a look?
Will appreciate your help on this.

Thanks

What errors are you facing?

Hello @revant … really appreciate your reponse.

When I visit the domain root, I get:

Screenshot from 2023-12-07 08-43-10

When running:

docker logs --timestamps --follow example-frontend-1

I am getting:

2023-12-07T05:46:49.479209170Z 172.25.0.1 - - [07/Dec/2023:05:46:49 +0000] "GET / HTTP/1.0" 404 111 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
2023-12-07T05:46:49.613905831Z 172.25.0.1 - - [07/Dec/2023:05:46:49 +0000] "GET /favicon.ico HTTP/1.0" 404 111 "https://v15.openly.ae/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"

.
.

So, proxying looks to be working and traffic is reaching the container, yet it is replying with 404!

Thanks again.

Host header and site name must match.

Or override it like this

Thank you @revant_one I knew you will give the right advice :rocket:

So, the page is now loading, yet it does not load JS and CSS files.
For each of the below files, I got a 404 error:

https://example.domain/assets/erpnext/dist/css/erpnext-web.bundle.IXSTIIWU.css
https://example.domain/assets/frappe/dist/css/login.bundle.BPARWUMP.css
https://example.domain/assets/frappe/dist/js/frappe-web.bundle.VKJCYSFT.js

While for those JS files, I get 200 success:

https://example.domain/assets/erpnext/dist/js/erpnext-web.bundle.J6G3BWUP.js
https://example.domain/website_script.js

→ Worth mentioning that I am now having the ERPNext version as: 15.5.0

More Information…

When did a:

docker exec -it example-frontend-1 /bin/bash
cd ~/frappe-bench/sites/assets/frappe/dist/css
ls -la

Got the file name but with a different “random” part. Please note screen shot:

So, for example, the browser is requesting:

https://example.domain/assets/frappe/dist/css/website.bundle.2I2FHKLZ.css

While the file on the filesystem in the container is:

~/frappe-bench/sites/assets/frappe/dist/css/website.bundle.DDSTCXJW.css

Hope this adds more insight.
Does it make sense to consider that there is an issue in the creation of Frappe assets in the container?

Please help.

By any chance you did bench build in the production container?

the files mentioned in “sites/assets/assets.json” and files present in directory should match.

Clean up your assets so that assets.json is correct.

You can start a fresh container and copy assets.json from it so it is in sync with actual files in image.

I will stop and delete all docker containers, volumes, networks. Do the setup from scratch again and confirm here.

I guess, this is an old volume being used for a new container.

Hello,

I did a:

docker compose down

Also, removed all related docker volumes.

Did a fresh:

python3 easy-install.py -n example -p -s v15.example.com --email email@example.com

again and all worked fine.

Hello @revant_one

I am facing the same issue. Yet, I cannot delete the docker volumes as I need the files there.

I checked the assets.json and it includes the exact file names of the files in the filesystem.

I tried from inside the backend container to:

  • bench build
  • bench migrate
  • bench clear-cache

All of which finished successfully.

Also, restarted NGINX and Docker.service.

Still when loading the /app/home the css file names that are called are wrong (not matching assets.json nor filesystem files)!

Can you please help with this?

Thanks
K

Hello @revant_one or anyone else… any advice on this?

DO NOT execute bench build in production containers.

Try deleting the sites/assets directory and restart containers. Backup the dir before deleting if you wish.

Hello @revant_one

Did that - deleted the directory.

I got “Internal Server Error”, when running docker logs, I get the following:

[2024-05-25 18:28:10 +0000] [7] [ERROR] Error handling request /
Traceback (most recent call last):
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/serve.py", line 19, in get_response
    endpoint, renderer_instance = path_resolver.resolve()
                                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/path_resolver.py", line 38, in resolve
    resolve_redirect(self.path, request.query_string)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/path_resolver.py", line 118, in resolve_redirect
    redirects += frappe.get_all("Website Route Redirect", ["source", "target"], order_by=None)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 2057, in get_all
    return get_list(doctype, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 2032, in get_list
    return frappe.model.db_query.DatabaseQuery(doctype).execute(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/model/db_query.py", line 167, in execute
    if is_virtual_doctype(self.doctype):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/caching.py", line 119, in site_cache_wrapper
    _SITE_CACHE[func_key][frappe.local.site][func_call_key] = func(*args, **kwargs)
                                                              ^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/model/utils/__init__.py", line 133, in is_virtual_doctype
    return frappe.db.get_value("DocType", doctype, "is_virtual")
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 519, in get_value
    result = self.get_values(
             ^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 623, in get_values
    out = self._get_values_from_table(
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 896, in _get_values_from_table
    return query.run(as_dict=as_dict, debug=debug, update=update, run=run, pluck=pluck)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/query_builder/utils.py", line 87, in execute_query
    result = frappe.db.sql(query, params, *args, **kwargs)  # nosemgrep
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 211, in sql
    self.connect()
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 117, in connect
    self._conn: "MariadbConnection" | "PostgresConnection" = self.get_connection()
                                                             ^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/mariadb/database.py", line 107, in get_connection
    conn = self._get_connection()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/mariadb/database.py", line 113, in _get_connection
    return self.create_connection()
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/mariadb/database.py", line 116, in create_connection
    return pymysql.connect(**self.get_connection_settings())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/connections.py", line 358, in __init__
    self.connect()
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/connections.py", line 664, in connect
    self._request_authentication()
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/connections.py", line 954, in _request_authentication
    auth_packet = self._read_packet()
                  ^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/connections.py", line 772, in _read_packet
    packet.raise_for_error()
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/protocol.py", line 221, in raise_for_error
    err.raise_mysql_exception(self._data)
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/err.py", line 143, in raise_mysql_exception
    raise errorclass(errno, errval)
pymysql.err.OperationalError: (1045, "Access denied for user '_03d69a0af8a8feab'@'172.20.0.5' (using password: YES)")

####################### REMOVING SIMILAR ERRORS #####################

Traceback (most recent call last):
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/gunicorn/workers/gthread.py", line 282, in handle
    keepalive = self.handle_request(req, conn)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/gunicorn/workers/gthread.py", line 334, in handle_request
    respiter = self.wsgi(environ, resp.start_response)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 74, in application
    app(environ, start_response),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/werkzeug/wrappers/request.py", line 190, in application
    resp = f(*args[:-2] + (request,))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 128, in application
    response = handle_exception(e)
               ^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 391, 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 28, in get_response
    response = ErrorPage(exception=e).render()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 84, in render
    html = self.get_html()
           ^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/utils.py", line 523, in cache_html_decorator
    html = func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 92, in get_html
    self.init_context()
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/error_page.py", line 14, in init_context
    super().init_context()
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/base_template_page.py", line 15, in init_context
    self.context.update(get_website_settings())
                        ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/doctype/website_settings/website_settings.py", line 263, in get_website_settings
    context.boot = get_boot_data()
                   ^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/utils.py", line 179, in get_boot_data
    "user": frappe.db.get_value("User", frappe.session.user, "time_zone") or get_system_timezone(),
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 519, in get_value
    result = self.get_values(
             ^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 623, in get_values
    out = self._get_values_from_table(
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 896, in _get_values_from_table
    return query.run(as_dict=as_dict, debug=debug, update=update, run=run, pluck=pluck)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/query_builder/utils.py", line 87, in execute_query
    result = frappe.db.sql(query, params, *args, **kwargs)  # nosemgrep
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 211, in sql
    self.connect()
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 117, in connect
    self._conn: "MariadbConnection" | "PostgresConnection" = self.get_connection()
                                                             ^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/mariadb/database.py", line 107, in get_connection
    conn = self._get_connection()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/mariadb/database.py", line 113, in _get_connection
    return self.create_connection()
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/mariadb/database.py", line 116, in create_connection
    return pymysql.connect(**self.get_connection_settings())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/connections.py", line 358, in __init__
    self.connect()
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/connections.py", line 664, in connect
    self._request_authentication()
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/connections.py", line 954, in _request_authentication
    auth_packet = self._read_packet()
                  ^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/connections.py", line 772, in _read_packet
    packet.raise_for_error()
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/protocol.py", line 221, in raise_for_error
    err.raise_mysql_exception(self._data)
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/pymysql/err.py", line 143, in raise_mysql_exception
    raise errorclass(errno, errval)
pymysql.err.OperationalError: (1045, "Access denied for user '_03d69a0af8a8feab'@'172.20.0.5' (using password: YES)")

The assets directory?

  • Try deleting and creating containers again
  • recreate assets directory and try recreation of containers

Hello @revant_one

Yes, the issue was running “bench build” as part of the update process.

Yet, it was not possible to delete the sites/assets folder easily.
I needed to stop/down the container, then inspect the “site” volume, find its path in the host. The from there remove that folder.

Then when start/up the container again, then it worked.

Thanks.

Helo @revant_one and all…

With the same setup (NGINX on Host + Frappe Docker).
Also, using a custom image. Only customization is adding a list of apps to the image: hrms, crm, payments, webshop, insights, builder and a custom app website_leads.

I created a new image with updated versions for the included apps.

Now to update, I run:

# Get the new custom image..
docker compose pull
# Remove containers..
docker compose down
# Recreate the containers..
docker compose up -d

I run a transient container to setup the site with the following docker compose section:

  create-site:
    image: ${CUSTOM_IMAGE}:${CUSTOM_TAG}
    deploy:
      restart_policy:
        condition: none
    depends_on:
      demo-openly-db:
        condition: service_healthy
        required: true
      backend:
        condition: service_started
        required: true
      configurator:
        condition: service_completed_successfully
        required: true
    volumes:
      - sites:/home/frappe/frappe-bench/sites
    environment:
      DB_HOST: ${DB_HOST:-}
      DB_PORT: ${DB_PORT:-}
      DB_NAME: ${DB_NAME}
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
      REDIS_CACHE: ${REDIS_CACHE:-}
      REDIS_QUEUE: ${REDIS_QUEUE:-}
      SOCKETIO_PORT: ${SOCKETIO_PORT:-9000}
      ADMIN_PASSWORD: ${ADMIN_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      SITE_NAME: ${SITE_NAME}
      APPS_TO_INSTALL: ${APPS_TO_INSTALL}
    entrypoint:
      - bash
      - -c
    command:
      - >
        wait-for-it -t 120 $$DB_HOST:$$DB_PORT &&
        wait-for-it -t 120 $$REDIS_CACHE &&
        wait-for-it -t 120 $$REDIS_QUEUE &&
        export start=`date +%s` &&
        until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
          [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
          [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
        do
          echo "Waiting for sites/common_site_config.json to be created";
          sleep 5;
          if (( `date +%s`-start > 120 )); then
            echo "could not find sites/common_site_config.json with required keys";
            exit 1
          fi
        done;
        echo "sites/common_site_config.json found";
        bench new-site --mariadb-user-host-login-scope='%' --admin-password=$$ADMIN_PASSWORD --db-host $$DB_HOST --db-port $$DB_PORT --db-name $$DB_NAME --db-password $$DB_PASSWORD --db-root-username=root --db-root-password=$$MYSQL_ROOT_PASSWORD $$SITE_NAME;
        bench use $$SITE_NAME;
        bench --site $$SITE_NAME enable-scheduler;
        bench --site $$SITE_NAME install-app erpnext;
        bench --site $$SITE_NAME set-config host_name $$SITE_NAME;
        echo "";
        for frappe_app in $$APPS_TO_INSTALL; do
          echo "Removing the App: $$frappe_app from installed App."
          bench remove-from-installed-apps $$frappe_app
        done;
        for frappe_app in $$APPS_TO_INSTALL; do
          echo "Installing the App: $$frappe_app in site: $$SITE_NAME."
          bench --site $$SITE_NAME update-app $$frappe_app
        done;
        bench --site $$SITE_NAME migrate;
        bench --site all clear-cache;
        bench --site all clear-website-cache;
        bench version

I got the following in the logs for this container:

wait-for-it: waiting 120 seconds for demo-openly-db:3306
wait-for-it: demo-openly-db:3306 is available after 0 seconds
wait-for-it: waiting 120 seconds for redis-cache:6379
wait-for-it: redis-cache:6379 is available after 0 seconds
wait-for-it: waiting 120 seconds for redis-queue:6379
wait-for-it: redis-queue:6379 is available after 0 seconds
sites/common_site_config.json found
Site demo.openly.ae already exists
Current Site set to demo.openly.ae
Enabled for demo.openly.ae
App erpnext already installed

Installing the App: hrms in site: demo.openly.ae.
App erpnext already installed
App hrms already installed
Installing the App: crm in site: demo.openly.ae.
App crm already installed
Installing the App: payments in site: demo.openly.ae.
App payments already installed
Installing the App: webshop in site: demo.openly.ae.
App payments already installed
App erpnext already installed
App webshop already installed
Installing the App: insights in site: demo.openly.ae.
App insights already installed
Installing the App: print_designer in site: demo.openly.ae.
App print_designer already installed
Installing the App: builder in site: demo.openly.ae.
App builder already installed
Installing the App: wiki in site: demo.openly.ae.
App wiki already installed
Installing the App: website_leads in site: demo.openly.ae.
App crm already installed
App erpnext already installed
App website_leads already installed
Migrating demo.openly.ae
Executing erpnext.patches.v15_0.rename_subcontracting_fields in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
rename_field: subcontracted_quantity not found in Purchase Order Item
rename_field: subcontracting_conversion_factor not found in Subcontracting Order Item
Success: Done in 0.147s
Updating DocTypes for frappe        : [========================================] 100%
Updating DocTypes for erpnext       : [========================================] 100%
Updating DocTypes for hrms          : [========================================] 100%
Updating DocTypes for crm           : [========================================] 100%
Updating DocTypes for payments      : [========================================] 100%
Updating DocTypes for webshop       : [========================================] 100%
Updating DocTypes for insights      : [========================================] 100%
Updating DocTypes for print_designer: [========================================] 100%
Updating DocTypes for builder       : [========================================] 100%
Updating DocTypes for wiki          : [========================================] 100%
Executing erpnext.patches.v14_0.update_posting_datetime in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
Success: Done in 0.069s
Executing erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
        Ensure SLE Indexes
Success: Done in 0.005s
Executing erpnext.patches.v15_0.update_query_report in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
Success: Done in 0.005s
Executing erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference #2025-03-18 in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
Success: Done in 0.07s
Executing erpnext.patches.v15_0.recalculate_amount_difference_field in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
Success: Done in 0.018s
Executing erpnext.patches.v15_0.rename_sla_fields #2025-03-12 in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
Success: Done in 0.064s
Executing erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
Success: Done in 0.005s
Executing hrms.patches.v15_0.update_payment_status_for_leave_encashment in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)

        Updates submitted Leave Encashment's status based on whether it was paid via a Salary Slip.

Success: Done in 0.007s
Executing hrms.patches.v15_0.create_accounting_dimensions_in_leave_encashment in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
Success: Done in 0.021s
Executing wiki.wiki.doctype.wiki_space.patches.wiki_navbar_app_switcher_migration in demo.openly.ae (dw4P8p2vL6rggJFhXAp3FsNk)
Success: Done in 0.018s
Connected to Site DB (Site DB)
Updating Dashboard for frappe
Updating Dashboard for erpnext
Updating Dashboard for hrms
Updating Dashboard for crm
Updating Dashboard for payments
Updating Dashboard for webshop
Updating Dashboard for insights
Updating Dashboard for print_designer
Updating Dashboard for builder
Updating Dashboard for wiki
Updating Dashboard for website_leads
Updating customizations for Address
Updating customizations for Contact
Syncing Builder Components
Syncing Builder Scripts
Syncing Builder Page Templates
Syncing Builder Block Templates
Queued rebuilding of search index for demo.openly.ae
Queued rebuilding of search index for demo.openly.ae

Yet, the Apps are not updated.

This is what appears in the Help > About:

Which are the old Apps.

When ran:

docker exec -it demo-openly-backend-1 /bin/bash

and checked under the folder:

/home/frappe/frappe-bench/apps/

I found out that the apps in this folder are the new versions.

How can I get the new versions to be activated?

Thanks all for the help

Khaldoun

Hello @revant_one and all…

Would this be a docker volume shadowing the in container data?

I will check this and report here.

Yet, please provide your input if any.

Thanks