From 2c80762082cebc2dea9fc9174e31adbf919f1837 Mon Sep 17 00:00:00 2001 From: Satya Tulasi Jalandhar C H Date: Tue, 7 Apr 2026 09:17:13 +0000 Subject: [PATCH 1/8] feat: add ERPNext version 16 configuration and assets --- blueprints/erpnext_16/docker-compose.yml | 307 +++++++++++++++++++++++ blueprints/erpnext_16/erpnext.svg | 5 + blueprints/erpnext_16/template.toml | 29 +++ meta.json | 21 ++ 4 files changed, 362 insertions(+) create mode 100644 blueprints/erpnext_16/docker-compose.yml create mode 100644 blueprints/erpnext_16/erpnext.svg create mode 100644 blueprints/erpnext_16/template.toml diff --git a/blueprints/erpnext_16/docker-compose.yml b/blueprints/erpnext_16/docker-compose.yml new file mode 100644 index 00000000..40d2ea3c --- /dev/null +++ b/blueprints/erpnext_16/docker-compose.yml @@ -0,0 +1,307 @@ +x-custom-image: &custom_image + image: ${IMAGE_NAME:-docker.io/frappe/erpnext}:${VERSION:-version-16} + pull_policy: ${PULL_POLICY:-always} + deploy: + restart_policy: + condition: always + +services: + backend: + <<: *custom_image + volumes: + - sites:/home/frappe/frappe-bench/sites + healthcheck: + test: ["CMD", "wait-for-it", "127.0.0.1:8000"] + interval: 2s + timeout: 10s + retries: 30 + + frontend: + <<: *custom_image + command: + - nginx-entrypoint.sh + depends_on: + backend: + condition: service_started + websocket: + condition: service_started + environment: + BACKEND: backend:8000 + FRAPPE_SITE_NAME_HEADER: ${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" + volumes: + - sites:/home/frappe/frappe-bench/sites + healthcheck: + test: ["CMD", "wait-for-it", "127.0.0.1:8080"] + interval: 2s + timeout: 30s + retries: 30 + + queue-default: + <<: *custom_image + command: + - bench + - worker + - --queue + - default + volumes: + - sites:/home/frappe/frappe-bench/sites + healthcheck: + test: ["CMD", "wait-for-it", "redis-queue:6379"] + interval: 2s + timeout: 10s + retries: 30 + depends_on: + configurator: + condition: service_completed_successfully + + queue-long: + <<: *custom_image + command: + - bench + - worker + - --queue + - long + volumes: + - sites:/home/frappe/frappe-bench/sites + healthcheck: + test: ["CMD", "wait-for-it", "redis-queue:6379"] + interval: 2s + timeout: 10s + retries: 30 + depends_on: + configurator: + condition: service_completed_successfully + + queue-short: + <<: *custom_image + command: + - bench + - worker + - --queue + - short + volumes: + - sites:/home/frappe/frappe-bench/sites + healthcheck: + test: ["CMD", "wait-for-it", "redis-queue:6379"] + interval: 2s + timeout: 10s + retries: 30 + depends_on: + configurator: + condition: service_completed_successfully + + scheduler: + <<: *custom_image + healthcheck: + test: ["CMD", "wait-for-it", "redis-queue:6379"] + interval: 2s + timeout: 10s + retries: 30 + command: + - bench + - schedule + depends_on: + configurator: + condition: service_completed_successfully + volumes: + - sites:/home/frappe/frappe-bench/sites + + websocket: + <<: *custom_image + healthcheck: + test: ["CMD", "wait-for-it", "websocket:9000"] + interval: 2s + timeout: 10s + retries: 30 + command: + - node + - /home/frappe/frappe-bench/apps/frappe/socketio.js + depends_on: + configurator: + condition: service_completed_successfully + volumes: + - sites:/home/frappe/frappe-bench/sites + + configurator: + <<: *custom_image + deploy: + mode: replicated + replicas: ${CONFIGURE:-0} + restart_policy: + condition: none + entrypoint: ["bash", "-c"] + command: + - > + echo "[configurator] starting"; + if [[ $${REGENERATE_APPS_TXT} == "1" ]]; then + echo "[configurator] regenerating sites/apps.txt"; + ls -1 apps > sites/apps.txt; + fi; + existing_db_host=$$(grep -hs '^' sites/common_site_config.json | jq -r ".db_host // empty" 2>/dev/null || true); + if [[ -n "$${existing_db_host}" ]]; then + echo "[configurator] common_site_config.json already configured, skipping"; + exit 0; + fi; + echo "[configurator] applying bench global config"; + 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; + echo "[configurator] done"; + environment: + DB_HOST: "${DB_HOST:-db}" + DB_PORT: "3306" + REDIS_CACHE: redis-cache:6379 + REDIS_QUEUE: redis-queue:6379 + SOCKETIO_PORT: "9000" + REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}" + volumes: + - sites:/home/frappe/frappe-bench/sites + + create-site: + <<: *custom_image + deploy: + mode: replicated + replicas: ${CREATE_SITE:-0} + restart_policy: + condition: none + depends_on: + db: + condition: service_healthy + redis-cache: + condition: service_healthy + redis-queue: + condition: service_healthy + configurator: + condition: service_completed_successfully + entrypoint: ["bash", "-c"] + command: + - > + 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"; + [[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && exit 0; + bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME}; + volumes: + - sites:/home/frappe/frappe-bench/sites + environment: + SITE_NAME: ${SITE_NAME} + ADMIN_PASSWORD: ${ADMIN_PASSWORD} + DB_HOST: ${DB_HOST:-db} + DB_PORT: "${DB_PORT:-3306}" + DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + INSTALL_APP_ARGS: ${INSTALL_APP_ARGS} + + migration: + <<: *custom_image + deploy: + mode: replicated + replicas: ${MIGRATE:-0} + restart_policy: + condition: none + depends_on: + frontend: + condition: service_healthy + configurator: + condition: service_completed_successfully + create-site: + condition: service_completed_successfully + entrypoint: ["bash", "-c"] + command: + - > + curl -f -H "Host: ${SITE_NAME}" http://frontend:8080/api/method/ping || { echo "Site busy"; exit 0; }; + bench --site all set-config -p maintenance_mode 1; + bench --site all set-config -p pause_scheduler 1; + bench --site all migrate; + bench --site all set-config -p maintenance_mode 0; + bench --site all set-config -p pause_scheduler 0; + volumes: + - sites:/home/frappe/frappe-bench/sites + + db: + image: mariadb:${DB_VERSION:-11.8.6} + deploy: + mode: replicated + replicas: ${ENABLE_DB:-0} + restart_policy: + condition: always + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + start_period: 10s + interval: 10s + timeout: 5s + retries: 3 + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + environment: + - MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD} + volumes: + - db-data:/var/lib/mysql/data + + redis-cache: + deploy: + restart_policy: + condition: always + image: redis:6.2-alpine + volumes: + - redis-cache-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 3 + + redis-queue: + deploy: + restart_policy: + condition: always + image: redis:6.2-alpine + volumes: + - redis-queue-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 3 + + redis-socketio: + deploy: + restart_policy: + condition: always + image: redis:6.2-alpine + volumes: + - redis-socketio-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 3 + +volumes: + db-data: + redis-cache-data: + redis-queue-data: + redis-socketio-data: + sites: + driver_opts: + type: "${SITE_VOLUME_TYPE}" + o: "${SITE_VOLUME_OPTS}" + device: "${SITE_VOLUME_DEV}" \ No newline at end of file diff --git a/blueprints/erpnext_16/erpnext.svg b/blueprints/erpnext_16/erpnext.svg new file mode 100644 index 00000000..d699ea2a --- /dev/null +++ b/blueprints/erpnext_16/erpnext.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/blueprints/erpnext_16/template.toml b/blueprints/erpnext_16/template.toml new file mode 100644 index 00000000..524c9f59 --- /dev/null +++ b/blueprints/erpnext_16/template.toml @@ -0,0 +1,29 @@ +[variables] +main_domain = "${domain}" +db_root_password = "${password:32}" +admin_password = "${password:32}" + +[config] +[[config.domains]] +serviceName = "frontend" +port = 8_080 +host = "${main_domain}" + +[config.env] +SITE_NAME = "${main_domain}" +ADMIN_PASSWORD = "${admin_password}" +DB_ROOT_PASSWORD = "${db_root_password}" +MIGRATE = "1" +ENABLE_DB = "1" +DB_HOST = "db" +CREATE_SITE = "1" +CONFIGURE = "1" +REGENERATE_APPS_TXT = "1" +INSTALL_APP_ARGS = "--install-app erpnext" +IMAGE_NAME = "docker.io/frappe/erpnext" +VERSION = "version-16" +FRAPPE_SITE_NAME_HEADER = "" +DB_VERSION = "11.8.6" +SITE_VOLUME_TYPE = "" +SITE_VOLUME_OPTS = "" +SITE_VOLUME_DEV = "" \ No newline at end of file diff --git a/meta.json b/meta.json index 3ae0cb5e..3830c1ae 100644 --- a/meta.json +++ b/meta.json @@ -2191,6 +2191,27 @@ "hrms" ] }, + { + "id": "erpnext_16", + "name": "ERPNext", + "version": "version-16", + "description": "100% Open Source and highly customizable ERP software.", + "logo": "erpnext.svg", + "links": { + "github": "https://github.com/frappe/erpnext", + "docs": "https://docs.frappe.io/erpnext", + "website": "https://frappe.io/erpnext" + }, + "tags": [ + "frappe", + "erp", + "accounts", + "manufacturing", + "retail", + "sales", + "pos" + ] + }, { "id": "etherpad", "name": "Etherpad", From 2d1eeb8d1dcc40513a08425ee2f4950bcda8877b Mon Sep 17 00:00:00 2001 From: Satya Tulasi Jalandhar C H Date: Tue, 7 Apr 2026 09:17:40 +0000 Subject: [PATCH 2/8] refactor: remove Strapi duplicate entry from meta.json --- meta.json | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/meta.json b/meta.json index 3830c1ae..db4353ef 100644 --- a/meta.json +++ b/meta.json @@ -5973,24 +5973,6 @@ "media" ] }, - { - "id": "strapi", - "name": "Strapi", - "version": "v5.33.0", - "description": "Open-source headless CMS to build powerful APIs with built-in content management.", - "logo": "strapi.svg", - "links": { - "github": "https://github.com/strapi/strapi", - "discord": "https://discord.com/invite/strapi", - "docs": "https://docs.strapi.io", - "website": "https://strapi.io" - }, - "tags": [ - "headless", - "cms", - "content-management" - ] - }, { "id": "supabase", "name": "SupaBase", From 84a19c4fb54e4b46dd6d1ab1458b9a344a8cf6e9 Mon Sep 17 00:00:00 2001 From: Satya Tulasi Jalandhar C H Date: Tue, 7 Apr 2026 09:29:45 +0000 Subject: [PATCH 3/8] chore: rename the folder --- blueprints/{erpnext_16 => erpnext-v16}/docker-compose.yml | 0 blueprints/{erpnext_16 => erpnext-v16}/erpnext.svg | 0 blueprints/{erpnext_16 => erpnext-v16}/template.toml | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename blueprints/{erpnext_16 => erpnext-v16}/docker-compose.yml (100%) rename blueprints/{erpnext_16 => erpnext-v16}/erpnext.svg (100%) rename blueprints/{erpnext_16 => erpnext-v16}/template.toml (100%) diff --git a/blueprints/erpnext_16/docker-compose.yml b/blueprints/erpnext-v16/docker-compose.yml similarity index 100% rename from blueprints/erpnext_16/docker-compose.yml rename to blueprints/erpnext-v16/docker-compose.yml diff --git a/blueprints/erpnext_16/erpnext.svg b/blueprints/erpnext-v16/erpnext.svg similarity index 100% rename from blueprints/erpnext_16/erpnext.svg rename to blueprints/erpnext-v16/erpnext.svg diff --git a/blueprints/erpnext_16/template.toml b/blueprints/erpnext-v16/template.toml similarity index 100% rename from blueprints/erpnext_16/template.toml rename to blueprints/erpnext-v16/template.toml From 76792ce6b2db0d01ff5635b14ded1e9f20042f05 Mon Sep 17 00:00:00 2001 From: Satya Tulasi Jalandhar C H Date: Tue, 7 Apr 2026 09:34:26 +0000 Subject: [PATCH 4/8] fix: update ERPNext ID format in meta.json --- meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta.json b/meta.json index db4353ef..7f4481ad 100644 --- a/meta.json +++ b/meta.json @@ -2192,7 +2192,7 @@ ] }, { - "id": "erpnext_16", + "id": "erpnext-v16", "name": "ERPNext", "version": "version-16", "description": "100% Open Source and highly customizable ERP software.", From 0064d0b598b7ade47378333e195beb6b4c155823 Mon Sep 17 00:00:00 2001 From: Satya Tulasi Jalandhar C H Date: Tue, 7 Apr 2026 09:37:05 +0000 Subject: [PATCH 5/8] fix: correct redis_socketio configuration in docker-compose.yml --- blueprints/erpnext-v16/docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blueprints/erpnext-v16/docker-compose.yml b/blueprints/erpnext-v16/docker-compose.yml index 40d2ea3c..5030666f 100644 --- a/blueprints/erpnext-v16/docker-compose.yml +++ b/blueprints/erpnext-v16/docker-compose.yml @@ -151,7 +151,7 @@ services: 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 -g redis_socketio "redis://$$REDIS_SOCKETIO"; bench set-config -gp socketio_port $$SOCKETIO_PORT; echo "[configurator] done"; environment: @@ -159,6 +159,7 @@ services: DB_PORT: "3306" REDIS_CACHE: redis-cache:6379 REDIS_QUEUE: redis-queue:6379 + REDIS_SOCKETIO: redis-socketio:6379 SOCKETIO_PORT: "9000" REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}" volumes: From 8d514c2d43e478199cf3eb14a802ec3205f06754 Mon Sep 17 00:00:00 2001 From: Satya Tulasi Jalandhar C H Date: Tue, 7 Apr 2026 09:41:02 +0000 Subject: [PATCH 6/8] fix: update volume path for db service in docker-compose.yml --- blueprints/erpnext-v16/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/erpnext-v16/docker-compose.yml b/blueprints/erpnext-v16/docker-compose.yml index 5030666f..834f9956 100644 --- a/blueprints/erpnext-v16/docker-compose.yml +++ b/blueprints/erpnext-v16/docker-compose.yml @@ -255,7 +255,7 @@ services: environment: - MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD} volumes: - - db-data:/var/lib/mysql/data + - db-data:/var/lib/mysql redis-cache: deploy: From 72519948b1d4cc9eb7e987a39cbf44d4975a4c18 Mon Sep 17 00:00:00 2001 From: Satya Tulasi Jalandhar C H Date: Tue, 7 Apr 2026 10:46:16 +0000 Subject: [PATCH 7/8] feat: add ENABLE_SCHEDULER variable to template.toml and update docker-compose.yml for scheduler management --- blueprints/erpnext-v16/docker-compose.yml | 6 ++++++ blueprints/erpnext-v16/template.toml | 1 + 2 files changed, 7 insertions(+) diff --git a/blueprints/erpnext-v16/docker-compose.yml b/blueprints/erpnext-v16/docker-compose.yml index 834f9956..da7ea9f1 100644 --- a/blueprints/erpnext-v16/docker-compose.yml +++ b/blueprints/erpnext-v16/docker-compose.yml @@ -199,6 +199,11 @@ services: echo "sites/common_site_config.json found"; [[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && exit 0; bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME}; + if [[ $${ENABLE_SCHEDULER} == "1" ]]; then + bench --site $${SITE_NAME} enable-scheduler; + else + echo "Skipping scheduler enable"; + fi; volumes: - sites:/home/frappe/frappe-bench/sites environment: @@ -208,6 +213,7 @@ services: DB_PORT: "${DB_PORT:-3306}" DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} INSTALL_APP_ARGS: ${INSTALL_APP_ARGS} + ENABLE_SCHEDULER: "${ENABLE_SCHEDULER}" migration: <<: *custom_image diff --git a/blueprints/erpnext-v16/template.toml b/blueprints/erpnext-v16/template.toml index 524c9f59..f7799b8d 100644 --- a/blueprints/erpnext-v16/template.toml +++ b/blueprints/erpnext-v16/template.toml @@ -20,6 +20,7 @@ CREATE_SITE = "1" CONFIGURE = "1" REGENERATE_APPS_TXT = "1" INSTALL_APP_ARGS = "--install-app erpnext" +ENABLE_SCHEDULER = "1" IMAGE_NAME = "docker.io/frappe/erpnext" VERSION = "version-16" FRAPPE_SITE_NAME_HEADER = "" From c7487d8c093aea2f15c4a126d140154d27a43f06 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Wed, 8 Jul 2026 02:44:14 -0600 Subject: [PATCH 8/8] fix: db healthcheck window too short and blocking depends_on aborted deploy The deploy failed with 'dependency failed to start: container db-1 is unhealthy'. Root cause: the MariaDB 11.8 healthcheck allowed only ~40s (start_period 10s + 3x10s retries) before flagging the container unhealthy, which is not enough for the first-boot datadir initialization on a modest VPS. Since create-site depended on db:service_healthy, docker compose up -d aborted the whole deploy. Fixes, following the pattern of the existing working erpnext template: - Extend the db healthcheck window (start_period 120s, 30 retries) so first-boot initialization does not mark the container unhealthy. - Drop depends_on from the one-shot create-site and migration jobs and gate create-site with wait-for-it (db/redis) inside its command, so docker compose up -d returns without blocking on the multi-minute site creation and cannot fail on transient health states. Verified on a fresh deploy: all services up, create-site installed frappe + erpnext and enabled the scheduler, frontend serves the ERPNext login page with HTTP 200. Co-Authored-By: Claude Fable 5 --- blueprints/erpnext-v16/docker-compose.yml | 27 ++++++----------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/blueprints/erpnext-v16/docker-compose.yml b/blueprints/erpnext-v16/docker-compose.yml index da7ea9f1..3201b4d7 100644 --- a/blueprints/erpnext-v16/docker-compose.yml +++ b/blueprints/erpnext-v16/docker-compose.yml @@ -172,18 +172,12 @@ services: replicas: ${CREATE_SITE:-0} restart_policy: condition: none - depends_on: - db: - condition: service_healthy - redis-cache: - condition: service_healthy - redis-queue: - condition: service_healthy - configurator: - condition: service_completed_successfully entrypoint: ["bash", "-c"] command: - > + wait-for-it -t 300 $${DB_HOST}:$${DB_PORT}; + wait-for-it -t 120 redis-cache:6379; + wait-for-it -t 120 redis-queue:6379; 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"` ]] && \ @@ -222,17 +216,10 @@ services: replicas: ${MIGRATE:-0} restart_policy: condition: none - depends_on: - frontend: - condition: service_healthy - configurator: - condition: service_completed_successfully - create-site: - condition: service_completed_successfully entrypoint: ["bash", "-c"] command: - > - curl -f -H "Host: ${SITE_NAME}" http://frontend:8080/api/method/ping || { echo "Site busy"; exit 0; }; + curl -f -H "Host: ${SITE_NAME}" http://frontend:8080/api/method/ping || { echo "Site not reachable yet, skipping migration"; exit 0; }; bench --site all set-config -p maintenance_mode 1; bench --site all set-config -p pause_scheduler 1; bench --site all migrate; @@ -250,10 +237,10 @@ services: condition: always healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] - start_period: 10s - interval: 10s + start_period: 120s + interval: 5s timeout: 5s - retries: 3 + retries: 30 command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci