Wire Coolify domain to SITE_NAME and document env template

SERVICE_FQDN_FRONTEND from the frontend domain drives site creation and nginx
headers; coolify.env.example adds CUSTOM_IMAGE/CUSTOM_TAG for Jenkins registry pulls.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
epistemophiliac 2026-06-16 20:34:44 -04:00
parent cacbce384f
commit f2f7e6355d
6 changed files with 102 additions and 63 deletions

View file

@ -28,8 +28,8 @@ See [docs/JENKINS.md](docs/JENKINS.md).
## Coolify deploy (you configure) ## Coolify deploy (you configure)
1. Docker Compose from this git repo, file `docker-compose.yml` 1. Docker Compose from this git repo, file `docker-compose.yml`
2. Env vars from [`example.env`](example.env) — use `CUSTOM_TAG` from latest green Jenkins build 2. Env vars from [`coolify.env.example`](coolify.env.example) — `CUSTOM_IMAGE`, `CUSTOM_TAG` from latest green Jenkins build (`dist/coolify-image.env`)
3. Domain on service **`frontend`**, port **`8080`** 3. Domain on service **`frontend`**, port **`8080`** Coolify sets `SERVICE_FQDN_FRONTEND`; compose uses it for `SITE_NAME` / `FRAPPE_SITE_NAME_HEADER`
See [docs/COOLIFY_DEPLOY.md](docs/COOLIFY_DEPLOY.md). See [docs/COOLIFY_DEPLOY.md](docs/COOLIFY_DEPLOY.md).

36
coolify.env.example Normal file
View file

@ -0,0 +1,36 @@
# 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=git.aexoradao.com/epistemophiliac/erpnext
CUSTOM_TAG=main-26933f3
PULL_POLICY=always
# --- Secrets (required — change before first deploy) ---
DB_PASSWORD=replace-with-strong-secret
ADMIN_PASSWORD=replace-with-strong-secret
# --- Domain (automatic — do NOT set unless overriding) ---
# 1. In Coolify UI: add domain on service "frontend", port 8080
# 2. Coolify sets SERVICE_FQDN_FRONTEND → compose uses it for SITE_NAME + nginx header
# 3. Deploy AFTER domain is assigned (first deploy creates the Frappe site)
#
# SITE_NAME=
# FRAPPE_SITE_NAME_HEADER=
# --- Apps installed on first site creation only (order matters) ---
INSTALL_APPS=erpnext,payments,hrms,lending,lms
# --- Redeploy behaviour ---
MIGRATE_SITES=true
RESTART_POLICY=unless-stopped
# --- Optional tuning ---
GUNICORN_THREADS=4
GUNICORN_WORKERS=2
GUNICORN_TIMEOUT=120
PROXY_READ_TIMEOUT=120
CLIENT_MAX_BODY_SIZE=50m
UPSTREAM_REAL_IP_ADDRESS=127.0.0.1
UPSTREAM_REAL_IP_HEADER=X-Forwarded-For
UPSTREAM_REAL_IP_RECURSIVE=off

View file

