From ae275df16173dd2778146fc241baef7cb9ffabae Mon Sep 17 00:00:00 2001 From: OmarElaraby26 Date: Sun, 5 Apr 2026 22:24:53 +0200 Subject: [PATCH 01/21] fix(security): replace APPS_JSON_BASE64 build-arg with BuildKit secret mount APPS_JSON_BASE64 is stored in image layer metadata, permanently exposing private repo tokens (GitHub PATs) to anyone with image pull access. Replace --build-arg with --mount=type=secret so that apps.json is only available during the RUN step and never committed to any layer. Refs: https://docs.docker.com/reference/build-checks/secrets-used-in-arg-or-env/ --- docs/02-setup/02-build-setup.md | 16 ++++++---------- .../08-single-server-nginxproxy-example.md | 14 +++----------- images/custom/Containerfile | 13 ++++++------- images/layered/Containerfile | 14 ++++++-------- 4 files changed, 21 insertions(+), 36 deletions(-) diff --git a/docs/02-setup/02-build-setup.md b/docs/02-setup/02-build-setup.md index 93331280..1ffa35d9 100644 --- a/docs/02-setup/02-build-setup.md +++ b/docs/02-setup/02-build-setup.md @@ -42,23 +42,19 @@ To include custom apps in your image, create an `apps.json` file in the reposito ] ``` -Then generate a base64-encoded string from this file: - -```bash -export APPS_JSON_BASE64=$(base64 -w 0 apps.json) -``` - # Build the image Choose the appropriate build command based on your container runtime and desired image type. This example builds the `layered` image with the custom `apps.json` you created. +> **Security note:** The `apps.json` file is passed as a [BuildKit secret](https://docs.docker.com/build/building/secrets/) so that private repository tokens are **never** stored in image layer metadata. Do not use `--build-arg` for `apps.json` — build arguments are permanently visible via `docker image history`. + `Docker`: ```bash -docker build \ +DOCKER_BUILDKIT=1 docker build \ --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ --build-arg=FRAPPE_BRANCH=version-15 \ - --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \ + --secret=id=apps_json,src=apps.json \ --tag=custom:15 \ --file=images/layered/Containerfile . ``` @@ -69,7 +65,7 @@ docker build \ podman build \ --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ --build-arg=FRAPPE_BRANCH=version-15 \ - --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \ + --secret=id=apps_json,src=apps.json \ --tag=custom:15 \ --file=images/layered/Containerfile . ``` @@ -82,7 +78,7 @@ podman build \ | FRAPPE_PATH | Repository URL for Frappe framework source code. Defaults to https://github.com/frappe/frappe | | FRAPPE_BRANCH | Branch to use for Frappe framework. Defaults to version-15 | | **Custom Apps** | | -| APPS_JSON_BASE64 | Base64-encoded JSON string from apps.json defining apps to install | +| (secret) apps_json | Passed via `--secret=id=apps_json,src=apps.json`. Never use `--build-arg` for this file. | | **Dependencies** | | | PYTHON_VERSION | Python version for the base image | | NODE_VERSION | Node.js version | diff --git a/docs/02-setup/08-single-server-nginxproxy-example.md b/docs/02-setup/08-single-server-nginxproxy-example.md index 0049e39f..2cef110d 100644 --- a/docs/02-setup/08-single-server-nginxproxy-example.md +++ b/docs/02-setup/08-single-server-nginxproxy-example.md @@ -84,25 +84,17 @@ cat > ~/gitops/apps.json <<'EOF' EOF ``` -Generate the BASE64 value and build: +Build the image, passing `apps.json` as a [BuildKit secret](https://docs.docker.com/build/building/secrets/) so that private repo tokens are never stored in image layers: ```shell -export APPS_JSON_BASE64=$(base64 -w 0 ~/gitops/apps.json) - -docker build \ +DOCKER_BUILDKIT=1 docker build \ --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ --build-arg=FRAPPE_BRANCH=version-16 \ - --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \ + --secret=id=apps_json,src=$HOME/gitops/apps.json \ --tag=my-erpnext-prod-image:16.0.0 \ --file=images/layered/Containerfile . ``` -If `base64 -w 0` is not available on your system, use: - -```shell -export APPS_JSON_BASE64=$(base64 ~/gitops/apps.json | tr -d '\n') -``` - ### Configure environment Create an environment file for the bench: diff --git a/images/custom/Containerfile b/images/custom/Containerfile index a8298b52..8a4a58b2 100644 --- a/images/custom/Containerfile +++ b/images/custom/Containerfile @@ -113,18 +113,17 @@ RUN apt-get update \ libbz2-dev \ && rm -rf /var/lib/apt/lists/* -# apps.json includes -ARG APPS_JSON_BASE64 -RUN if [ -n "${APPS_JSON_BASE64}" ]; then \ - mkdir /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \ - fi +# apps.json is passed as a BuildKit secret so that private repo tokens +# are never baked into any image layer. The secret is mounted only for +# this RUN step and is not present in the final image. USER frappe ARG FRAPPE_BRANCH=version-16 ARG FRAPPE_PATH=https://github.com/frappe/frappe -RUN export APP_INSTALL_ARGS="" && \ - if [ -n "${APPS_JSON_BASE64}" ]; then \ +RUN --mount=type=secret,id=apps_json,target=/opt/frappe/apps.json,uid=1000,gid=1000 \ + 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}\ diff --git a/images/layered/Containerfile b/images/layered/Containerfile index 142c487a..117ab1ab 100644 --- a/images/layered/Containerfile +++ b/images/layered/Containerfile @@ -4,18 +4,16 @@ FROM frappe/build:${FRAPPE_BRANCH} AS builder ARG FRAPPE_BRANCH=version-16 ARG FRAPPE_PATH=https://github.com/frappe/frappe -ARG APPS_JSON_BASE64 -USER root - -RUN if [ -n "${APPS_JSON_BASE64}" ]; then \ - mkdir /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \ - fi +# apps.json is passed as a BuildKit secret so that private repo tokens +# are never baked into any image layer. The secret is mounted only for +# this RUN step and is not present in the final image. USER frappe -RUN export APP_INSTALL_ARGS="" && \ - if [ -n "${APPS_JSON_BASE64}" ]; then \ +RUN --mount=type=secret,id=apps_json,target=/opt/frappe/apps.json,uid=1000,gid=1000 \ + 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}\ From 8892908f5d3041d4c9383702265d88b4c8a08687 Mon Sep 17 00:00:00 2001 From: OmarElaraby26 Date: Tue, 7 Apr 2026 20:12:29 +0200 Subject: [PATCH 02/21] docs: require Docker Engine v23+ instead of setting DOCKER_BUILDKIT=1 BuildKit has been the default builder since Docker Engine 23.0 (Feb 2023), so prefixing the example build commands with DOCKER_BUILDKIT=1 is redundant on any supported install. Replace the prefix with an explicit prerequisite note so the requirement lives with the user's environment, not the example. The build relies on BuildKit secret mounts (--secret) to keep apps.json tokens out of image layers, which is why a real BuildKit-default engine is mandatory rather than merely recommended. Addresses review feedback on PR #1861. --- docs/02-setup/02-build-setup.md | 8 +++++--- docs/02-setup/08-single-server-nginxproxy-example.md | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/02-setup/02-build-setup.md b/docs/02-setup/02-build-setup.md index 1ffa35d9..d411cab7 100644 --- a/docs/02-setup/02-build-setup.md +++ b/docs/02-setup/02-build-setup.md @@ -7,11 +7,13 @@ This guide walks you through building Frappe images from the repository resource # Prerequisites - git -- docker or podman +- docker (Engine **v23.0+**) or podman - docker compose v2 or podman compose > Install containerization software according to the official maintainer documentation. Avoid package managers when not recommended, as they frequently cause compatibility issues. +> **Why Docker Engine v23+?** The build uses [BuildKit secrets](https://docs.docker.com/build/building/secrets/) (`--secret`) to keep `apps.json` tokens out of image layers. BuildKit is the default builder starting with Docker Engine 23.0 — older releases will fail or silently fall back to the legacy builder, which does not support secret mounts. + # Clone this repo ```bash @@ -46,12 +48,12 @@ To include custom apps in your image, create an `apps.json` file in the reposito Choose the appropriate build command based on your container runtime and desired image type. This example builds the `layered` image with the custom `apps.json` you created. -> **Security note:** The `apps.json` file is passed as a [BuildKit secret](https://docs.docker.com/build/building/secrets/) so that private repository tokens are **never** stored in image layer metadata. Do not use `--build-arg` for `apps.json` — build arguments are permanently visible via `docker image history`. +> **Security note:** The `apps.json` file is passed as a [BuildKit secret](https://docs.docker.com/build/building/secrets/) so that private repository tokens are **never** stored in image layer metadata. Do not use `--build-arg` for `apps.json` — build arguments are permanently visible via `docker image history`. This requires **Docker Engine v23.0+** (where BuildKit is the default builder). `Docker`: ```bash -DOCKER_BUILDKIT=1 docker build \ +docker build \ --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ --build-arg=FRAPPE_BRANCH=version-15 \ --secret=id=apps_json,src=apps.json \ diff --git a/docs/02-setup/08-single-server-nginxproxy-example.md b/docs/02-setup/08-single-server-nginxproxy-example.md index 2cef110d..4f1c212f 100644 --- a/docs/02-setup/08-single-server-nginxproxy-example.md +++ b/docs/02-setup/08-single-server-nginxproxy-example.md @@ -15,7 +15,7 @@ We will setup the following: ## Requirements -- A server that can run Docker (recommended: 2 vCPU, 4 GB RAM, 50 GB SSD). +- A server that can run Docker Engine **v23.0+** (recommended: 2 vCPU, 4 GB RAM, 50 GB SSD). The custom-image build below uses [BuildKit secrets](https://docs.docker.com/build/building/secrets/), which require BuildKit as the default builder (Docker Engine 23.0+). - A public domain with DNS control. - Two subdomains pointing to your server IP (A/AAAA records): - `erp.your-domain.com` @@ -84,10 +84,10 @@ cat > ~/gitops/apps.json <<'EOF' EOF ``` -Build the image, passing `apps.json` as a [BuildKit secret](https://docs.docker.com/build/building/secrets/) so that private repo tokens are never stored in image layers: +Build the image, passing `apps.json` as a [BuildKit secret](https://docs.docker.com/build/building/secrets/) so that private repo tokens are never stored in image layers. This requires **Docker Engine v23.0+**, where BuildKit is the default builder: ```shell -DOCKER_BUILDKIT=1 docker build \ +docker build \ --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ --build-arg=FRAPPE_BRANCH=version-16 \ --secret=id=apps_json,src=$HOME/gitops/apps.json \ From 90d9d25eb3a9ac41353c0d2547a04b3e32cfd3f7 Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:17:52 +0200 Subject: [PATCH 03/21] fix(docs): override vulnerable vite dependency --- docs/package.json | 1 + docs/pnpm-lock.yaml | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/package.json b/docs/package.json index 5a0481aa..153ee463 100644 --- a/docs/package.json +++ b/docs/package.json @@ -10,6 +10,7 @@ }, "pnpm": { "overrides": { + "vite": "7.3.2", "minimatch": "10.2.5", "picomatch": "4.0.4" } diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 500375b9..2e0b4ee7 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -5,6 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: + vite: 7.3.2 minimatch: 10.2.5 picomatch: 4.0.4 @@ -412,7 +413,7 @@ packages: resolution: {integrity: sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: 7.3.2 vue: ^3.2.25 '@vue/compiler-core@3.5.30': @@ -846,8 +847,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + vite@7.3.2: + resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1185,10 +1186,10 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-vue@6.0.5(vite@7.3.1)(vue@3.5.30)': + '@vitejs/plugin-vue@6.0.5(vite@7.3.2)(vue@3.5.30)': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 - vite: 7.3.1 + vite: 7.3.2 vue: 3.5.30 '@vue/compiler-core@3.5.30': @@ -1665,7 +1666,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@7.3.1: + vite@7.3.2: dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) @@ -1692,7 +1693,7 @@ snapshots: '@shikijs/transformers': 3.23.0 '@shikijs/types': 3.23.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 6.0.5(vite@7.3.1)(vue@3.5.30) + '@vitejs/plugin-vue': 6.0.5(vite@7.3.2)(vue@3.5.30) '@vue/devtools-api': 8.1.0 '@vue/shared': 3.5.30 '@vueuse/core': 14.2.1(vue@3.5.30) @@ -1701,7 +1702,7 @@ snapshots: mark.js: 8.11.1 minisearch: 7.2.0 shiki: 3.23.0 - vite: 7.3.1 + vite: 7.3.2 vue: 3.5.30 optionalDependencies: postcss: 8.5.8 From 91308ce43d2d469362b47b2158b0faac8f0176b4 Mon Sep 17 00:00:00 2001 From: Daniel Radl Date: Fri, 10 Apr 2026 16:51:19 +0200 Subject: [PATCH 04/21] chore(vscode): exclude build artifacts and deps from file watcher --- development/vscode-example/settings.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/development/vscode-example/settings.json b/development/vscode-example/settings.json index 1490b727..e89d2005 100644 --- a/development/vscode-example/settings.json +++ b/development/vscode-example/settings.json @@ -1,3 +1,19 @@ { - "python.defaultInterpreterPath": "${workspaceFolder}/frappe-bench/env/bin/python" + "python.defaultInterpreterPath": "${workspaceFolder}/frappe-bench/env/bin/python", + "files.watcherExclude": { + // --- Node modules --- + "**/node_modules/**": true, + + // --- Frappe bench core dirs --- + "**/env/**": true, + "**/config/**": true, + + // --- Build artifacts --- + "**/__pycache__/**": true, + "**/*.pyc": true + }, + "files.exclude": { + "**/__pycache__": true, + "**/*.pyc": true + } } From f36bde7acab2879110436985f311f9be7907e6bb Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 11 Apr 2026 05:32:15 +0000 Subject: [PATCH 05/21] chore: Update example.env --- example.env | 2 +- pwd.yml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/example.env b/example.env index 2e6864aa..5eaeaa7d 100644 --- a/example.env +++ b/example.env @@ -1,6 +1,6 @@ # Reference: https://github.com/frappe/frappe_docker/blob/main/docs/02-setup/04-env-variables.md -ERPNEXT_VERSION=v16.13.2 +ERPNEXT_VERSION=v16.13.3 DB_PASSWORD=123 diff --git a/pwd.yml b/pwd.yml index a0259f04..0564d09b 100644 --- a/pwd.yml +++ b/pwd.yml @@ -1,6 +1,6 @@ services: backend: - image: frappe/erpnext:v16.13.2 + image: frappe/erpnext:v16.13.3 networks: - frappe_network deploy: @@ -16,7 +16,7 @@ services: MARIADB_ROOT_PASSWORD: admin configurator: - image: frappe/erpnext:v16.13.2 + image: frappe/erpnext:v16.13.3 networks: - frappe_network deploy: @@ -45,7 +45,7 @@ services: - logs:/home/frappe/frappe-bench/logs create-site: - image: frappe/erpnext:v16.13.2 + image: frappe/erpnext:v16.13.3 networks: - frappe_network deploy: @@ -100,7 +100,7 @@ services: - db-data:/var/lib/mysql frontend: - image: frappe/erpnext:v16.13.2 + image: frappe/erpnext:v16.13.3 networks: - frappe_network depends_on: @@ -126,7 +126,7 @@ services: - "8080:8080" queue-long: - image: frappe/erpnext:v16.13.2 + image: frappe/erpnext:v16.13.3 networks: - frappe_network deploy: @@ -145,7 +145,7 @@ services: FRAPPE_REDIS_QUEUE: redis://redis-queue:6379 queue-short: - image: frappe/erpnext:v16.13.2 + image: frappe/erpnext:v16.13.3 networks: - frappe_network deploy: @@ -182,7 +182,7 @@ services: condition: on-failure scheduler: - image: frappe/erpnext:v16.13.2 + image: frappe/erpnext:v16.13.3 networks: - frappe_network deploy: @@ -196,7 +196,7 @@ services: - logs:/home/frappe/frappe-bench/logs websocket: - image: frappe/erpnext:v16.13.2 + image: frappe/erpnext:v16.13.3 networks: - frappe_network deploy: From adaf37dfa58f7cc4a100fa5643df431531551647 Mon Sep 17 00:00:00 2001 From: Daniel Radl Date: Sun, 12 Apr 2026 15:06:17 +0200 Subject: [PATCH 06/21] docs: add MAINTAINERS.md to document project maintainers --- MAINTAINERS.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 MAINTAINERS.md diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 00000000..b4afca59 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,25 @@ +# Maintainers + +This project is actively maintained by the following people. + +Maintainers are responsible for: + +- Reviewing and merging pull requests +- Managing releases +- Triaging and responding to issues +- Ensuring the overall health and direction of the project + +## Current Maintainers + +- [@revant](https://github.com/revant) +- [@DanielRadlAMR](https://github.com/DanielRadlAMR) +- [@Rocket-Quack](https://github.com/Rocket-Quack) + +## Emeritus Maintainers + +_(none)_ + +## Becoming a Maintainer + +Contributors who consistently help review pull requests, participate in issue triage, +and contribute to releases may be invited to become maintainers. From 526119247fc7940d0bbecb74de751854906a9a4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 08:39:30 +0000 Subject: [PATCH 07/21] chore(deps): bump docker/bake-action from 7.0.0 to 7.1.0 Bumps [docker/bake-action](https://github.com/docker/bake-action) from 7.0.0 to 7.1.0. - [Release notes](https://github.com/docker/bake-action/releases) - [Commits](https://github.com/docker/bake-action/compare/v7.0.0...v7.1.0) --- updated-dependencies: - dependency-name: docker/bake-action dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build_bench.yml | 4 ++-- .github/workflows/docker-build-push.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_bench.yml b/.github/workflows/build_bench.yml index 257a31c1..1bc47018 100644 --- a/.github/workflows/build_bench.yml +++ b/.github/workflows/build_bench.yml @@ -38,7 +38,7 @@ jobs: run: echo "LATEST_BENCH_RELEASE=$(curl -s 'https://api.github.com/repos/frappe/bench/releases/latest' | jq -r '.tag_name')" >> "$GITHUB_ENV" - name: Build and test - uses: docker/bake-action@v7.0.0 + uses: docker/bake-action@v7.1.0 with: source: . targets: bench-test @@ -52,7 +52,7 @@ jobs: - name: Push if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - uses: docker/bake-action@v7.0.0 + uses: docker/bake-action@v7.1.0 with: targets: bench push: true diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml index 376118cd..9fee1782 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/docker-build-push.yml @@ -66,7 +66,7 @@ jobs: echo "NODE_VERSION=${{ inputs.node_version }}" >> "$GITHUB_ENV" - name: Build - uses: docker/bake-action@v7.0.0 + uses: docker/bake-action@v7.1.0 with: source: . push: true @@ -95,7 +95,7 @@ jobs: - name: Push if: ${{ inputs.push }} - uses: docker/bake-action@v7.0.0 + uses: docker/bake-action@v7.1.0 with: push: true set: "*.platform=linux/amd64,linux/arm64" From 3e1e045f7a0ad28c1719770ebb27f1b0337613b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 08:39:38 +0000 Subject: [PATCH 08/21] chore(deps): bump pnpm/action-setup from 5 to 6 Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 5 to 6. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v5...v6) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/publish_docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index ab9de0ef..8aa04ea7 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -33,7 +33,7 @@ jobs: uses: actions/checkout@v6 - name: Install pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@v6 with: version: 10 From db8868b25b899ddcf3037ab34a4ec978bf61bd93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 08:23:11 +0000 Subject: [PATCH 09/21] chore(deps): bump actions/upload-pages-artifact from 4 to 5 Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/publish_docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index ab9de0ef..94812ac0 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -51,7 +51,7 @@ jobs: run: pnpm docs:build - name: Upload artifact - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: docs/.vitepress/dist From 169d5be00ce15529b5f2833cdb8023b5a56964b9 Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:49:21 +0200 Subject: [PATCH 10/21] ci(docs): pin pnpm version for pages build --- .github/workflows/publish_docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index 31a72e4a..130c3402 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -35,14 +35,14 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v6 with: - version: 10 + version: 10.28.2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: "pnpm" - cache-dependency-path: ./docs + cache-dependency-path: ./docs/pnpm-lock.yaml - name: Install dependencies run: pnpm i --frozen-lockfile From 0feb49d00a2d2534efcc1895e99a99e8c502cd1a Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:55:29 +0200 Subject: [PATCH 11/21] ci(docs): add lockfile debug output for pages workflow --- .github/workflows/publish_docs.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index 130c3402..4b52f9e8 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -44,6 +44,23 @@ jobs: cache: "pnpm" cache-dependency-path: ./docs/pnpm-lock.yaml + - name: Debug lockfile on runner + run: | + pwd + ls -la + echo "sha256 (workspace file):" + sha256sum pnpm-lock.yaml + echo "sha256 (git blob at HEAD):" + git show HEAD:docs/pnpm-lock.yaml | sha256sum + echo "lockfileVersion count:" + grep -c '^lockfileVersion:' pnpm-lock.yaml || true + echo "yaml document markers:" + grep -nE '^---$|^\\.\\.\\.$|^<<<<<<<|^=======|^>>>>>>>$' pnpm-lock.yaml || true + echo "first 20 lines:" + sed -n '1,20p' pnpm-lock.yaml + echo "last 20 lines:" + tail -n 20 pnpm-lock.yaml + - name: Install dependencies run: pnpm i --frozen-lockfile From 1d957628157f66cba6daa1dd54b7b2c955390feb Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:02:04 +0200 Subject: [PATCH 12/21] ci(docs): switch pages workflow to corepack-managed pnpm --- .github/workflows/publish_docs.yml | 31 +++++++++++------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index 4b52f9e8..adbac6ea 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -32,11 +32,6 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Install pnpm - uses: pnpm/action-setup@v6 - with: - version: 10.28.2 - - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v6 with: @@ -44,22 +39,18 @@ jobs: cache: "pnpm" cache-dependency-path: ./docs/pnpm-lock.yaml - - name: Debug lockfile on runner + - name: Enable Corepack + run: corepack enable + + - name: Activate pnpm + run: corepack prepare pnpm@10.28.2 --activate + + - name: Show tool versions run: | - pwd - ls -la - echo "sha256 (workspace file):" - sha256sum pnpm-lock.yaml - echo "sha256 (git blob at HEAD):" - git show HEAD:docs/pnpm-lock.yaml | sha256sum - echo "lockfileVersion count:" - grep -c '^lockfileVersion:' pnpm-lock.yaml || true - echo "yaml document markers:" - grep -nE '^---$|^\\.\\.\\.$|^<<<<<<<|^=======|^>>>>>>>$' pnpm-lock.yaml || true - echo "first 20 lines:" - sed -n '1,20p' pnpm-lock.yaml - echo "last 20 lines:" - tail -n 20 pnpm-lock.yaml + node --version + corepack --version + pnpm --version + which pnpm - name: Install dependencies run: pnpm i --frozen-lockfile From adc72561a148b5e315bb48a9cafda43160765b65 Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:04:05 +0200 Subject: [PATCH 13/21] ci(docs): remove setup-node pnpm cache for corepack flow --- .github/workflows/publish_docs.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index adbac6ea..ffecb82c 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -36,8 +36,6 @@ jobs: uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} - cache: "pnpm" - cache-dependency-path: ./docs/pnpm-lock.yaml - name: Enable Corepack run: corepack enable From 616ffd417797031f760e7a6c9669923a5febed66 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 14 Apr 2026 18:46:14 +0000 Subject: [PATCH 14/21] chore: Update example.env --- example.env | 2 +- pwd.yml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/example.env b/example.env index 5eaeaa7d..5471b7dc 100644 --- a/example.env +++ b/example.env @@ -1,6 +1,6 @@ # Reference: https://github.com/frappe/frappe_docker/blob/main/docs/02-setup/04-env-variables.md -ERPNEXT_VERSION=v16.13.3 +ERPNEXT_VERSION=v16.14.0 DB_PASSWORD=123 diff --git a/pwd.yml b/pwd.yml index 0564d09b..612344db 100644 --- a/pwd.yml +++ b/pwd.yml @@ -1,6 +1,6 @@ services: backend: - image: frappe/erpnext:v16.13.3 + image: frappe/erpnext:v16.14.0 networks: - frappe_network deploy: @@ -16,7 +16,7 @@ services: MARIADB_ROOT_PASSWORD: admin configurator: - image: frappe/erpnext:v16.13.3 + image: frappe/erpnext:v16.14.0 networks: - frappe_network deploy: @@ -45,7 +45,7 @@ services: - logs:/home/frappe/frappe-bench/logs create-site: - image: frappe/erpnext:v16.13.3 + image: frappe/erpnext:v16.14.0 networks: - frappe_network deploy: @@ -100,7 +100,7 @@ services: - db-data:/var/lib/mysql frontend: - image: frappe/erpnext:v16.13.3 + image: frappe/erpnext:v16.14.0 networks: - frappe_network depends_on: @@ -126,7 +126,7 @@ services: - "8080:8080" queue-long: - image: frappe/erpnext:v16.13.3 + image: frappe/erpnext:v16.14.0 networks: - frappe_network deploy: @@ -145,7 +145,7 @@ services: FRAPPE_REDIS_QUEUE: redis://redis-queue:6379 queue-short: - image: frappe/erpnext:v16.13.3 + image: frappe/erpnext:v16.14.0 networks: - frappe_network deploy: @@ -182,7 +182,7 @@ services: condition: on-failure scheduler: - image: frappe/erpnext:v16.13.3 + image: frappe/erpnext:v16.14.0 networks: - frappe_network deploy: @@ -196,7 +196,7 @@ services: - logs:/home/frappe/frappe-bench/logs websocket: - image: frappe/erpnext:v16.13.3 + image: frappe/erpnext:v16.14.0 networks: - frappe_network deploy: From 1fe7523bfbf43202911f531c86d772833cb3bf95 Mon Sep 17 00:00:00 2001 From: Daniel Radl Date: Wed, 15 Apr 2026 12:44:03 +0200 Subject: [PATCH 15/21] chore(vscode): show git folder --- development/vscode-example/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/development/vscode-example/settings.json b/development/vscode-example/settings.json index e89d2005..cfc5b0f7 100644 --- a/development/vscode-example/settings.json +++ b/development/vscode-example/settings.json @@ -14,6 +14,7 @@ }, "files.exclude": { "**/__pycache__": true, - "**/*.pyc": true + "**/*.pyc": true, + "**/.git": false } } From 4e5f84fa29a1e0bd1cbed3a57e36f5766421260c Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:52:31 +0200 Subject: [PATCH 16/21] chore: remove comments about why BuildKit is being used to parse apps.json --- images/custom/Containerfile | 4 ---- images/layered/Containerfile | 4 ---- 2 files changed, 8 deletions(-) diff --git a/images/custom/Containerfile b/images/custom/Containerfile index 8a4a58b2..c7519e84 100644 --- a/images/custom/Containerfile +++ b/images/custom/Containerfile @@ -113,10 +113,6 @@ RUN apt-get update \ libbz2-dev \ && rm -rf /var/lib/apt/lists/* -# apps.json is passed as a BuildKit secret so that private repo tokens -# are never baked into any image layer. The secret is mounted only for -# this RUN step and is not present in the final image. - USER frappe ARG FRAPPE_BRANCH=version-16 diff --git a/images/layered/Containerfile b/images/layered/Containerfile index 117ab1ab..de5e835d 100644 --- a/images/layered/Containerfile +++ b/images/layered/Containerfile @@ -5,10 +5,6 @@ FROM frappe/build:${FRAPPE_BRANCH} AS builder ARG FRAPPE_BRANCH=version-16 ARG FRAPPE_PATH=https://github.com/frappe/frappe -# apps.json is passed as a BuildKit secret so that private repo tokens -# are never baked into any image layer. The secret is mounted only for -# this RUN step and is not present in the final image. - USER frappe RUN --mount=type=secret,id=apps_json,target=/opt/frappe/apps.json,uid=1000,gid=1000 \ From 9cecbc7b2de8dc9d410ea30f7c34576a42f8b366 Mon Sep 17 00:00:00 2001 From: jslocomotor <210083531+jslocomotor@users.noreply.github.com> Date: Sat, 18 Apr 2026 21:18:22 +0200 Subject: [PATCH 17/21] chore(compose): use MariaDB 11.8 and remove obsolete MariaDB 10.6 workaround --- devcontainer-example/docker-compose.yml | 1 - docs/01-getting-started/03-arm64.md | 3 +-- overrides/compose.mariadb-shared.yaml | 1 - overrides/compose.mariadb.yaml | 1 - pwd.yml | 3 +-- 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/devcontainer-example/docker-compose.yml b/devcontainer-example/docker-compose.yml index 3c23b4f3..bf3bbf72 100644 --- a/devcontainer-example/docker-compose.yml +++ b/devcontainer-example/docker-compose.yml @@ -5,7 +5,6 @@ services: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 environment: MYSQL_ROOT_PASSWORD: 123 MARIADB_AUTO_UPGRADE: 1 diff --git a/docs/01-getting-started/03-arm64.md b/docs/01-getting-started/03-arm64.md index c76beb06..b9defd89 100644 --- a/docs/01-getting-started/03-arm64.md +++ b/docs/01-getting-started/03-arm64.md @@ -100,7 +100,7 @@ services: bench new-site --mariadb-user-host-login-scope=% --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend; db: - image: mariadb:10.6 + image: mariadb:11.8 platform: linux/amd64 healthcheck: test: mysqladmin ping -h localhost --password=admin @@ -113,7 +113,6 @@ services: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 environment: MYSQL_ROOT_PASSWORD: admin volumes: diff --git a/overrides/compose.mariadb-shared.yaml b/overrides/compose.mariadb-shared.yaml index f28031c0..0c815007 100644 --- a/overrides/compose.mariadb-shared.yaml +++ b/overrides/compose.mariadb-shared.yaml @@ -13,7 +13,6 @@ services: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-changeit} MARIADB_AUTO_UPGRADE: 1 diff --git a/overrides/compose.mariadb.yaml b/overrides/compose.mariadb.yaml index e79288c0..94e177c3 100644 --- a/overrides/compose.mariadb.yaml +++ b/overrides/compose.mariadb.yaml @@ -20,7 +20,6 @@ services: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-123} MARIADB_AUTO_UPGRADE: 1 diff --git a/pwd.yml b/pwd.yml index 612344db..4cb9b474 100644 --- a/pwd.yml +++ b/pwd.yml @@ -78,7 +78,7 @@ services: bench new-site --mariadb-user-host-login-scope='%' --admin-password=admin --db-root-username=root --db-root-password=admin --install-app erpnext --set-default frontend; db: - image: mariadb:10.6 + image: mariadb:11.8 networks: - frappe_network healthcheck: @@ -92,7 +92,6 @@ services: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 environment: MYSQL_ROOT_PASSWORD: admin MARIADB_ROOT_PASSWORD: admin From 9ae698926929476213bbb3b9b4cb836b68856a73 Mon Sep 17 00:00:00 2001 From: ews-pgasser Date: Mon, 20 Apr 2026 15:34:55 +0200 Subject: [PATCH 18/21] fix: remove nested sites assets volume --- images/layered/Containerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/images/layered/Containerfile b/images/layered/Containerfile index de5e835d..02bf9a20 100644 --- a/images/layered/Containerfile +++ b/images/layered/Containerfile @@ -34,7 +34,6 @@ WORKDIR /home/frappe/frappe-bench VOLUME [ \ "/home/frappe/frappe-bench/sites", \ - "/home/frappe/frappe-bench/sites/assets", \ "/home/frappe/frappe-bench/logs" \ ] From 0cddb6f35becbf9027f67058ef8ffcc74c505321 Mon Sep 17 00:00:00 2001 From: ews-pgasser Date: Mon, 20 Apr 2026 15:35:36 +0200 Subject: [PATCH 19/21] docs: document volume migration notes for sites/assets change --- docs/02-setup/02-build-setup.md | 39 +++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/docs/02-setup/02-build-setup.md b/docs/02-setup/02-build-setup.md index d411cab7..c4404d93 100644 --- a/docs/02-setup/02-build-setup.md +++ b/docs/02-setup/02-build-setup.md @@ -74,20 +74,20 @@ podman build \ ## Build args -| Arg | Purpose | -| -------------------- | --------------------------------------------------------------------------------------------- | -| **Frappe Framework** | | -| FRAPPE_PATH | Repository URL for Frappe framework source code. Defaults to https://github.com/frappe/frappe | -| FRAPPE_BRANCH | Branch to use for Frappe framework. Defaults to version-15 | -| **Custom Apps** | | -| (secret) apps_json | Passed via `--secret=id=apps_json,src=apps.json`. Never use `--build-arg` for this file. | -| **Dependencies** | | -| PYTHON_VERSION | Python version for the base image | -| NODE_VERSION | Node.js version | -| WKHTMLTOPDF_VERSION | wkhtmltopdf version | -| **bench only** | | -| DEBIAN_BASE | Debian base version for the bench image, defaults to `bookworm` | -| WKHTMLTOPDF_DISTRO | use the specified distro for debian package. Default is `bookworm` | +| Arg | Purpose | +| -------------------- | ----------------------------------------------------------------------------------------------- | +| **Frappe Framework** | | +| FRAPPE_PATH | Repository URL for Frappe framework source code. Defaults to | +| FRAPPE_BRANCH | Branch to use for Frappe framework. Defaults to version-15 | +| **Custom Apps** | | +| (secret) apps_json | Passed via `--secret=id=apps_json,src=apps.json`. Never use `--build-arg` for this file. | +| **Dependencies** | | +| PYTHON_VERSION | Python version for the base image | +| NODE_VERSION | Node.js version | +| WKHTMLTOPDF_VERSION | wkhtmltopdf version | +| **bench only** | | +| DEBIAN_BASE | Debian base version for the bench image, defaults to `bookworm` | +| WKHTMLTOPDF_DISTRO | use the specified distro for debian package. Default is `bookworm` | # env file @@ -130,6 +130,17 @@ This generates `compose.custom.yaml`, which you'll use to start all containers. > **NOTE**: podman compose is just a wrapper, it uses docker-compose if it is available or podman-compose if not. podman-compose have an issue reading .env files ([Issue](https://github.com/containers/podman-compose/issues/475)) and might create an issue when running the containers. +# Upgrading from images with a nested sites/assets volume + +Previous images declared `VOLUME /home/frappe/frappe-bench/sites/assets` separately. This created an implicit nested mountpoint inside the `sites` volume, which could cause Docker to attach different anonymous volumes per container in multi-container setups. +That declaration has been removed. `sites` is now the single shared mount, consistent with the compose setup and docs. + +**After pulling the updated image:** + +- Recreate all containers (`docker compose up --force-recreate`). Without this, Docker may keep the old anonymous `sites/assets` volume + attached from before the change. +- No `bench build` is needed — this only fixes mount consistency, not the asset workflow. + --- **Next:** [Start Setup →](03-start-setup.md) From 63f5169610e483770069061aa99040aeaaa13bce Mon Sep 17 00:00:00 2001 From: ews-pgasser Date: Mon, 20 Apr 2026 16:30:58 +0200 Subject: [PATCH 20/21] fix: removed sites/assets volume from custom & production Containerfile too --- images/custom/Containerfile | 1 - images/production/Containerfile | 1 - 2 files changed, 2 deletions(-) diff --git a/images/custom/Containerfile b/images/custom/Containerfile index c7519e84..e0c5b335 100644 --- a/images/custom/Containerfile +++ b/images/custom/Containerfile @@ -144,7 +144,6 @@ WORKDIR /home/frappe/frappe-bench VOLUME [ \ "/home/frappe/frappe-bench/sites", \ - "/home/frappe/frappe-bench/sites/assets", \ "/home/frappe/frappe-bench/logs" \ ] diff --git a/images/production/Containerfile b/images/production/Containerfile index 17f1573d..4dfb8685 100644 --- a/images/production/Containerfile +++ b/images/production/Containerfile @@ -137,7 +137,6 @@ WORKDIR /home/frappe/frappe-bench VOLUME [ \ "/home/frappe/frappe-bench/sites", \ - "/home/frappe/frappe-bench/sites/assets", \ "/home/frappe/frappe-bench/logs" \ ] From 17670ec04c34d2666bd82b819a43d8c01c1277f2 Mon Sep 17 00:00:00 2001 From: ews-pgasser Date: Mon, 20 Apr 2026 17:23:46 +0200 Subject: [PATCH 21/21] docs: move sites/assets volume upgrade note to migration docs --- docs/02-setup/02-build-setup.md | 11 ----------- .../06-migration/01-migrate-from-multi-image-setup.md | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/02-setup/02-build-setup.md b/docs/02-setup/02-build-setup.md index c4404d93..d0e6907b 100644 --- a/docs/02-setup/02-build-setup.md +++ b/docs/02-setup/02-build-setup.md @@ -130,17 +130,6 @@ This generates `compose.custom.yaml`, which you'll use to start all containers. > **NOTE**: podman compose is just a wrapper, it uses docker-compose if it is available or podman-compose if not. podman-compose have an issue reading .env files ([Issue](https://github.com/containers/podman-compose/issues/475)) and might create an issue when running the containers. -# Upgrading from images with a nested sites/assets volume - -Previous images declared `VOLUME /home/frappe/frappe-bench/sites/assets` separately. This created an implicit nested mountpoint inside the `sites` volume, which could cause Docker to attach different anonymous volumes per container in multi-container setups. -That declaration has been removed. `sites` is now the single shared mount, consistent with the compose setup and docs. - -**After pulling the updated image:** - -- Recreate all containers (`docker compose up --force-recreate`). Without this, Docker may keep the old anonymous `sites/assets` volume - attached from before the change. -- No `bench build` is needed — this only fixes mount consistency, not the asset workflow. - --- **Next:** [Start Setup →](03-start-setup.md) diff --git a/docs/06-migration/01-migrate-from-multi-image-setup.md b/docs/06-migration/01-migrate-from-multi-image-setup.md index 0fd2e344..04d8a2c1 100644 --- a/docs/06-migration/01-migrate-from-multi-image-setup.md +++ b/docs/06-migration/01-migrate-from-multi-image-setup.md @@ -114,3 +114,14 @@ create-site: # ... removed for brevity ``` + +## Upgrading from images with a nested sites/assets volume + +Previous images declared `VOLUME /home/frappe/frappe-bench/sites/assets` separately. This created an implicit nested mountpoint inside the `sites` volume, which could cause Docker to attach different anonymous volumes per container in multi-container setups. +That declaration has been removed. `sites` is now the single shared mount, consistent with the compose setup and docs. + +**After pulling the updated image:** + +- Recreate all containers (`docker compose up --force-recreate`). Without this, Docker may keep the old anonymous `sites/assets` volume + attached from before the change. +- No `bench build` is needed — this only fixes mount consistency, not the asset workflow.