From 17c2c5ead8ec7101e223319044ee7c318a499716 Mon Sep 17 00:00:00 2001 From: epistemophiliac Date: Tue, 16 Jun 2026 19:10:17 -0400 Subject: [PATCH] Add custom Frappe image build with HRMS, Lending, and LMS. Jenkins builds from apps.json, pushes to Forgejo registry, and archives Coolify image tags; compose installs all apps on first site creation. --- .dockerignore | 6 ++ Jenkinsfile | 44 ++++++++++-- README.md | 66 ++++++++---------- apps.json | 22 ++++++ docker-compose.yml | 11 ++- docs/COOLIFY_DEPLOY.md | 86 +++++++---------------- docs/JENKINS.md | 108 +++++++++++++---------------- example.env | 14 +++- images/layered/Containerfile | 58 ++++++++++++++++ resources/core/main-entrypoint.sh | 12 ++++ resources/core/start.sh | 19 +++++ scripts/ci/jenkins-build-image.sh | 35 ++++++++++ scripts/ci/jenkins-pull-image.sh | 9 --- scripts/ci/jenkins-push-image.sh | 23 ++++++ scripts/ci/jenkins-run.sh | 15 +++- scripts/ci/jenkins-verify-image.sh | 9 +++ 16 files changed, 355 insertions(+), 182 deletions(-) create mode 100644 .dockerignore create mode 100644 apps.json create mode 100644 images/layered/Containerfile create mode 100644 resources/core/main-entrypoint.sh create mode 100644 resources/core/start.sh create mode 100755 scripts/ci/jenkins-build-image.sh delete mode 100755 scripts/ci/jenkins-pull-image.sh create mode 100755 scripts/ci/jenkins-push-image.sh create mode 100755 scripts/ci/jenkins-verify-image.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2aded29 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +.ci-bin +dist +*.md +Jenkinsfile +Makefile diff --git a/Jenkinsfile b/Jenkinsfile index 0e5214a..d5892f1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,13 @@ -// ERPNext → Coolify production CI (Forgejo: epistemophiliac/erpnext) +// ERPNext → Coolify: validate, build custom image, push to Forgejo registry pipeline { agent any + environment { + REGISTRY_IMAGE = 'git.aexoradao.com/epistemophiliac/erpnext' + REGISTRY_HOST = 'git.aexoradao.com' + FRAPPE_BRANCH = 'version-16' + } + options { timestamps() disableConcurrentBuilds() @@ -12,12 +18,14 @@ pipeline { stage('Verify') { steps { sh ''' - echo "=== ERPNext Coolify CI ===" + echo "=== ERPNext custom image CI ===" echo "commit: $(git rev-parse --short HEAD)" echo "branch: ${BRANCH_NAME:-main}" test -f Jenkinsfile test -f docker-compose.yml test -f example.env + test -f apps.json + test -f images/layered/Containerfile ''' } } @@ -44,20 +52,42 @@ pipeline { } } - stage('Verify ERPNext image') { + stage('Build custom image') { steps { - sh 'bash scripts/ci/jenkins-pull-image.sh' + sh 'bash scripts/ci/jenkins-build-image.sh' + } + } + + stage('Push to Forgejo registry') { + steps { + withCredentials([usernamePassword( + credentialsId: 'forgejo-erpnext', + usernameVariable: 'REGISTRY_USER', + passwordVariable: 'REGISTRY_PASSWORD' + )]) { + sh 'bash scripts/ci/jenkins-push-image.sh' + } + } + } + + stage('Verify registry pull') { + steps { + sh 'bash scripts/ci/jenkins-verify-image.sh' } } } post { success { - archiveArtifacts artifacts: 'dist/docker-compose.coolify.yml', fingerprint: true, onlyIfSuccessful: true - echo 'CI passed — safe to deploy docker-compose.yml on Coolify (set DB_PASSWORD, SITE_NAME, ADMIN_PASSWORD).' + archiveArtifacts artifacts: 'dist/*', fingerprint: true, onlyIfSuccessful: true + echo ''' + CI passed. + Image: git.aexoradao.com/epistemophiliac/erpnext:main- (+ :main) + Coolify: set CUSTOM_IMAGE / CUSTOM_TAG from dist/coolify-image.env + ''' } failure { - echo 'CI failed — do not deploy to Coolify until this build is green.' + echo 'CI failed — image was not published.' } } } diff --git a/README.md b/README.md index 528cb76..822305e 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,49 @@ # Production ERPNext on Coolify -Validated Docker Compose stack for [ERPNext](https://erpnext.com) on [Coolify](https://coolify.io), derived from [frappe/frappe_docker](https://github.com/frappe/frappe_docker). +Custom Docker image and Compose stack for [ERPNext](https://erpnext.com) plus **HRMS**, **Lending**, and **LMS (Learning)** on [Coolify](https://coolify.io). Derived from [frappe/frappe_docker](https://github.com/frappe/frappe_docker). **Repository:** https://git.aexoradao.com/epistemophiliac/erpnext -## Quick start (Coolify) +## Apps baked into the image -1. **New Resource** → **Docker Compose** -2. **Git repository:** `https://git.aexoradao.com/epistemophiliac/erpnext` -3. **Compose file:** `docker-compose.yml` -4. Set environment variables from [`example.env`](example.env) (at minimum `DB_PASSWORD`, `SITE_NAME`, `ADMIN_PASSWORD`) -5. Assign your domain to service **`frontend`**, port **`8080`** -6. Deploy — first boot creates the site and installs ERPNext (~5–15 minutes) +| App | Source | Branch | +|-----|--------|--------| +| ERPNext | frappe/erpnext | version-16 | +| Payments | frappe/payments | version-16 (required by LMS) | +| HRMS | frappe/hrms | version-16 | +| Lending | frappe/lending | version-16 | +| LMS (Learning) | frappe/lms | v2.55.0 | -Login: user `Administrator`, password = `ADMIN_PASSWORD`. +Defined in [`apps.json`](apps.json). Edit that file and push to change apps; Jenkins rebuilds the image. -## What this stack includes +## Pipeline (Jenkins) -| Service | Role | -|---------|------| -| `db` | MariaDB 11.8 | -| `redis-cache` / `redis-queue` | Cache and job queue | -| `configurator` | One-shot bench config | -| `create-site` | Idempotent site + ERPNext install | -| `migrator` | `bench migrate` on redeploy | -| `backend` | Gunicorn API | -| `frontend` | Nginx (port **8080**) | -| `websocket` | Socket.IO realtime | -| `queue-short` / `queue-long` / `scheduler` | Background workers | +1. Validate compose + readiness +2. **Build** custom image (`images/layered/Containerfile`) +3. **Push** to Forgejo registry: `git.aexoradao.com/epistemophiliac/erpnext:main-` and `:main` +4. Archive `dist/coolify-image.env` with `CUSTOM_IMAGE` / `CUSTOM_TAG` for Coolify -## CI (Jenkins) +See [docs/JENKINS.md](docs/JENKINS.md). -Jenkins runs the same checks on every build via [`Jenkinsfile`](Jenkinsfile): +## Coolify deploy (you configure) -- `scripts/ci/ci-readiness.sh` — secrets, docs, compose checks -- `scripts/ci/validate-docker-compose.sh` — Coolify compose rules + `docker compose config` -- `docker compose config` + pull pinned `frappe/erpnext` image +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 +3. Domain on service **`frontend`**, port **`8080`** -**Jenkins:** see [docs/JENKINS.md](docs/JENKINS.md) — Multibranch needs **Discover branches** behaviour, or use a simple **Pipeline** job on `main`. +See [docs/COOLIFY_DEPLOY.md](docs/COOLIFY_DEPLOY.md). -Run locally: +## Local checks ```bash -make ci +make ci # validate only +BUILD_IMAGE=1 bash scripts/ci/jenkins-run.sh # build image locally (slow) ``` -## Requirements +## Stack services -- Coolify server with **4 GB+ RAM** (8 GB recommended) -- Domain DNS pointing to your Coolify proxy -- `SITE_NAME` and `FRAPPE_SITE_NAME_HEADER` must match the Coolify domain - -## Documentation - -- [Coolify deploy guide](docs/COOLIFY_DEPLOY.md) -- [Upstream frappe_docker docs](https://frappe.github.io/frappe_docker/) +MariaDB, Redis, configurator, create-site, migrator, backend, frontend (8080), websocket, workers. ## License -Compose and docs: MIT. ERPNext/Frappe images: see upstream licenses. +Compose and docs: MIT. Frappe/ERPNext apps: see upstream licenses. diff --git a/apps.json b/apps.json new file mode 100644 index 0000000..d82caed --- /dev/null +++ b/apps.json @@ -0,0 +1,22 @@ +[ + { + "url": "https://github.com/frappe/erpnext", + "branch": "version-16" + }, + { + "url": "https://github.com/frappe/payments", + "branch": "version-16" + }, + { + "url": "https://github.com/frappe/hrms", + "branch": "version-16" + }, + { + "url": "https://github.com/frappe/lending", + "branch": "version-16" + }, + { + "url": "https://github.com/frappe/lms", + "branch": "v2.55.0" + } +] diff --git a/docker-compose.yml b/docker-compose.yml index 2875063..ea43dad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ # No ports: — routing uses SERVICE_URL_FRONTEND_8080. x-customizable-image: &customizable_image - image: ${CUSTOM_IMAGE:-frappe/erpnext}:${CUSTOM_TAG:-${ERPNEXT_VERSION:-v16.22.0}} + image: ${CUSTOM_IMAGE:-git.aexoradao.com/epistemophiliac/erpnext}:${CUSTOM_TAG:-main} pull_policy: ${PULL_POLICY:-always} restart: ${RESTART_POLICY:-unless-stopped} @@ -102,11 +102,18 @@ services: 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"; $$B new-site "$$SITE" --mariadb-user-host-login-scope='%' --admin-password "$$ADMIN_PASSWORD" --db-root-password "$$DB_PASSWORD" --install-app erpnext --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: - 'SITE_NAME=${SITE_NAME:-erp.example.com}' - '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: diff --git a/docs/COOLIFY_DEPLOY.md b/docs/COOLIFY_DEPLOY.md index 7f049b4..c2ff378 100644 --- a/docs/COOLIFY_DEPLOY.md +++ b/docs/COOLIFY_DEPLOY.md @@ -1,12 +1,13 @@ -# Coolify deployment — Production ERPNext +# Coolify deployment — Production ERPNext (+ HRMS, Lending, LMS) ## Prerequisites - Coolify v4+ with Docker Compose support -- Server: **minimum 4 GB RAM**, **8 GB+** for production workloads +- Jenkins green build published image to `git.aexoradao.com/epistemophiliac/erpnext` +- Server: **minimum 4 GB RAM**, **8 GB+** recommended (custom image + LMS frontend assets) - Public domain (e.g. `erp.yourdomain.com`) -## 1. Create the Coolify service +## 1. Create the Coolify service (you do this) | Setting | Value | |---------|--------| @@ -17,86 +18,51 @@ ## 2. Environment variables -Set these in **Coolify → Service → Environment Variables** before first deploy: +From latest **green Jenkins build**, use `dist/coolify-image.env` or: | Variable | Required | Example | Notes | |----------|----------|---------|-------| -| `ERPNEXT_VERSION` | yes | `v16.22.0` | Pin image tag | +| `CUSTOM_IMAGE` | yes | `git.aexoradao.com/epistemophiliac/erpnext` | Forgejo registry | +| `CUSTOM_TAG` | yes | `main-3eefb73` or `main` | Pin SHA for prod; `main` = latest CI | +| `PULL_POLICY` | yes | `always` | Pull from registry on deploy | | `DB_PASSWORD` | yes | strong secret | MariaDB root | | `SITE_NAME` | yes | `erp.yourdomain.com` | Must match domain | | `ADMIN_PASSWORD` | yes | strong secret | Frappe login | | `FRAPPE_SITE_NAME_HEADER` | yes | same as `SITE_NAME` | Single-site routing | -| `MIGRATE_SITES` | no | `true` | Run migrate on redeploy | +| `INSTALL_APPS` | yes | `erpnext,payments,hrms,lending,lms` | First site only | +| `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 after changes. +> **Coolify env cache:** Changing defaults in `docker-compose.yml` does not update values already stored in Coolify. Edit them in the UI. ## 3. Domain routing -1. Open the deployed service in Coolify -2. Add domain: `erp.yourdomain.com` -3. Attach domain to service **`frontend`** -4. Internal port: **`8080`** (Frappe nginx — not 80) - -The compose file sets `SERVICE_URL_FRONTEND_8080` so Coolify routes HTTPS to nginx correctly. +1. Add domain: `erp.yourdomain.com` +2. Attach to service **`frontend`** +3. Port **`8080`** ## 4. First deploy timeline ```text -db (healthy) → redis → configurator (exit 0) - → create-site (new-site + install-app erpnext, ~5–15 min) +pull custom image → db (healthy) → redis → configurator + → create-site (install erpnext + payments + hrms + lending + lms, ~10–20 min) → migrator → backend / workers / frontend ``` -Watch logs: +## 5. Upgrades -- `create-site` — site creation progress -- `backend` — gunicorn ready -- `frontend` — nginx on 8080 +1. Push app changes to git → Jenkins builds new image +2. Set `CUSTOM_TAG` in Coolify to new `main-` +3. Redeploy — `migrator` runs `bench migrate` -## 5. Post-deploy verification +## Apps in the image -From Coolify terminal on `frontend`: - -```bash -curl -sI http://localhost:8080/ -``` - -From your machine: - -```bash -curl -sI https://erp.yourdomain.com/ -``` - -Login at `https://erp.yourdomain.com` — user `Administrator`. - -## 6. Upgrades - -1. Bump `ERPNEXT_VERSION` in Coolify env vars -2. Redeploy — `migrator` runs `bench --site all migrate` -3. Confirm `migrator` logs show success +See [`apps.json`](../apps.json). Site install list: `INSTALL_APPS` in [`example.env`](../example.env). ## Troubleshooting | Symptom | Fix | |---------|-----| -| Coolify 404 | Domain on wrong service — must be `frontend:8080` | -| Site not found | `SITE_NAME` ≠ domain; fix `FRAPPE_SITE_NAME_HEADER` in UI | -| Stack unhealthy | Healthcheck port must be **8080** on frontend | -| create-site fails on redeploy | Should be idempotent — check `sites/$SITE_NAME` exists | -| Env change ignored | Update variable in Coolify UI, not only in git | - -## What we intentionally omit - -- No Traefik / nginx-proxy / Let's Encrypt in compose — Coolify handles TLS -- No `ports:` — Coolify proxy only -- No `pwd.yml` demo stack - -## CI / production gate - -Every merge to `main` should pass the Jenkins pipeline (`Jenkinsfile`) before Coolify deploy. - -Local check: - -```bash -make ci -``` +| Image pull failed | Check registry login on Coolify host; verify tag exists in Forgejo Packages | +| create-site fails on LMS | Ensure `payments` is in `INSTALL_APPS` before `lms` | +| 502 / unhealthy frontend | Wait for create-site; check `backend` health | +| Wrong site | `SITE_NAME` and `FRAPPE_SITE_NAME_HEADER` must match Coolify domain | diff --git a/docs/JENKINS.md b/docs/JENKINS.md index b993f57..f70722e 100644 --- a/docs/JENKINS.md +++ b/docs/JENKINS.md @@ -1,89 +1,77 @@ -# Jenkins setup (Forgejo) +# Jenkins CI — custom image build + Forgejo registry Repo: `https://git.aexoradao.com/epistemophiliac/erpnext.git` Branch: `main` -Pipeline file: `Jenkinsfile` (repo root) +Registry image: `git.aexoradao.com/epistemophiliac/erpnext` -## Option A — Simple Pipeline (fastest) +## What Jenkins does -If Multibranch shows an empty folder, use this instead. +| Stage | Purpose | +|-------|---------| +| **Verify** | Required files including `apps.json`, `Containerfile` | +| **Production readiness** | Secrets/docs/compose checks | +| **Bootstrap Docker tools** | Static docker CLI + compose, socket access | +| **Compose validate** | Coolify-safe `docker compose config` | +| **Build custom image** | `bench init` from `apps.json` (ERPNext, HRMS, Lending, LMS, payments) | +| **Push to Forgejo registry** | Tags `main-` and `main` | +| **Verify registry pull** | Confirms the pushed image is pullable | -1. **New Item** → **Pipeline** → name `erpnext` -2. **Pipeline** → Definition: **Pipeline script from SCM** -3. SCM: **Git** - - Repository URL: `https://git.aexoradao.com/epistemophiliac/erpnext.git` - - Credentials: Forgejo user + access token - - Branch: `*/main` -4. Script Path: `Jenkinsfile` -5. **Save** → **Build Now** +**Artifacts:** `dist/coolify-image.env`, `dist/docker-compose.coolify.yml`, `dist/image-reference.txt` -## Option B — Multibranch Pipeline +First image build can take **30–60+ minutes** (compiles assets). Later builds use Docker layer cache unless `apps.json` changes. -Indexing succeeds but the folder stays empty when **Discover branches** is missing. +## Jenkins job setup -1. **New Item** → **Multibranch Pipeline** → name `erpnext` -2. **Branch Sources** → **Git** - - URL + credentials (same as above) -3. **Behaviours** → **Add** → **Discover branches** - - Strategy: **All branches** (or include `main` via wildcard filter) -4. **Build Configuration** - - Mode: **by Jenkinsfile** - - Script Path: `Jenkinsfile` -5. **Save** → **Scan Repository Now** +Same as before — **Pipeline from SCM** or **Multibranch** with **Discover branches**. -You should see a `main` branch under the folder. Click it → **Build Now**. +**Credentials:** `forgejo-erpnext` (username + Forgejo token) — used for git checkout **and** `docker login git.aexoradao.com`. -### Optional: Forgejo webhook +Token needs: -Install the **Gitea** plugin in Jenkins, then use **Gitea** as the branch source (Forgejo-compatible) for automatic scans on push. +- Repo read (checkout) +- **Package write** (push container images to Forgejo registry) -## Credentials +Enable **Packages** on the Forgejo repo if pushes fail with 404/403. -**Manage Jenkins → Credentials → Add** +## After a green build -- Kind: Username with password -- Username: `epistemophiliac` -- Password: Forgejo personal access token (repo read scope) +Download `dist/coolify-image.env` from Jenkins artifacts, or use: -## Pipeline stages +```env +CUSTOM_IMAGE=git.aexoradao.com/epistemophiliac/erpnext +CUSTOM_TAG=main- +PULL_POLICY=always +``` -| Stage | What it checks | -|-------|----------------| -| **Verify** | `Jenkinsfile`, `docker-compose.yml`, `example.env` present | -| **Production readiness** | `ci-readiness.sh` + `validate-docker-compose.sh` (Coolify rules, no secrets tracked) | -| **Bootstrap Docker tools** | Static `docker` + `docker-compose` in `.ci-bin/`, socket access | -| **Compose validate** | `docker compose config` on Coolify-stripped compose (no `exclude_from_hc`, no host ports) | -| **Verify ERPNext image** | `docker pull frappe/erpnext:` from `example.env` | +Set those in Coolify before deploy (or use `CUSTOM_TAG=main` for latest main build). -**Post-success:** archives `dist/docker-compose.coolify.yml` (the compose file Coolify actually parses). +## Changing apps (HRMS, Lending, LMS, …) -This validates the repo before deploy; **Coolify deploy is separate** (set `DB_PASSWORD`, `SITE_NAME`, `ADMIN_PASSWORD`, domain on `frontend:8080`). +1. Edit [`apps.json`](../apps.json) (branches must match `FRAPPE_BRANCH=version-16` where applicable) +2. Push to `main` +3. Jenkins rebuilds and pushes a new image tag +4. Update `CUSTOM_TAG` in Coolify and redeploy ## Troubleshooting -### `fatal: not in a git directory` (branch indexing) - -Usually a **corrupt Jenkins git cache** after changing container user. In the **jenkins** container terminal (Coolify): - -```bash -rm -rf /var/jenkins_home/caches/git-* -chown -R jenkins:jenkins /var/jenkins_home -``` - -Then **Scan Repository Now** on the multibranch job. - ### `permission denied` on `/var/run/docker.sock` -Jenkins must be in the host **docker** group. On the Coolify host: +Set `DOCKER_GID` on the Jenkins Coolify service to the host docker group GID (`stat -c '%g' /var/run/docker.sock`), redeploy Jenkins. -```bash -stat -c '%g' /var/run/docker.sock -``` +### Registry push 401/403 -Set that number as `DOCKER_GID` on the **jenkins** Coolify service (was wrong at `999` on this host — use **`991`**), redeploy Jenkins, rebuild. +- Token needs **write:package** (or full repo scope including packages) +- `docker login git.aexoradao.com` with same credentials as git -Do **not** run Jenkins as `user: 0:0` — it breaks `jenkins_home` ownership and git caches. +### Build fails on `bench init` -### Always use **Build Now** on `main`, not **Rebuild** on old builds +- All apps in `apps.json` must be compatible with `version-16` +- LMS has no `version-16` branch — pinned to tag `v2.55.0` in `apps.json` -Old rebuilds replay old commits with old `Jenkinsfile` content. +### `source: not found` + +All pipeline steps use `bash scripts/ci/*.sh` — do not use `source` in bare `sh '''` blocks. + +### Use **Build Now**, not **Rebuild** on old runs + +Rebuild replays an old commit. diff --git a/example.env b/example.env index ad9217e..e7670af 100644 --- a/example.env +++ b/example.env @@ -1,8 +1,13 @@ # Copy to Coolify Environment Variables (Service > Environment). -# Upstream reference: https://github.com/frappe/frappe_docker/blob/main/docs/02-setup/04-env-variables.md +# Image tags come from Jenkins (Forgejo container registry). -# Image tag — pin for reproducible deploys -ERPNEXT_VERSION=v16.22.0 +# Custom image built by Jenkins (apps: ERPNext, HRMS, Lending, LMS + payments) +CUSTOM_IMAGE=git.aexoradao.com/epistemophiliac/erpnext +CUSTOM_TAG=main +PULL_POLICY=always + +# Frappe major line — must match apps.json branches (version-16) +FRAPPE_BRANCH=version-16 # MariaDB root password (required — change before production) DB_PASSWORD=changeme @@ -16,6 +21,9 @@ ADMIN_PASSWORD=changeme # Nginx site header — for single-site Coolify, same as SITE_NAME FRAPPE_SITE_NAME_HEADER=erp.example.com +# Apps installed on first site creation (comma-separated, order matters) +INSTALL_APPS=erpnext,payments,hrms,lending,lms + # Run bench migrate on every deploy (set false to skip) MIGRATE_SITES=true diff --git a/images/layered/Containerfile b/images/layered/Containerfile new file mode 100644 index 0000000..5005c9a --- /dev/null +++ b/images/layered/Containerfile @@ -0,0 +1,58 @@ +# Custom ERPNext image (HRMS, Lending, LMS + dependencies). +# Pattern: https://github.com/frappe/frappe_docker/tree/main/images/layered +ARG FRAPPE_BRANCH=version-16 +ARG FRAPPE_IMAGE_PREFIX=frappe + +FROM ${FRAPPE_IMAGE_PREFIX}/build:${FRAPPE_BRANCH} AS builder + +ARG FRAPPE_BRANCH=version-16 +ARG FRAPPE_PATH=https://github.com/frappe/frappe +ARG CACHE_BUST="" + +USER frappe + +RUN --mount=type=secret,id=apps_json,target=/opt/frappe/apps.json,uid=1000,gid=1000 \ + : "${CACHE_BUST}" && \ + export APP_INSTALL_ARGS="" && \ + if [ -f /opt/frappe/apps.json ] && [ -s /opt/frappe/apps.json ]; then \ + export APP_INSTALL_ARGS="--apps_path=/opt/frappe/apps.json"; \ + fi && \ + bench init ${APP_INSTALL_ARGS}\ + --frappe-branch=${FRAPPE_BRANCH} \ + --frappe-path=${FRAPPE_PATH} \ + --no-procfile \ + --no-backups \ + --skip-redis-config-generation \ + --verbose \ + /home/frappe/frappe-bench && \ + cd /home/frappe/frappe-bench && \ + echo "{}" > sites/common_site_config.json && \ + find apps -mindepth 1 -path "*/.git" | xargs rm -fr + +FROM ${FRAPPE_IMAGE_PREFIX}/base:${FRAPPE_BRANCH} AS backend + +USER frappe + +COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench + +WORKDIR /home/frappe/frappe-bench + +RUN cp -r /home/frappe/frappe-bench/sites/assets /home/frappe/frappe-bench/assets && \ + rm -rf /home/frappe/frappe-bench/sites/assets + +VOLUME [ \ + "/home/frappe/frappe-bench/sites", \ + "/home/frappe/frappe-bench/logs" \ +] + +USER root +COPY resources/core/main-entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod 755 /usr/local/bin/entrypoint.sh + +COPY resources/core/start.sh /usr/local/bin/start.sh +RUN chmod 755 /usr/local/bin/start.sh + +USER frappe +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] + +CMD ["start.sh"] diff --git a/resources/core/main-entrypoint.sh b/resources/core/main-entrypoint.sh new file mode 100644 index 0000000..d1e8960 --- /dev/null +++ b/resources/core/main-entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +ASSETS_PATH="/home/frappe/frappe-bench/sites/assets" +BAKED_PATH="/home/frappe/frappe-bench/assets" + +echo "Linking fresh assets to volume..." +rm -rf "$ASSETS_PATH" +mkdir -p "$(dirname "$ASSETS_PATH")" +ln -s "$BAKED_PATH" "$ASSETS_PATH" + +exec "$@" diff --git a/resources/core/start.sh b/resources/core/start.sh new file mode 100644 index 0000000..2dce20c --- /dev/null +++ b/resources/core/start.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +GUNICORN_THREADS=${GUNICORN_THREADS:-4} +GUNICORN_WORKERS=${GUNICORN_WORKERS:-2} +GUNICORN_TIMEOUT=${GUNICORN_TIMEOUT:-120} + +echo "Booting Gunicorn with $GUNICORN_WORKERS workers and $GUNICORN_THREADS threads..." + +exec /home/frappe/frappe-bench/env/bin/gunicorn \ + --chdir=/home/frappe/frappe-bench/sites \ + --bind=0.0.0.0:8000 \ + --threads="$GUNICORN_THREADS" \ + --workers="$GUNICORN_WORKERS" \ + --worker-class=gthread \ + --worker-tmp-dir=/dev/shm \ + --timeout="$GUNICORN_TIMEOUT" \ + --preload \ + frappe.app:application diff --git a/scripts/ci/jenkins-build-image.sh b/scripts/ci/jenkins-build-image.sh new file mode 100755 index 0000000..6ed9895 --- /dev/null +++ b/scripts/ci/jenkins-build-image.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=/dev/null +source .ci-bin/ci-env.sh + +FRAPPE_BRANCH="${FRAPPE_BRANCH:-version-16}" +REGISTRY_IMAGE="${REGISTRY_IMAGE:-git.aexoradao.com/epistemophiliac/erpnext}" +GIT_SHA="$(git rev-parse --short HEAD)" +IMAGE_TAG="${IMAGE_TAG:-main-${GIT_SHA}}" +CACHE_BUST="$(sha256sum apps.json | awk '{print $1}')" + +export DOCKER_BUILDKIT=1 + +echo "=== Building ${REGISTRY_IMAGE}:${IMAGE_TAG} ===" +echo "FRAPPE_BRANCH=${FRAPPE_BRANCH}" +echo "apps.json sha256=${CACHE_BUST}" + +$DOCKER build \ + --build-arg="FRAPPE_BRANCH=${FRAPPE_BRANCH}" \ + --build-arg="CACHE_BUST=${CACHE_BUST}" \ + --secret=id=apps_json,src=apps.json \ + --tag="${REGISTRY_IMAGE}:${IMAGE_TAG}" \ + --tag="${REGISTRY_IMAGE}:main" \ + --file=images/layered/Containerfile . + +mkdir -p dist +echo "${REGISTRY_IMAGE}:${IMAGE_TAG}" > dist/image-reference.txt +cat > dist/coolify-image.env <