@ -1,6 +1,7 @@
# ERPNext production stack for Coolify. # ERPNext production stack for Coolify.
# Based on https://github.com/frappe/frappe_docker (compose.yaml + mariadb + redis). # Domain: assign in Coolify UI → service `frontend` → port 8080.
# Coolify: assign your domain to service `frontend` on 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).
# No ports: — routing uses SERVICE_URL_FRONTEND_8080. # No ports: — routing uses SERVICE_URL_FRONTEND_8080.
x-customizable-image: &customizable_image x-customizable-image: &customizable_image
@ -99,12 +100,13 @@ services:
- > - >
B=/usr/local/bin/bench; B=/usr/local/bin/bench;
SITE=$$SITE_NAME; 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 db:3306;
wait-for-it -t 120 redis-cache:6379; wait-for-it -t 120 redis-cache:6379;
wait-for-it -t 120 redis-queue: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 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: environment:
- 'SITE_NAME=${SITE_NAME:-erp.example.com}' - 'SITE_NAME=${SITE_NAME:-${SERVICE_FQDN_FRONTEND}}'
- 'ADMIN_PASSWORD=${ADMIN_PASSWORD:-changeme}' - 'ADMIN_PASSWORD=${ADMIN_PASSWORD:-changeme}'
- 'DB_PASSWORD=${DB_PASSWORD:-changeme}' - 'DB_PASSWORD=${DB_PASSWORD:-changeme}'
- 'INSTALL_APPS=${INSTALL_APPS:-erpnext,payments,hrms,lending,lms}' - 'INSTALL_APPS=${INSTALL_APPS:-erpnext,payments,hrms,lending,lms}'
@ -172,9 +174,10 @@ services:
- nginx-entrypoint.sh - nginx-entrypoint.sh
environment: environment:
- SERVICE_URL_FRONTEND_8080 - SERVICE_URL_FRONTEND_8080
- SERVICE_FQDN_FRONTEND
- 'BACKEND=backend:8000' - 'BACKEND=backend:8000'
- 'SOCKETIO=websocket:9000' - 'SOCKETIO=websocket:9000'
- 'FRAPPE_SITE_NAME_HEADER=${FRAPPE_SITE_NAME_HEADER:-${SITE_NAME:-erp.example.com}}' - '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_ADDRESS=${UPSTREAM_REAL_IP_ADDRESS:-127.0.0.1}'
- 'UPSTREAM_REAL_IP_HEADER=${UPSTREAM_REAL_IP_HEADER:-X-Forwarded-For}' - 'UPSTREAM_REAL_IP_HEADER=${UPSTREAM_REAL_IP_HEADER:-X-Forwarded-For}'
- 'UPSTREAM_REAL_IP_RECURSIVE=${UPSTREAM_REAL_IP_RECURSIVE:-off}' - 'UPSTREAM_REAL_IP_RECURSIVE=${UPSTREAM_REAL_IP_RECURSIVE:-off}'

View file

