From 0281722f7530a3e50401187247682e7be55c6c9f Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:05:00 +0200 Subject: [PATCH 1/6] feat(ci): split core image workflows and publish base images to ghcr --- .github/scripts/get_latest_tags.py | 10 ++ .github/workflows/build_develop.yml | 33 ------- .../{build_bench.yml => core-build-bench.yml} | 4 +- .github/workflows/core-build-develop.yml | 49 ++++++++++ ...build_stable.yml => core-build-stable.yml} | 48 ++++++++-- ...ld-push.yml => core-build-test-images.yml} | 57 +++++++----- .github/workflows/core-publish-images.yml | 92 +++++++++++++++++++ ...publish_docs.yml => docs-publish-site.yml} | 4 +- CONTRIBUTING.md | 2 +- README.md | 4 +- docker-bake.hcl | 4 + 11 files changed, 232 insertions(+), 75 deletions(-) delete mode 100644 .github/workflows/build_develop.yml rename .github/workflows/{build_bench.yml => core-build-bench.yml} (95%) create mode 100644 .github/workflows/core-build-develop.yml rename .github/workflows/{build_stable.yml => core-build-stable.yml} (67%) rename .github/workflows/{docker-build-push.yml => core-build-test-images.yml} (64%) create mode 100644 .github/workflows/core-publish-images.yml rename .github/workflows/{publish_docs.yml => docs-publish-site.yml} (93%) diff --git a/.github/scripts/get_latest_tags.py b/.github/scripts/get_latest_tags.py index 2eb62fb7..526716ce 100644 --- a/.github/scripts/get_latest_tags.py +++ b/.github/scripts/get_latest_tags.py @@ -49,6 +49,13 @@ def update_env(file_name: str, frappe_tag: str, erpnext_tag: str | None = None): f.write(text) +def update_output(file_name: str, frappe_tag: str, erpnext_tag: str | None = None): + with open(file_name, "a", encoding="utf-8") as f: + f.write(f"frappe_version={frappe_tag}\n") + if erpnext_tag: + f.write(f"erpnext_version={erpnext_tag}\n") + + def _print_resp(frappe_tag: str, erpnext_tag: str | None = None): print(json.dumps({"frappe": frappe_tag, "erpnext": erpnext_tag})) @@ -70,6 +77,9 @@ def main(_args: list[str]) -> int: file_name = os.getenv("GITHUB_ENV") if file_name: update_env(file_name, frappe_tag, erpnext_tag) + file_name = os.getenv("GITHUB_OUTPUT") + if file_name: + update_output(file_name, frappe_tag, erpnext_tag) _print_resp(frappe_tag, erpnext_tag) return 0 diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml deleted file mode 100644 index 8ec061ed..00000000 --- a/.github/workflows/build_develop.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Develop build - -on: - pull_request: - branches: - - main - paths: - - images/production/** - - overrides/** - - tests/** - - compose.yaml - - docker-bake.hcl - - example.env - - .github/workflows/build_develop.yml - - schedule: - # Every day at 12:00 pm - - cron: 0 0 * * * - - workflow_dispatch: - -jobs: - build: - uses: ./.github/workflows/docker-build-push.yml - with: - repo: erpnext - version: develop - push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - python_version: 3.14.2 - node_version: 24.12.0 - secrets: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/build_bench.yml b/.github/workflows/core-build-bench.yml similarity index 95% rename from .github/workflows/build_bench.yml rename to .github/workflows/core-build-bench.yml index 1bc47018..ef0ecbd0 100644 --- a/.github/workflows/build_bench.yml +++ b/.github/workflows/core-build-bench.yml @@ -1,4 +1,4 @@ -name: Bench +name: Core / Build Bench on: pull_request: @@ -7,7 +7,7 @@ on: paths: - images/bench/** - docker-bake.hcl - - .github/workflows/build_bench.yml + - .github/workflows/core-build-bench.yml schedule: # Every day at 12:00 pm diff --git a/.github/workflows/core-build-develop.yml b/.github/workflows/core-build-develop.yml new file mode 100644 index 00000000..64800241 --- /dev/null +++ b/.github/workflows/core-build-develop.yml @@ -0,0 +1,49 @@ +name: Core / Build Develop + +permissions: + contents: read + packages: write + +on: + pull_request: + branches: + - main + paths: + - images/production/** + - overrides/** + - tests/** + - compose.yaml + - docker-bake.hcl + - example.env + - .github/workflows/core-build-develop.yml + - .github/workflows/core-build-test-images.yml + - .github/workflows/core-publish-images.yml + + schedule: + # Every day at 12:00 pm + - cron: 0 0 * * * + + workflow_dispatch: + +jobs: + test: + uses: ./.github/workflows/core-build-test-images.yml + with: + repo: erpnext + version: develop + python_version: 3.14.2 + node_version: 24.12.0 + + publish: + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + needs: test + uses: ./.github/workflows/core-publish-images.yml + with: + repo: erpnext + frappe_version: ${{ needs.test.outputs.frappe_version }} + erpnext_version: ${{ needs.test.outputs.erpnext_version }} + python_version: 3.14.2 + node_version: 24.12.0 + secrets: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/build_stable.yml b/.github/workflows/core-build-stable.yml similarity index 67% rename from .github/workflows/build_stable.yml rename to .github/workflows/core-build-stable.yml index 174516bc..6c453adb 100644 --- a/.github/workflows/build_stable.yml +++ b/.github/workflows/core-build-stable.yml @@ -1,4 +1,8 @@ -name: Stable build +name: Core / Build Stable + +permissions: + contents: read + packages: write on: pull_request: @@ -11,7 +15,9 @@ on: - compose.yaml - docker-bake.hcl - example.env - - .github/workflows/build_stable.yml + - .github/workflows/core-build-stable.yml + - .github/workflows/core-build-test-images.yml + - .github/workflows/core-publish-images.yml push: branches: @@ -23,6 +29,9 @@ on: - compose.yaml - docker-bake.hcl - example.env + - .github/workflows/core-build-stable.yml + - .github/workflows/core-build-test-images.yml + - .github/workflows/core-publish-images.yml # Triggered from frappe/frappe and frappe/erpnext on releases repository_dispatch: @@ -30,24 +39,43 @@ on: workflow_dispatch: jobs: - v15: - uses: ./.github/workflows/docker-build-push.yml + v15_test: + uses: ./.github/workflows/core-build-test-images.yml with: repo: erpnext version: "15" - push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + python_version: 3.11.6 + node_version: 20.19.2 + + v15_publish: + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + needs: v15_test + uses: ./.github/workflows/core-publish-images.yml + with: + repo: erpnext + frappe_version: ${{ needs.v15_test.outputs.frappe_version }} + erpnext_version: ${{ needs.v15_test.outputs.erpnext_version }} python_version: 3.11.6 node_version: 20.19.2 secrets: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - v16: - uses: ./.github/workflows/docker-build-push.yml + v16_test: + uses: ./.github/workflows/core-build-test-images.yml with: repo: erpnext version: "16" - push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + python_version: 3.14.2 + node_version: 24.12.0 + v16_publish: + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + needs: v16_test + uses: ./.github/workflows/core-publish-images.yml + with: + repo: erpnext + frappe_version: ${{ needs.v16_test.outputs.frappe_version }} + erpnext_version: ${{ needs.v16_test.outputs.erpnext_version }} python_version: 3.14.2 node_version: 24.12.0 secrets: @@ -58,7 +86,7 @@ jobs: name: Update example.env and pwd.yml runs-on: ubuntu-latest if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - needs: v16 + needs: v16_publish steps: - name: Checkout @@ -96,7 +124,7 @@ jobs: name: Release Helm runs-on: ubuntu-latest if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - needs: v16 + needs: v16_publish steps: - name: Setup deploy key diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/core-build-test-images.yml similarity index 64% rename from .github/workflows/docker-build-push.yml rename to .github/workflows/core-build-test-images.yml index 9fee1782..7c118c93 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/core-build-test-images.yml @@ -1,4 +1,4 @@ -name: Build +name: Core / Build and Test Images on: workflow_call: @@ -11,9 +11,6 @@ on: required: true type: string description: "Major version, git tags should match 'v{version}.*'; or 'develop'" - push: - required: true - type: boolean python_version: required: true type: string @@ -22,16 +19,36 @@ on: required: true type: string description: NodeJS Version - secrets: - DOCKERHUB_USERNAME: - required: true - DOCKERHUB_TOKEN: - required: true + outputs: + frappe_version: + description: "Resolved frappe image tag" + value: ${{ jobs.resolve.outputs.frappe_version }} + erpnext_version: + description: "Resolved erpnext image tag" + value: ${{ jobs.resolve.outputs.erpnext_version }} + +permissions: + contents: read jobs: + resolve: + name: Resolve Versions + runs-on: ubuntu-latest + outputs: + frappe_version: ${{ steps.resolve.outputs.frappe_version }} + erpnext_version: ${{ steps.resolve.outputs.erpnext_version }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Resolve image versions + id: resolve + run: python3 ./.github/scripts/get_latest_tags.py --repo ${{ inputs.repo }} --version ${{ inputs.version }} + build: name: Build runs-on: ubuntu-latest + needs: resolve services: registry: image: docker.io/registry:2 @@ -57,8 +74,12 @@ jobs: driver-opts: network=host platforms: linux/${{ matrix.arch }} - - name: Get latest versions - run: python3 ./.github/scripts/get_latest_tags.py --repo ${{ inputs.repo }} --version ${{ inputs.version }} + - name: Set resolved versions + run: | + echo "FRAPPE_VERSION=${{ needs.resolve.outputs.frappe_version }}" >> "$GITHUB_ENV" + if [ -n "${{ needs.resolve.outputs.erpnext_version }}" ]; then + echo "ERPNEXT_VERSION=${{ needs.resolve.outputs.erpnext_version }}" >> "$GITHUB_ENV" + fi - name: Set build args run: | @@ -85,17 +106,3 @@ jobs: - name: Test run: venv/bin/pytest --color=yes - - - name: Login - if: ${{ inputs.push }} - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Push - if: ${{ inputs.push }} - uses: docker/bake-action@v7.1.0 - with: - push: true - set: "*.platform=linux/amd64,linux/arm64" diff --git a/.github/workflows/core-publish-images.yml b/.github/workflows/core-publish-images.yml new file mode 100644 index 00000000..4da6bfee --- /dev/null +++ b/.github/workflows/core-publish-images.yml @@ -0,0 +1,92 @@ +name: Core / Publish Images + +on: + workflow_call: + inputs: + repo: + required: true + type: string + description: "'erpnext' or 'frappe'" + frappe_version: + required: true + type: string + description: "Resolved frappe image tag" + erpnext_version: + required: false + type: string + description: "Resolved erpnext image tag" + python_version: + required: true + type: string + description: Python Version + node_version: + required: true + type: string + description: NodeJS Version + secrets: + DOCKERHUB_USERNAME: + required: true + DOCKERHUB_TOKEN: + required: true + +permissions: + contents: read + packages: write + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup QEMU + uses: docker/setup-qemu-action@v4 + with: + image: tonistiigi/binfmt:latest + platforms: all + + - name: Setup Buildx + uses: docker/setup-buildx-action@v4 + + - name: Set resolved versions + run: | + echo "FRAPPE_VERSION=${{ inputs.frappe_version }}" >> "$GITHUB_ENV" + if [ -n "${{ inputs.erpnext_version }}" ]; then + echo "ERPNEXT_VERSION=${{ inputs.erpnext_version }}" >> "$GITHUB_ENV" + fi + + - name: Set build args + run: | + echo "PYTHON_VERSION=${{ inputs.python_version }}" >> "$GITHUB_ENV" + echo "NODE_VERSION=${{ inputs.node_version }}" >> "$GITHUB_ENV" + + - name: Login to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Push Docker Hub images + uses: docker/bake-action@v7.1.0 + with: + push: true + set: "*.platform=linux/amd64,linux/arm64" + + - name: Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push GHCR base images + uses: docker/bake-action@v7.1.0 + with: + targets: base-images + push: true + set: "*.platform=linux/amd64,linux/arm64" + env: + REGISTRY_USER: ghcr.io/${{ github.repository_owner }} diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/docs-publish-site.yml similarity index 93% rename from .github/workflows/publish_docs.yml rename to .github/workflows/docs-publish-site.yml index ffecb82c..32a9fb48 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/docs-publish-site.yml @@ -1,11 +1,11 @@ -name: Deploy Frappe Docker Docs to GitHub Pages +name: Docs / Publish Site on: push: branches: [main] paths: - "docs/**" - - ".github/workflows/publish_docs.yml" + - ".github/workflows/docs-publish-site.yml" workflow_dispatch: permissions: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47be9ebb..1b1db5c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -143,4 +143,4 @@ In case of new release of Debian. e.g. bullseye to bookworm. Change following fi Change following files on release of ERPNext -- `.github/workflows/build_stable.yml`: Add the new release step under `jobs` and remove the unmaintained one. e.g. In case v12, v13 available, v14 will be added and v12 will be removed on release of v14. Also change the `needs:` for later steps to `v14` from `v13`. +- `.github/workflows/core-build-stable.yml`: Add the new release step under `jobs` and remove the unmaintained one. e.g. In case v12, v13 available, v14 will be added and v12 will be removed on release of v14. Also change the `needs:` for later steps to `v14` from `v13`. diff --git a/README.md b/README.md index 6619e4d2..ccf213ab 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Frappe Docker -[![Build Stable](https://github.com/frappe/frappe_docker/actions/workflows/build_stable.yml/badge.svg)](https://github.com/frappe/frappe_docker/actions/workflows/build_stable.yml) -[![Build Develop](https://github.com/frappe/frappe_docker/actions/workflows/build_develop.yml/badge.svg)](https://github.com/frappe/frappe_docker/actions/workflows/build_develop.yml) +[![Build Stable](https://github.com/frappe/frappe_docker/actions/workflows/core-build-stable.yml/badge.svg)](https://github.com/frappe/frappe_docker/actions/workflows/core-build-stable.yml) +[![Build Develop](https://github.com/frappe/frappe_docker/actions/workflows/core-build-develop.yml/badge.svg)](https://github.com/frappe/frappe_docker/actions/workflows/core-build-develop.yml) Docker images and orchestration for Frappe applications. diff --git a/docker-bake.hcl b/docker-bake.hcl index baf7f3b3..cb59f835 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -62,6 +62,10 @@ group "default" { targets = ["erpnext", "base", "build"] } +group "base-images" { + targets = ["base", "build"] +} + function "tag" { params = [repo, version] result = [ From 01af0df21dd2464c554204a3a052f659d68b74d3 Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:05:14 +0200 Subject: [PATCH 2/6] feat(ci): add reusable workflow for downstream app images --- .github/workflows/app-build-image.yml | 189 ++++++++++++++++++++++++++ images/layered/Containerfile | 5 +- 2 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/app-build-image.yml diff --git a/.github/workflows/app-build-image.yml b/.github/workflows/app-build-image.yml new file mode 100644 index 00000000..710d31c9 --- /dev/null +++ b/.github/workflows/app-build-image.yml @@ -0,0 +1,189 @@ +name: App / Build Image + +on: + workflow_call: + inputs: + app_name: + required: true + type: string + description: "App module and image name, for example 'crm'" + app_repo: + required: true + type: string + description: "Git URL or GitHub slug for the app repository" + app_ref: + required: true + type: string + description: "Git branch or tag to install for the app" + frappe_ref: + required: true + type: string + description: "Tag of the existing frappe/base and frappe/build images, for example version-16" + frappe_image_prefix: + required: false + type: string + default: frappe + description: "Image prefix for existing base and build images, for example 'frappe' or 'ghcr.io/frappe'" + image_name: + required: true + type: string + description: "Full image name, for example ghcr.io/frappe/crm" + image_tag: + required: true + type: string + description: "Image tag, for example develop or v16.0.0" + push: + required: true + type: boolean + registry: + required: false + type: string + default: docker.io + frappe_repo: + required: false + type: string + default: https://github.com/frappe/frappe + description: "Git URL for the Frappe framework repository" + builder_repository: + required: false + type: string + default: Rocket-Quack/frappe_docker + description: "Repository that contains the Containerfile and helper scripts" + builder_ref: + required: false + type: string + default: main + description: "Ref to checkout from the builder repository" + platforms: + required: false + type: string + default: linux/amd64 + description: "Docker platforms for the final build" + secrets: + REGISTRY_USERNAME: + required: false + REGISTRY_PASSWORD: + required: false + +permissions: + contents: read + packages: write + +concurrency: + group: app-image-${{ github.repository }}-${{ inputs.app_name }}-${{ inputs.app_ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + BUILDER_DIR: builder + APPS_JSON_PATH: builder/.github/tmp/apps.json + CACHE_SCOPE: app-image-${{ inputs.app_name }}-${{ inputs.frappe_ref }} + TEST_IMAGE: local/${{ inputs.app_name }}:${{ github.run_id }}-${{ github.run_attempt }} + FINAL_IMAGE: ${{ inputs.image_name }}:${{ inputs.image_tag }} + + steps: + - name: Checkout builder repository + uses: actions/checkout@v6 + with: + repository: ${{ inputs.builder_repository }} + ref: ${{ inputs.builder_ref }} + path: ${{ env.BUILDER_DIR }} + + - name: Setup QEMU + uses: docker/setup-qemu-action@v4 + with: + image: tonistiigi/binfmt:latest + platforms: all + + - name: Setup Buildx + uses: docker/setup-buildx-action@v4 + + - name: Create apps.json + env: + APP_REPO: ${{ inputs.app_repo }} + APP_REF: ${{ inputs.app_ref }} + APPS_JSON_PATH: ${{ env.APPS_JSON_PATH }} + run: | + mkdir -p "$(dirname "$APPS_JSON_PATH")" + python3 - <<'PY' + import json + import os + from pathlib import Path + + repo = os.environ["APP_REPO"].strip() + ref = os.environ["APP_REF"].strip() + + if repo.count("/") == 1 and not repo.startswith(("https://", "http://")): + repo = f"https://github.com/{repo}" + + for prefix in ("refs/heads/", "refs/tags/"): + if ref.startswith(prefix): + ref = ref.removeprefix(prefix) + + Path(os.environ["APPS_JSON_PATH"]).write_text( + json.dumps([{"url": repo, "branch": ref}], indent=2) + "\n", + encoding="utf-8", + ) + PY + + - name: Build smoke-test image + uses: docker/build-push-action@v6 + with: + context: ${{ env.BUILDER_DIR }} + file: ${{ env.BUILDER_DIR }}/images/layered/Containerfile + build-args: | + FRAPPE_IMAGE_PREFIX=${{ inputs.frappe_image_prefix }} + FRAPPE_PATH=${{ inputs.frappe_repo }} + FRAPPE_BRANCH=${{ inputs.frappe_ref }} + cache-from: type=gha,scope=${{ env.CACHE_SCOPE }} + cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }} + load: true + platforms: linux/amd64 + secrets: | + id=apps_json,src=${{ env.APPS_JSON_PATH }} + tags: ${{ env.TEST_IMAGE }} + + - name: Smoke test image contents + env: + APP_NAME: ${{ inputs.app_name }} + TEST_IMAGE: ${{ env.TEST_IMAGE }} + run: | + docker run --rm --entrypoint bash "$TEST_IMAGE" -lc \ + "test -d /home/frappe/frappe-bench/apps/frappe && test -d /home/frappe/frappe-bench/apps/${APP_NAME}" + + - name: Login to GHCR + if: ${{ inputs.push && inputs.registry == 'ghcr.io' }} + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Login to target registry + if: ${{ inputs.push && inputs.registry != 'ghcr.io' }} + uses: docker/login-action@v4 + with: + registry: ${{ inputs.registry }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push multi-arch image + if: ${{ inputs.push }} + uses: docker/build-push-action@v6 + with: + context: ${{ env.BUILDER_DIR }} + file: ${{ env.BUILDER_DIR }}/images/layered/Containerfile + build-args: | + FRAPPE_IMAGE_PREFIX=${{ inputs.frappe_image_prefix }} + FRAPPE_PATH=${{ inputs.frappe_repo }} + FRAPPE_BRANCH=${{ inputs.frappe_ref }} + cache-from: type=gha,scope=${{ env.CACHE_SCOPE }} + cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }} + platforms: ${{ inputs.platforms }} + push: true + secrets: | + id=apps_json,src=${{ env.APPS_JSON_PATH }} + tags: ${{ env.FINAL_IMAGE }} diff --git a/images/layered/Containerfile b/images/layered/Containerfile index 02bf9a20..808f898f 100644 --- a/images/layered/Containerfile +++ b/images/layered/Containerfile @@ -1,6 +1,7 @@ ARG FRAPPE_BRANCH=version-16 +ARG FRAPPE_IMAGE_PREFIX=frappe -FROM frappe/build:${FRAPPE_BRANCH} AS builder +FROM ${FRAPPE_IMAGE_PREFIX}/build:${FRAPPE_BRANCH} AS builder ARG FRAPPE_BRANCH=version-16 ARG FRAPPE_PATH=https://github.com/frappe/frappe @@ -24,7 +25,7 @@ RUN --mount=type=secret,id=apps_json,target=/opt/frappe/apps.json,uid=1000,gid=1 echo "{}" > sites/common_site_config.json && \ find apps -mindepth 1 -path "*/.git" | xargs rm -fr -FROM frappe/base:${FRAPPE_BRANCH} AS backend +FROM ${FRAPPE_IMAGE_PREFIX}/base:${FRAPPE_BRANCH} AS backend USER frappe From fec3af20cd75dce0da2d073e6eef5ce54ba9c532 Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:05:23 +0200 Subject: [PATCH 3/6] docs(ci): document current image workflow setup --- .../06-github-actions-image-workflows.md | 312 ++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 docs/08-reference/06-github-actions-image-workflows.md diff --git a/docs/08-reference/06-github-actions-image-workflows.md b/docs/08-reference/06-github-actions-image-workflows.md new file mode 100644 index 00000000..85805b64 --- /dev/null +++ b/docs/08-reference/06-github-actions-image-workflows.md @@ -0,0 +1,312 @@ +--- +title: GitHub Actions Image Workflows +--- + +This document describes the current workflow setup for shared core images and reusable downstream app images. + +# Workflow roles + +The current workflow layout is: + +- `.github/workflows/core-build-develop.yml` +- `.github/workflows/core-build-stable.yml` +- `.github/workflows/core-build-test-images.yml` +- `.github/workflows/core-publish-images.yml` +- `.github/workflows/app-build-image.yml` + +`core-build-develop.yml` and `core-build-stable.yml` are orchestration workflows. +They decide when the core image pipeline runs. + +`core-build-test-images.yml` is the reusable workflow that: + +- resolves the image versions for the requested release line +- builds the shared core images into a local registry +- runs the test suite against those images + +`core-publish-images.yml` is the reusable workflow that: + +- publishes the tested images to Docker Hub +- publishes `base` and `build` to GHCR + +`app-build-image.yml` is the reusable workflow that downstream repositories call to: + +- create an `apps.json` file from the caller's app repository and ref +- build `images/layered/Containerfile` +- consume existing `base` and `build` images +- install the requested app into the final image +- optionally push the final app image to the caller's registry + +# Current flow + +The current structure is: + +```text +core orchestration + -> core build and test + -> core publish + +downstream app workflow + -> consume published base and build + -> install app + -> publish final app image +``` + +Current Mermaid overview: + +```mermaid +flowchart TD + subgraph Core["Core image flow"] + A[core-build-develop.yml or core-build-stable.yml] + B[core-build-test-images.yml] + C[Resolve versions] + D[Build local test images] + E[Run pytest] + F[core-publish-images.yml] + G[Push Docker Hub: erpnext, base, build] + H[Push GHCR: base, build] + + A --> B + B --> C + C --> D + D --> E + E --> F + F --> G + F --> H + end + + subgraph App["Downstream app flow"] + I[Downstream repo workflow] + J[app-build-image.yml] + K[Create apps.json] + L[Build images/layered/Containerfile] + M[Install app] + N[Push final app image] + + I --> J + J --> K + K --> L + L --> M + M --> N + end + + G --> J + H --> J +``` + +More concretely: + +```text +core-build-test-images.yml + -> resolves frappe and erpnext tags + -> builds images into a local CI registry + -> runs tests + +core-publish-images.yml + -> pushes Docker Hub: erpnext, base, build + -> pushes GHCR: base, build + +app-build-image.yml + -> pulls: + - /base: + - /build: + -> installs app from app_repo + app_ref + -> pushes final image_name:image_tag +``` + +# Naming convention + +GitHub Actions requires workflow files to stay directly inside `.github/workflows`. +Subdirectories are not supported for workflow files, so structure should come from file names and `name:` values. + +Recommended file naming convention: + +```text +--.yml +``` + +Current examples: + +- `core-build-bench.yml` +- `core-build-develop.yml` +- `core-build-stable.yml` +- `core-build-test-images.yml` +- `core-publish-images.yml` +- `app-build-image.yml` +- `docs-publish-site.yml` + +Recommended visible workflow names: + +- `Core / Build Bench` +- `Core / Build Develop` +- `Core / Build Stable` +- `Core / Build and Test Images` +- `Core / Publish Images` +- `App / Build Image` +- `Docs / Publish Site` + +# Style rules + +To keep workflows predictable, use one convention per category instead of mixing styles. + +Workflow file names should use kebab-case: + +```text +core-build-test-images.yml +app-build-image.yml +``` + +Workflow display names should use short title-style groups: + +```text +Core / Build and Test Images +App / Build Image +``` + +Workflow inputs should use snake_case: + +```yaml +app_name: +frappe_ref: +image_name: +``` + +Environment variables should use upper snake case: + +```text +FRAPPE_IMAGE_PREFIX +PYTHON_VERSION +NODE_VERSION +``` + +The recommended rule set is: + +- workflow file names: kebab-case +- workflow `name:` values: grouped title case +- workflow inputs: snake_case +- job ids and step ids: snake_case where practical +- environment variables: UPPER_SNAKE_CASE + +This means `-` is preferred for file names, while `_` remains appropriate for YAML keys, inputs, and environment variables. + +# Important inputs in `app-build-image.yml` + +The reusable app workflow is controlled mainly by these inputs: + +- `app_name` + The application directory name, for example `crm` +- `app_repo` + The Git repository to install, for example `frappe/crm` +- `app_ref` + The branch or tag to install, for example `develop` +- `frappe_ref` + The tag of the existing `base` and `build` images, for example `version-16` +- `frappe_image_prefix` + Where the shared `base` and `build` images come from, for example `frappe` or `ghcr.io/frappe` +- `image_name` + The final target image name, for example `ghcr.io/acme/crm` +- `image_tag` + The final target image tag, for example `develop` +- `registry` + The registry for the final push, for example `ghcr.io` or `docker.io` + +The key distinction is: + +```text +frappe_image_prefix = source of shared base/build images +image_name = destination of the final app image +``` + +# Example: caller repository publishes to GHCR + +This example assumes: + +- shared base images exist in `ghcr.io/frappe/base` and `ghcr.io/frappe/build` +- the caller repository wants to publish its own app image to `ghcr.io/acme/crm` + +```yaml +name: App / Build CRM Image + +on: + workflow_dispatch: + push: + branches: + - develop + +permissions: + contents: read + packages: write + +jobs: + build-image: + uses: frappe/frappe_docker/.github/workflows/app-build-image.yml@main + with: + app_name: crm + app_repo: acme/crm + app_ref: develop + frappe_ref: version-16 + frappe_image_prefix: ghcr.io/frappe + image_name: ghcr.io/acme/crm + image_tag: develop + registry: ghcr.io + push: true + platforms: linux/amd64 +``` + +What happens: + +```text +1. app-build-image.yml is called +2. apps.json is generated from acme/crm + develop +3. the workflow builds images/layered/Containerfile +4. layered uses: + - ghcr.io/frappe/build:version-16 + - ghcr.io/frappe/base:version-16 +5. CRM is installed +6. the final image is pushed to ghcr.io/acme/crm:develop +``` + +For GHCR, the caller workflow should grant: + +- `permissions: packages: write` + +The reusable workflow then logs in with the workflow token. + +# Example: caller repository publishes to Docker Hub + +This example assumes: + +- shared base images come from Docker Hub under `frappe` +- the caller repository wants to publish its app image to Docker Hub as `acme/crm` + +```yaml +name: App / Build CRM Image + +on: + workflow_dispatch: + push: + branches: + - develop + +jobs: + build-image: + uses: frappe/frappe_docker/.github/workflows/app-build-image.yml@main + with: + app_name: crm + app_repo: acme/crm + app_ref: develop + frappe_ref: version-16 + frappe_image_prefix: frappe + image_name: acme/crm + image_tag: develop + registry: docker.io + push: true + platforms: linux/amd64 + secrets: + REGISTRY_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} +``` + +In this case: + +- shared images are pulled from `frappe/base:version-16` and `frappe/build:version-16` +- the final image is pushed to Docker Hub as `acme/crm:develop` From 3024cd132d27327b7becbb66d64510fd9a58d464 Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:27:39 +0200 Subject: [PATCH 4/6] chore(ci): allow image publishing on test fork --- .github/workflows/core-build-develop.yml | 2 +- .github/workflows/core-build-stable.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/core-build-develop.yml b/.github/workflows/core-build-develop.yml index 64800241..8085b553 100644 --- a/.github/workflows/core-build-develop.yml +++ b/.github/workflows/core-build-develop.yml @@ -35,7 +35,7 @@ jobs: node_version: 24.12.0 publish: - if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + if: ${{ contains(fromJSON('["frappe/frappe_docker","Rocket-Quack/frappe_docker"]'), github.repository) && github.event_name != 'pull_request' }} needs: test uses: ./.github/workflows/core-publish-images.yml with: diff --git a/.github/workflows/core-build-stable.yml b/.github/workflows/core-build-stable.yml index 6c453adb..5e11a015 100644 --- a/.github/workflows/core-build-stable.yml +++ b/.github/workflows/core-build-stable.yml @@ -48,7 +48,7 @@ jobs: node_version: 20.19.2 v15_publish: - if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + if: ${{ contains(fromJSON('["frappe/frappe_docker","Rocket-Quack/frappe_docker"]'), github.repository) && github.event_name != 'pull_request' }} needs: v15_test uses: ./.github/workflows/core-publish-images.yml with: @@ -69,7 +69,7 @@ jobs: python_version: 3.14.2 node_version: 24.12.0 v16_publish: - if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + if: ${{ contains(fromJSON('["frappe/frappe_docker","Rocket-Quack/frappe_docker"]'), github.repository) && github.event_name != 'pull_request' }} needs: v16_test uses: ./.github/workflows/core-publish-images.yml with: From 84a48c65eb2d7b109c7beb6c64b0f6e304a3d9ec Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:40:08 +0200 Subject: [PATCH 5/6] chore(ci): restore upstream defaults after fork validation --- .github/workflows/app-build-image.yml | 2 +- .github/workflows/core-build-develop.yml | 2 +- .github/workflows/core-build-stable.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/app-build-image.yml b/.github/workflows/app-build-image.yml index 710d31c9..74a01048 100644 --- a/.github/workflows/app-build-image.yml +++ b/.github/workflows/app-build-image.yml @@ -47,7 +47,7 @@ on: builder_repository: required: false type: string - default: Rocket-Quack/frappe_docker + default: frappe/frappe_docker description: "Repository that contains the Containerfile and helper scripts" builder_ref: required: false diff --git a/.github/workflows/core-build-develop.yml b/.github/workflows/core-build-develop.yml index 8085b553..64800241 100644 --- a/.github/workflows/core-build-develop.yml +++ b/.github/workflows/core-build-develop.yml @@ -35,7 +35,7 @@ jobs: node_version: 24.12.0 publish: - if: ${{ contains(fromJSON('["frappe/frappe_docker","Rocket-Quack/frappe_docker"]'), github.repository) && github.event_name != 'pull_request' }} + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} needs: test uses: ./.github/workflows/core-publish-images.yml with: diff --git a/.github/workflows/core-build-stable.yml b/.github/workflows/core-build-stable.yml index 5e11a015..6c453adb 100644 --- a/.github/workflows/core-build-stable.yml +++ b/.github/workflows/core-build-stable.yml @@ -48,7 +48,7 @@ jobs: node_version: 20.19.2 v15_publish: - if: ${{ contains(fromJSON('["frappe/frappe_docker","Rocket-Quack/frappe_docker"]'), github.repository) && github.event_name != 'pull_request' }} + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} needs: v15_test uses: ./.github/workflows/core-publish-images.yml with: @@ -69,7 +69,7 @@ jobs: python_version: 3.14.2 node_version: 24.12.0 v16_publish: - if: ${{ contains(fromJSON('["frappe/frappe_docker","Rocket-Quack/frappe_docker"]'), github.repository) && github.event_name != 'pull_request' }} + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} needs: v16_test uses: ./.github/workflows/core-publish-images.yml with: From de4c85f68f89600d4ecd9ab93c78a029a1abfe6f Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Wed, 22 Apr 2026 00:07:57 +0200 Subject: [PATCH 6/6] chore(ci): add compatibility wrappers for legacy workflow names --- .github/workflows/build_develop.yml | 12 ++++++++++++ .github/workflows/build_stable.yml | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 .github/workflows/build_develop.yml create mode 100644 .github/workflows/build_stable.yml diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml new file mode 100644 index 00000000..dfb0ee69 --- /dev/null +++ b/.github/workflows/build_develop.yml @@ -0,0 +1,12 @@ +name: Legacy / Build Develop + +on: + workflow_dispatch: + +jobs: + delegate: + uses: ./.github/workflows/core-build-develop.yml + permissions: + contents: read + packages: write + secrets: inherit diff --git a/.github/workflows/build_stable.yml b/.github/workflows/build_stable.yml new file mode 100644 index 00000000..15438f00 --- /dev/null +++ b/.github/workflows/build_stable.yml @@ -0,0 +1,12 @@ +name: Legacy / Build Stable + +on: + workflow_dispatch: + +jobs: + delegate: + uses: ./.github/workflows/core-build-stable.yml + permissions: + contents: read + packages: write + secrets: inherit