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.
This commit is contained in:
epistemophiliac 2026-06-16 19:10:17 -04:00
parent 3eefb73727
commit 17c2c5ead8
16 changed files with 355 additions and 182 deletions

6
.dockerignore Normal file
View file

@ -0,0 +1,6 @@
.git
.ci-bin
dist
*.md
Jenkinsfile
Makefile

44
Jenkinsfile vendored
View file

@ -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-<sha> (+ :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.'
}
}
}

View file

@ -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 (~515 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-<sha>` 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.

22
apps.json Normal file
View file

@ -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"
}
]

View file

@ -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:

View file

@ -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, ~515 min)
pull custom image → db (healthy) → redis → configurator
→ create-site (install erpnext + payments + hrms + lending + lms, ~1020 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-<sha>`
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 |

View file

@ -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-<git-sha>` 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 **3060+ 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-<commit-sha>
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:<ERPNEXT_VERSION>` 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.

View file

@ -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

View file

@ -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"]

View file

@ -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 "$@"

19
resources/core/start.sh Normal file
View file

@ -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

View file

@ -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 <<EOF
CUSTOM_IMAGE=${REGISTRY_IMAGE}
CUSTOM_TAG=${IMAGE_TAG}
PULL_POLICY=always
EOF
echo "Built ${REGISTRY_IMAGE}:${IMAGE_TAG} and ${REGISTRY_IMAGE}:main"

View file

@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# shellcheck source=/dev/null
source .ci-bin/ci-env.sh
VERSION="$(grep -E '^ERPNEXT_VERSION=' example.env | cut -d= -f2)"
$DOCKER pull "frappe/erpnext:${VERSION}"
echo "frappe/erpnext:${VERSION} OK"

View file

@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
# shellcheck source=/dev/null
source .ci-bin/ci-env.sh
REGISTRY_IMAGE="${REGISTRY_IMAGE:-git.aexoradao.com/epistemophiliac/erpnext}"
REGISTRY_HOST="${REGISTRY_HOST:-git.aexoradao.com}"
GIT_SHA="$(git rev-parse --short HEAD)"
IMAGE_TAG="${IMAGE_TAG:-main-${GIT_SHA}}"
if [ -z "${REGISTRY_USER:-}" ] || [ -z "${REGISTRY_PASSWORD:-}" ]; then
echo "ERROR: set REGISTRY_USER and REGISTRY_PASSWORD (Jenkins forgejo-erpnext credentials)"
exit 1
fi
echo "$REGISTRY_PASSWORD" | $DOCKER login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin
$DOCKER push "${REGISTRY_IMAGE}:${IMAGE_TAG}"
$DOCKER push "${REGISTRY_IMAGE}:main"
echo "Pushed ${REGISTRY_IMAGE}:${IMAGE_TAG}"
echo "Pushed ${REGISTRY_IMAGE}:main"

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Local / all-in-one CI runner (same checks as Jenkinsfile stages).
# Local CI. Full image build + push: BUILD_IMAGE=1 REGISTRY_USER=... REGISTRY_PASSWORD=... bash scripts/ci/jenkins-run.sh
set -euo pipefail
echo "=== erpnext CI (local) ==="
@ -10,4 +10,15 @@ bash scripts/ci/ci-readiness.sh .
bash scripts/ci/validate-docker-compose.sh .
bash scripts/ci/jenkins-bootstrap.sh
bash scripts/ci/jenkins-compose-validate.sh
bash scripts/ci/jenkins-pull-image.sh
if [ "${BUILD_IMAGE:-0}" = "1" ]; then
bash scripts/ci/jenkins-build-image.sh
if [ -n "${REGISTRY_USER:-}" ] && [ -n "${REGISTRY_PASSWORD:-}" ]; then
bash scripts/ci/jenkins-push-image.sh
bash scripts/ci/jenkins-verify-image.sh
else
echo "Skip push: set REGISTRY_USER and REGISTRY_PASSWORD to publish"
fi
else
echo "Skip image build (set BUILD_IMAGE=1 to build locally)"
fi

View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
# shellcheck source=/dev/null
source .ci-bin/ci-env.sh
REF="$(cat dist/image-reference.txt)"
$DOCKER pull "$REF"
echo "Verified pull: $REF"