@ -3,11 +3,11 @@
## Prerequisites ## Prerequisites
- Coolify v4+ with Docker Compose support - Coolify v4+ with Docker Compose support
- Jenkins green build published image to `git.aexoradao.com/epistemophiliac/erpnext` - Jenkins green build → image in Forgejo Packages
- Server: **minimum 4 GB RAM**, **8 GB+** recommended (custom image + LMS frontend assets) - **4 GB+ RAM** (8 GB+ recommended)
- Public domain (e.g. `erp.yourdomain.com`) - Compose file: `docker-compose.yml`
## 1. Create the Coolify service (you do this) ## 1. Create the Coolify service
| Setting | Value | | Setting | Value |
|---------|--------| |---------|--------|
@ -16,53 +16,55 @@
| Branch | `main` | | Branch | `main` |
| Compose file | `docker-compose.yml` | | Compose file | `docker-compose.yml` |
## 2. Environment variables ## 2. Environment variables (Coolify UI)
From latest **green Jenkins build**, use `dist/coolify-image.env` or: Copy from [`coolify.env.example`](../coolify.env.example). **Required before first deploy:**
| Variable | Required | Example | Notes | | Variable | Set in Coolify? | Source |
|----------|----------|---------|-------| |----------|----------------|--------|
| `CUSTOM_IMAGE` | yes | `git.aexoradao.com/epistemophiliac/erpnext` | Forgejo registry | | `CUSTOM_IMAGE` | yes | Jenkins artifact / `dist/coolify-image.env` |
| `CUSTOM_TAG` | yes | `main-3eefb73` or `main` | Pin SHA for prod; `main` = latest CI | | `CUSTOM_TAG` | yes | e.g. `main-26933f3` (pin) or `main` |
| `PULL_POLICY` | yes | `always` | Pull from registry on deploy | | `PULL_POLICY` | yes | `always` |
| `DB_PASSWORD` | yes | strong secret | MariaDB root | | `DB_PASSWORD` | yes | strong secret |
| `SITE_NAME` | yes | `erp.yourdomain.com` | Must match domain | | `ADMIN_PASSWORD` | yes | Frappe `Administrator` password |
| `ADMIN_PASSWORD` | yes | strong secret | Frappe login | | `INSTALL_APPS` | yes | `erpnext,payments,hrms,lending,lms` |
| `FRAPPE_SITE_NAME_HEADER` | yes | same as `SITE_NAME` | Single-site routing | | `SITE_NAME` | **no** (auto) | From Coolify domain via `SERVICE_FQDN_FRONTEND` |
| `INSTALL_APPS` | yes | `erpnext,payments,hrms,lending,lms` | First site only | | `FRAPPE_SITE_NAME_HEADER` | **no** (auto) | Same as domain via `SERVICE_FQDN_FRONTEND` |
| `MIGRATE_SITES` | no | `true` | Migrate on redeploy |
> **Coolify env cache:** Changing defaults in `docker-compose.yml` does not update values already stored in Coolify. Edit them in the UI. > **Coolify env cache:** If you previously set `SITE_NAME=erp.example.com` in Coolify, **delete it** so compose defaults use your real domain. Changing `docker-compose.yml` defaults alone does not update stored values.
## 3. Domain routing ## 3. Domain (before first deploy)
1. Add domain: `erp.yourdomain.com` 1. Coolify → your service → **Domains**
2. Attach to service **`frontend`** 2. Add domain, e.g. `erp.aexoradao.com`
3. Port **`8080`** 3. Attach to service **`frontend`**, port **`8080`**
4. Coolify writes `SERVICE_FQDN_FRONTEND=erp.aexoradao.com` into the stack `.env`
5. Compose sets:
- `create-site``SITE_NAME=erp.aexoradao.com`
- `frontend``FRAPPE_SITE_NAME_HEADER=erp.aexoradao.com`
## 4. First deploy timeline **Order matters:** assign domain **then** deploy. If `create-site` runs with an empty site name, the stack exits with a clear error.
## 4. First deploy
```text ```text
pull custom image → db (healthy) → redis → configurator pull CUSTOM_IMAGE:TAG → db → redis → configurator
→ create-site (install erpnext + payments + hrms + lending + lms, ~1020 min) → create-site (install apps, ~1020 min)
→ migrator → backend / workers / frontend → migrator → backend / workers / frontend
``` ```
Login: `https://your-domain` — user `Administrator`, password = `ADMIN_PASSWORD`.
## 5. Upgrades ## 5. Upgrades
1. Push app changes to git → Jenkins builds new image 1. Jenkins builds new image → update `CUSTOM_TAG` in Coolify
2. Set `CUSTOM_TAG` in Coolify to new `main-<sha>` 2. Redeploy — `migrator` runs `bench migrate`
3. Redeploy — `migrator` runs `bench migrate`
## Apps in the image
See [`apps.json`](../apps.json). Site install list: `INSTALL_APPS` in [`example.env`](../example.env).
## Troubleshooting ## Troubleshooting
| Symptom | Fix | | Symptom | Fix |
|---------|-----| |---------|-----|
| Image pull failed | Check registry login on Coolify host; verify tag exists in Forgejo Packages | | `SITE_NAME empty` on create-site | Assign domain on `frontend:8080` before deploy |
| create-site fails on LMS | Ensure `payments` is in `INSTALL_APPS` before `lms` | | Wrong site / 404 nginx | Delete old `SITE_NAME` in Coolify UI; ensure header matches domain |
| 502 / unhealthy frontend | Wait for create-site; check `backend` health | | Site created with wrong name | Wipe `sites` volume or rename site manually — env change alone won't rename |
| Wrong site | `SITE_NAME` and `FRAPPE_SITE_NAME_HEADER` must match Coolify domain | | Image pull failed | Check `CUSTOM_IMAGE` / `CUSTOM_TAG` in Forgejo Packages |

View file

