From 2aca11bb757518cd6aca708cc66cd3a0dba9f865 Mon Sep 17 00:00:00 2001 From: epistemophiliac Date: Tue, 16 Jun 2026 21:05:44 -0400 Subject: [PATCH] Use :main tag only and host sync instead of compose preload. Remove image-preload service (compose creates all containers before it can run); Jenkins and sync-main-from-forgejo.sh load :main on the host internally. --- coolify.env.example | 13 +-- docker-compose.yml | 97 +++++++---------------- docs/COOLIFY_DEPLOY.md | 34 ++++---- scripts/ci/jenkins-build-image.sh | 4 +- scripts/coolify/preload-image.sh | 54 ------------- scripts/coolify/sync-main-from-forgejo.sh | 35 ++++++++ 6 files changed, 83 insertions(+), 154 deletions(-) delete mode 100755 scripts/coolify/preload-image.sh create mode 100755 scripts/coolify/sync-main-from-forgejo.sh diff --git a/coolify.env.example b/coolify.env.example index d830b13..b1cce99 100644 --- a/coolify.env.example +++ b/coolify.env.example @@ -1,16 +1,9 @@ # Paste into Coolify → erpnext service → Environment Variables -# After Jenkins green build, copy CUSTOM_IMAGE / CUSTOM_TAG from dist/coolify-image.env -# --- Custom image (required — from Jenkins Forgejo registry) --- +# --- Custom image (always :main = latest Jenkins build) --- CUSTOM_IMAGE=git.aexoradao.com/epistemophiliac/erpnext -CUSTOM_TAG=main-26933f3 -PULL_POLICY=never - -# Forgejo registry read (internal image-preload service — same creds as Jenkins) -REGISTRY_USER=epistemophiliac -REGISTRY_PASSWORD=replace-with-forgejo-token -FORGEJO_HOST=forgejo-vydgeq365afzmxe4s1d75fwv -FORGEJO_NETWORK=vydgeq365afzmxe4s1d75fwv +CUSTOM_TAG=main +PULL_POLICY=if_not_present # --- Secrets (required — change before first deploy) --- DB_PASSWORD=replace-with-strong-secret diff --git a/docker-compose.yml b/docker-compose.yml index a946899..b643e56 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,12 @@ # 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. +# Image: CUSTOM_IMAGE + CUSTOM_TAG=main (latest Jenkins build on Forgejo). +# Host must have the image before deploy — Jenkins preloads :main after each push. +# Manual once: bash scripts/coolify/sync-main-from-forgejo.sh x-customizable-image: &customizable_image image: ${CUSTOM_IMAGE:-git.aexoradao.com/epistemophiliac/erpnext}:${CUSTOM_TAG:-main} - pull_policy: ${PULL_POLICY:-never} + pull_policy: ${PULL_POLICY:-if_not_present} restart: ${RESTART_POLICY:-unless-stopped} x-frappe-platform: &frappe_platform @@ -16,31 +16,15 @@ 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"; +x-depends-on-configurator: &depends_on_configurator + depends_on: + configurator: + condition: service_completed_successfully +x-backend-defaults: &backend_defaults + <<: [*depends_on_configurator, *customizable_image, *frappe_platform, *sites_volume] + +services: db: image: mariadb:11.8 restart: unless-stopped @@ -82,7 +66,7 @@ services: retries: 5 configurator: - <<: [*customizable_image, *frappe_platform, *sites_volume] + <<: *backend_defaults exclude_from_hc: true restart: 'no' entrypoint: ['bash', '-c'] @@ -103,8 +87,6 @@ services: - 'REDIS_QUEUE=redis-queue:6379' - 'SOCKETIO_PORT=9000' depends_on: - image-preload: - condition: service_completed_successfully db: condition: service_healthy redis-cache: @@ -113,9 +95,10 @@ services: condition: service_healthy create-site: - <<: [*customizable_image, *frappe_platform, *sites_volume] + <<: *customizable_image exclude_from_hc: true restart: 'no' + platform: linux/amd64 entrypoint: ['bash', '-c'] command: - > @@ -131,16 +114,16 @@ services: - 'ADMIN_PASSWORD=${ADMIN_PASSWORD:-changeme}' - 'DB_PASSWORD=${DB_PASSWORD:-changeme}' - 'INSTALL_APPS=${INSTALL_APPS:-erpnext,payments,hrms,lending,lms}' + volumes: + - sites:/home/frappe/frappe-bench/sites depends_on: - image-preload: - condition: service_completed_successfully configurator: condition: service_completed_successfully db: condition: service_healthy migrator: - <<: [*customizable_image, *frappe_platform, *sites_volume] + <<: *backend_defaults exclude_from_hc: true restart: 'no' entrypoint: ['bash', '-c'] @@ -153,22 +136,16 @@ services: 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] + <<: *backend_defaults 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: @@ -183,20 +160,17 @@ services: start_period: 120s websocket: - <<: [*customizable_image, *frappe_platform, *sites_volume] + <<: [*depends_on_configurator, *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] + <<: *customizable_image + platform: linux/amd64 command: - nginx-entrypoint.sh environment: @@ -210,9 +184,9 @@ services: - '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}' + volumes: + - sites:/home/frappe/frappe-bench/sites depends_on: - image-preload: - condition: service_completed_successfully backend: condition: service_healthy websocket: @@ -225,49 +199,37 @@ services: start_period: 90s queue-short: - <<: [*customizable_image, *frappe_platform, *sites_volume] + <<: *backend_defaults 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] + <<: *backend_defaults 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] + <<: *backend_defaults command: - bench - schedule depends_on: - image-preload: - condition: service_completed_successfully - configurator: - condition: service_completed_successfully create-site: condition: service_completed_successfully migrator: @@ -277,8 +239,3 @@ volumes: sites: db-data: redis-queue-data: - -networks: - forgejo-internal: - external: true - name: ${FORGEJO_NETWORK:-vydgeq365afzmxe4s1d75fwv} diff --git a/docs/COOLIFY_DEPLOY.md b/docs/COOLIFY_DEPLOY.md index ef43015..d4dd7f1 100644 --- a/docs/COOLIFY_DEPLOY.md +++ b/docs/COOLIFY_DEPLOY.md @@ -23,10 +23,8 @@ Copy from [`coolify.env.example`](../coolify.env.example). **Required before fir | Variable | Set in Coolify? | Source | |----------|----------------|--------| | `CUSTOM_IMAGE` | yes | Jenkins artifact / `dist/coolify-image.env` | -| `CUSTOM_TAG` | yes | e.g. `main-26933f3` (pin) or `main` | -| `PULL_POLICY` | yes | `never` (image-preload loads internally) | -| `REGISTRY_USER` | yes | Forgejo username | -| `REGISTRY_PASSWORD` | yes | Forgejo token (package read) | +| `CUSTOM_TAG` | yes | `main` (latest Jenkins build — do not pin commit tags) | +| `PULL_POLICY` | yes | `if_not_present` | | `DB_PASSWORD` | yes | strong secret | | `ADMIN_PASSWORD` | yes | Frappe `Administrator` password | | `INSTALL_APPS` | yes | `erpnext,payments,hrms,lending,lms` | @@ -47,21 +45,21 @@ Copy from [`coolify.env.example`](../coolify.env.example). **Required before fir **Order matters:** assign domain **then** deploy. If `create-site` runs with an empty site name, the stack exits with a clear error. -## 4. Internal image load (automatic) +## 4. Image on the Coolify host (before first deploy) -Compose includes an **`image-preload`** init service (Skopeo → internal Forgejo, same path as Jenkins push). It copies the image into host Docker **before** ERPNext services start — no Cloudflare pull. +The custom image is **~1.2 GB**. Coolify must find `git.aexoradao.com/epistemophiliac/erpnext:main` on the **host** Docker daemon — not pulled through Cloudflare during deploy. -Required Coolify env vars (see [`coolify.env.example`](../coolify.env.example)): +**Jenkins does this automatically** after each green build (`jenkins-push-image.sh` copies `:main` from internal Forgejo via Skopeo). -```env -REGISTRY_USER=epistemophiliac -REGISTRY_PASSWORD= -CUSTOM_IMAGE=git.aexoradao.com/epistemophiliac/erpnext -CUSTOM_TAG=main-26933f3 -PULL_POLICY=never +**Manual / one-time** on the Coolify server as root: + +```bash +export REGISTRY_USER=epistemophiliac +export REGISTRY_PASSWORD='' +bash scripts/coolify/sync-main-from-forgejo.sh ``` -Jenkins also preloads the host after each green build, so redeploys are fast. +Coolify env: `CUSTOM_TAG=main` only — no `main-` pins. ## 5. First deploy @@ -75,8 +73,8 @@ Login: `https://your-domain` — user `Administrator`, password = `ADMIN_PASSWOR ## 6. Upgrades -1. Jenkins builds new image → update `CUSTOM_TAG` in Coolify -2. Redeploy — `migrator` runs `bench migrate` +1. Jenkins builds new image → pushes `:main` and preloads host Docker +2. Redeploy Coolify — `if_not_present` uses the updated local `:main` ## Troubleshooting @@ -85,5 +83,5 @@ Login: `https://your-domain` — user `Administrator`, password = `ADMIN_PASSWOR | `SITE_NAME empty` on create-site | Assign domain on `frontend:8080` before deploy | | Wrong site / 404 nginx | Delete old `SITE_NAME` in Coolify UI; ensure header matches domain | | Site created with wrong name | Wipe `sites` volume or rename site manually — env change alone won't rename | -| Deploy fails at `Downloading …/487MB` / exit 255 | Image is OK — run `scripts/coolify/preload-image.sh` on host, set `PULL_POLICY=if_not_present`, redeploy | -| Image pull failed | Check `CUSTOM_IMAGE` / `CUSTOM_TAG` in Forgejo Packages; preload on host for private/large registry | +| Deploy fails at pull / `No such image` | Run `scripts/coolify/sync-main-from-forgejo.sh` on host, or Jenkins **Build Now** (preloads `:main`) | +| Image pull failed | Ensure `:main` on host via Jenkins or sync script — do not pull large image through Cloudflare | diff --git a/scripts/ci/jenkins-build-image.sh b/scripts/ci/jenkins-build-image.sh index c7b54bd..73d92f8 100755 --- a/scripts/ci/jenkins-build-image.sh +++ b/scripts/ci/jenkins-build-image.sh @@ -29,10 +29,10 @@ $DOCKER buildx build --load \ --file=images/layered/Containerfile . mkdir -p dist -echo "${REGISTRY_IMAGE}:${IMAGE_TAG}" > dist/image-reference.txt +echo "${REGISTRY_IMAGE}:main" > dist/image-reference.txt cat > dist/coolify-image.env </dev/null 2>&1; then - echo "ERROR: run as root on the Coolify host (docker info failed)" - exit 1 -fi - -echo "=== Preload ${REGISTRY_IMAGE}:${IMAGE_TAG} ===" -echo "Source: http://${FORGEJO_HOST}:3000/epistemophiliac/erpnext:${IMAGE_TAG} (internal)" - -docker run --rm \ - --network "${FORGEJO_NETWORK}" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - "${SKOPEO_IMAGE}" \ - copy \ - "docker://${FORGEJO_HOST}:3000/epistemophiliac/erpnext:${IMAGE_TAG}" \ - "docker-daemon:${REGISTRY_IMAGE}:${IMAGE_TAG}" \ - --src-creds "${REGISTRY_USER}:${REGISTRY_PASSWORD}" \ - --src-tls-verify=false \ - --retry-times 3 - -if [ "${IMAGE_TAG}" != "main" ]; then - docker tag "${REGISTRY_IMAGE}:${IMAGE_TAG}" "${REGISTRY_IMAGE}:main" - echo "Also tagged ${REGISTRY_IMAGE}:main" -fi - -echo "OK: ${REGISTRY_IMAGE}:${IMAGE_TAG} is on the host." -echo "Set PULL_POLICY=if_not_present in Coolify, then redeploy." diff --git a/scripts/coolify/sync-main-from-forgejo.sh b/scripts/coolify/sync-main-from-forgejo.sh new file mode 100755 index 0000000..e76270a --- /dev/null +++ b/scripts/coolify/sync-main-from-forgejo.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copy :main from internal Forgejo into host Docker (bypasses Cloudflare). +# Run on the Coolify host as root before first deploy, or after Jenkins pushes a new image. +set -euo pipefail + +REGISTRY_IMAGE="${CUSTOM_IMAGE:-git.aexoradao.com/epistemophiliac/erpnext}" +IMAGE_TAG="${CUSTOM_TAG:-main}" +FORGEJO_HOST="${FORGEJO_HOST:-forgejo-vydgeq365afzmxe4s1d75fwv}" +FORGEJO_NETWORK="${FORGEJO_NETWORK:-vydgeq365afzmxe4s1d75fwv}" +SKOPEO_IMAGE="${SKOPEO_IMAGE:-quay.io/skopeo/stable:v1.17.0}" + +if [ -z "${REGISTRY_USER:-}" ] || [ -z "${REGISTRY_PASSWORD:-}" ]; then + echo "ERROR: set REGISTRY_USER and REGISTRY_PASSWORD" + exit 1 +fi + +if ! docker info >/dev/null 2>&1; then + echo "ERROR: run as root on the Coolify host" + exit 1 +fi + +echo "=== Sync ${REGISTRY_IMAGE}:${IMAGE_TAG} from internal Forgejo ===" + +docker run --rm \ + --network "${FORGEJO_NETWORK}" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + "${SKOPEO_IMAGE}" \ + copy \ + "docker://${FORGEJO_HOST}:3000/epistemophiliac/erpnext:${IMAGE_TAG}" \ + "docker-daemon:${REGISTRY_IMAGE}:${IMAGE_TAG}" \ + --src-creds "${REGISTRY_USER}:${REGISTRY_PASSWORD}" \ + --src-tls-verify=false \ + --retry-times 3 + +echo "OK: ${REGISTRY_IMAGE}:${IMAGE_TAG} ready on host — deploy erpnext in Coolify"