erpnext/docker-compose.yml
epistemophiliac 260aa8c7da Fix image-preload ordering so ERPNext waits for internal copy.
YAML merge was dropping depends_on; configurator started before skopeo loaded the image.
2026-06-16 21:02:02 -04:00

284 lines
9.7 KiB
YAML

# ERPNext production stack for Coolify.
# Domain: assign in Coolify UI → service `frontend` → port 8080.
# SITE_NAME + FRAPPE_SITE_NAME_HEADER use SERVICE_FQDN_FRONTEND automatically.
# Image: set CUSTOM_IMAGE / CUSTOM_TAG from Jenkins (dist/coolify-image.env).
# image-preload copies from internal Forgejo before any ERPNext container starts.
x-customizable-image: &customizable_image
image: ${CUSTOM_IMAGE:-git.aexoradao.com/epistemophiliac/erpnext}:${CUSTOM_TAG:-main}
pull_policy: ${PULL_POLICY:-never}
restart: ${RESTART_POLICY:-unless-stopped}
x-frappe-platform: &frappe_platform
platform: linux/amd64
x-sites-volume: &sites_volume
volumes:
- sites:/home/frappe/frappe-bench/sites
services:
image-preload:
image: quay.io/skopeo/stable:v1.17.0
exclude_from_hc: true
restart: 'no'
networks:
- default
- forgejo-internal
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- 'CUSTOM_IMAGE=${CUSTOM_IMAGE:-git.aexoradao.com/epistemophiliac/erpnext}'
- 'CUSTOM_TAG=${CUSTOM_TAG:-main}'
- 'FORGEJO_HOST=${FORGEJO_HOST:-forgejo-vydgeq365afzmxe4s1d75fwv}'
- 'REGISTRY_USER=${REGISTRY_USER}'
- 'REGISTRY_PASSWORD=${REGISTRY_PASSWORD}'
entrypoint: ['sh', '-c']
command:
- >
if [ -z "$$REGISTRY_USER" ] || [ -z "$$REGISTRY_PASSWORD" ]; then echo "[image-preload] ERROR: set REGISTRY_USER and REGISTRY_PASSWORD in Coolify"; exit 1; fi;
echo "[image-preload] copying from http://$$FORGEJO_HOST:3000/epistemophiliac/erpnext:$$CUSTOM_TAG (internal Forgejo)";
skopeo copy "docker://$$FORGEJO_HOST:3000/epistemophiliac/erpnext:$$CUSTOM_TAG" "docker-daemon:$$CUSTOM_IMAGE:$$CUSTOM_TAG" --src-creds "$$REGISTRY_USER:$$REGISTRY_PASSWORD" --src-tls-verify=false --retry-times 3;
if [ "$$CUSTOM_TAG" != "main" ]; then skopeo copy "docker://$$FORGEJO_HOST:3000/epistemophiliac/erpnext:main" "docker-daemon:$$CUSTOM_IMAGE:main" --src-creds "$$REGISTRY_USER:$$REGISTRY_PASSWORD" --src-tls-verify=false --retry-times 3; fi;
echo "[image-preload] OK: $$CUSTOM_IMAGE:$$CUSTOM_TAG on host docker";
db:
image: mariadb:11.8
restart: unless-stopped
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
- --skip-innodb-read-only-compressed
environment:
- 'MYSQL_ROOT_PASSWORD=${DB_PASSWORD:-changeme}'
- 'MARIADB_AUTO_UPGRADE=1'
healthcheck:
test: ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized']
start_period: 5s
interval: 5s
timeout: 5s
retries: 10
volumes:
- db-data:/var/lib/mysql
redis-cache:
image: redis:8.6-alpine
restart: unless-stopped
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
timeout: 5s
retries: 5
redis-queue:
image: redis:8.6-alpine
restart: unless-stopped
volumes:
- redis-queue-data:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
timeout: 5s
retries: 5
configurator:
<<: [*customizable_image, *frappe_platform, *sites_volume]
exclude_from_hc: true
restart: 'no'
entrypoint: ['bash', '-c']
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;
bench set-config -g chromium_path /usr/bin/chromium-headless-shell;
environment:
- 'DB_HOST=db'
- 'DB_PORT=3306'
- 'REDIS_CACHE=redis-cache:6379'
- 'REDIS_QUEUE=redis-queue:6379'
- 'SOCKETIO_PORT=9000'
depends_on:
image-preload:
condition: service_completed_successfully
db:
condition: service_healthy
redis-cache:
condition: service_healthy
redis-queue:
condition: service_healthy
create-site:
<<: [*customizable_image, *frappe_platform, *sites_volume]
exclude_from_hc: true
restart: 'no'
entrypoint: ['bash', '-c']
command:
- >
B=/usr/local/bin/bench;
SITE=$$SITE_NAME;
if [ -z "$$SITE" ]; then echo "[create-site] ERROR: SITE_NAME empty — assign domain to frontend:8080 in Coolify (SERVICE_FQDN_FRONTEND)"; exit 1; fi;
wait-for-it -t 120 db:3306;
wait-for-it -t 120 redis-cache:6379;
wait-for-it -t 120 redis-queue:6379;
if [ -d "sites/$$SITE" ]; then echo "[create-site] exists"; $$B use "$$SITE"; else echo "[create-site] creating"; INSTALL_ARGS=""; IFS=',' read -r -a apps <<< "$$INSTALL_APPS"; for app in "$${apps[@]}"; do INSTALL_ARGS="$$INSTALL_ARGS --install-app $$app"; done; $$B new-site "$$SITE" --mariadb-user-host-login-scope='%' --admin-password "$$ADMIN_PASSWORD" --db-root-password "$$DB_PASSWORD" $$INSTALL_ARGS --set-default; fi
environment:
- 'SITE_NAME=${SITE_NAME:-${SERVICE_FQDN_FRONTEND}}'
- 'ADMIN_PASSWORD=${ADMIN_PASSWORD:-changeme}'
- 'DB_PASSWORD=${DB_PASSWORD:-changeme}'
- 'INSTALL_APPS=${INSTALL_APPS:-erpnext,payments,hrms,lending,lms}'
depends_on:
image-preload:
condition: service_completed_successfully
configurator:
condition: service_completed_successfully
db:
condition: service_healthy
migrator:
<<: [*customizable_image, *frappe_platform, *sites_volume]
exclude_from_hc: true
restart: 'no'
entrypoint: ['bash', '-c']
command:
- >
if [ "$$MIGRATE_SITES" != "true" ]; then echo "[migrator] disabled"; exit 0; fi;
if [ -z "$$(find sites -mindepth 2 -maxdepth 2 -name site_config.json 2>/dev/null)" ]; then echo "[migrator] no sites"; exit 0; fi;
echo "[migrator] migrating all sites";
bench --site all migrate;
environment:
- 'MIGRATE_SITES=${MIGRATE_SITES:-true}'
depends_on:
image-preload:
condition: service_completed_successfully
configurator:
condition: service_completed_successfully
create-site:
condition: service_completed_successfully
backend:
<<: [*customizable_image, *frappe_platform, *sites_volume]
environment:
- 'GUNICORN_THREADS=${GUNICORN_THREADS:-4}'
- 'GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}'
- 'GUNICORN_TIMEOUT=${GUNICORN_TIMEOUT:-120}'
depends_on:
image-preload:
condition: service_completed_successfully
configurator:
condition: service_completed_successfully
create-site:
condition: service_completed_successfully
migrator:
condition: service_completed_successfully
healthcheck:
test: ['CMD-SHELL', 'curl -sf http://localhost:8000/api/method/ping || exit 1']
interval: 15s
timeout: 10s
retries: 10
start_period: 120s
websocket:
<<: [*customizable_image, *frappe_platform, *sites_volume]
command:
- node
- /home/frappe/frappe-bench/apps/frappe/socketio.js
depends_on:
image-preload:
condition: service_completed_successfully
configurator:
condition: service_completed_successfully
create-site:
condition: service_completed_successfully
frontend:
<<: [*customizable_image, *frappe_platform, *sites_volume]
command:
- nginx-entrypoint.sh
environment:
- SERVICE_URL_FRONTEND_8080
- SERVICE_FQDN_FRONTEND
- 'BACKEND=backend:8000'
- 'SOCKETIO=websocket:9000'
- 'FRAPPE_SITE_NAME_HEADER=${FRAPPE_SITE_NAME_HEADER:-${SERVICE_FQDN_FRONTEND}}'
- 'UPSTREAM_REAL_IP_ADDRESS=${UPSTREAM_REAL_IP_ADDRESS:-127.0.0.1}'
- 'UPSTREAM_REAL_IP_HEADER=${UPSTREAM_REAL_IP_HEADER:-X-Forwarded-For}'
- 'UPSTREAM_REAL_IP_RECURSIVE=${UPSTREAM_REAL_IP_RECURSIVE:-off}'
- 'PROXY_READ_TIMEOUT=${PROXY_READ_TIMEOUT:-120}'
- 'CLIENT_MAX_BODY_SIZE=${CLIENT_MAX_BODY_SIZE:-50m}'
depends_on:
image-preload:
condition: service_completed_successfully
backend:
condition: service_healthy
websocket:
condition: service_started
healthcheck:
test: ['CMD-SHELL', 'curl -sf http://localhost:8080/ || exit 1']
interval: 15s
timeout: 10s
retries: 15
start_period: 90s
queue-short:
<<: [*customizable_image, *frappe_platform, *sites_volume]
command:
- bench
- worker
- --queue
- short,default
depends_on:
image-preload:
condition: service_completed_successfully
configurator:
condition: service_completed_successfully
create-site:
condition: service_completed_successfully
migrator:
condition: service_completed_successfully
queue-long:
<<: [*customizable_image, *frappe_platform, *sites_volume]
command:
- bench
- worker
- --queue
- long,default,short
depends_on:
image-preload:
condition: service_completed_successfully
configurator:
condition: service_completed_successfully
create-site:
condition: service_completed_successfully
migrator:
condition: service_completed_successfully
scheduler:
<<: [*customizable_image, *frappe_platform, *sites_volume]
command:
- bench
- schedule
depends_on:
image-preload:
condition: service_completed_successfully
configurator:
condition: service_completed_successfully
create-site:
condition: service_completed_successfully
migrator:
condition: service_completed_successfully
volumes:
sites:
db-data:
redis-queue-data:
networks:
forgejo-internal:
external: true
name: ${FORGEJO_NETWORK:-vydgeq365afzmxe4s1d75fwv}