@ -1,38 +1,24 @@
# Copy to Coolify Environment Variables (Service > Environment). # Reference for local testing (`docker compose --env-file example.env config`).
# Image tags come from Jenkins (Forgejo container registry). # For Coolify, use coolify.env.example — domain comes from SERVICE_FQDN_FRONTEND.
# Custom image built by Jenkins (apps: ERPNext, HRMS, Lending, LMS + payments) # Custom image (match latest Jenkins build — see dist/coolify-image.env)
CUSTOM_IMAGE=git.aexoradao.com/epistemophiliac/erpnext CUSTOM_IMAGE=git.aexoradao.com/epistemophiliac/erpnext
CUSTOM_TAG=main CUSTOM_TAG=main
PULL_POLICY=always PULL_POLICY=always
RESTART_POLICY=unless-stopped
# Frappe major line — must match apps.json branches (version-16) # Local-only overrides when not using Coolify magic vars
FRAPPE_BRANCH=version-16
# MariaDB root password (required — change before production)
DB_PASSWORD=changeme
# Frappe site name — MUST match your Coolify domain
SITE_NAME=erp.example.com SITE_NAME=erp.example.com
# Frappe Administrator password
ADMIN_PASSWORD=changeme
# Nginx site header — for single-site Coolify, same as SITE_NAME
FRAPPE_SITE_NAME_HEADER=erp.example.com FRAPPE_SITE_NAME_HEADER=erp.example.com
# Apps installed on first site creation (comma-separated, order matters) DB_PASSWORD=changeme
ADMIN_PASSWORD=changeme
INSTALL_APPS=erpnext,payments,hrms,lending,lms INSTALL_APPS=erpnext,payments,hrms,lending,lms
# Run bench migrate on every deploy (set false to skip)
MIGRATE_SITES=true MIGRATE_SITES=true
# Gunicorn tuning (optional)
GUNICORN_THREADS=4 GUNICORN_THREADS=4
GUNICORN_WORKERS=2 GUNICORN_WORKERS=2
GUNICORN_TIMEOUT=120 GUNICORN_TIMEOUT=120
# Proxy / upload limits (optional)
PROXY_READ_TIMEOUT=120 PROXY_READ_TIMEOUT=120
CLIENT_MAX_BODY_SIZE=50m CLIENT_MAX_BODY_SIZE=50m
UPSTREAM_REAL_IP_ADDRESS=127.0.0.1 UPSTREAM_REAL_IP_ADDRESS=127.0.0.1

View file

@ -66,6 +66,18 @@ else
err "[DC-03] missing SERVICE_URL_FRONTEND_8080 on frontend" err "[DC-03] missing SERVICE_URL_FRONTEND_8080 on frontend"
fi fi
if grep -q 'SERVICE_FQDN_FRONTEND' "$COMPOSE_FILE"; then
pass "[DC-07] SERVICE_FQDN_FRONTEND present (Coolify domain → SITE_NAME)"
else
warn "[DC-07] missing SERVICE_FQDN_FRONTEND — SITE_NAME will not track Coolify domain"
fi
if grep -q 'CUSTOM_IMAGE' "$COMPOSE_FILE" && grep -q 'CUSTOM_TAG' "$COMPOSE_FILE"; then
pass "[DC-10] CUSTOM_IMAGE / CUSTOM_TAG configured for Jenkins registry"
else
warn "[DC-10] missing CUSTOM_IMAGE or CUSTOM_TAG in compose"
fi
# Bind mounts ./scripts on long-running services (heuristic) # Bind mounts ./scripts on long-running services (heuristic)
if grep -E '^\s+-\s+['\''"]?\./scripts' "$COMPOSE_FILE" >/dev/null 2>&1; then if grep -E '^\s+-\s+['\''"]?\./scripts' "$COMPOSE_FILE" >/dev/null 2>&1; then
if grep -B30 './scripts' "$COMPOSE_FILE" | grep -qE 'restart:\s+(unless-stopped|always)'; then if grep -B30 './scripts' "$COMPOSE_FILE" | grep -qE 'restart:\s+(unless-stopped|always)'; then