Introduction
This is a brief guide to automated builds and deployment for custom Frappe images. Depending on your specific setup, environment and security rules, the information below may need to be adapted to your needs.
Requirements
Knowledge
Basic knowledge of Docker and build pipelines is expected.
Please refer to the Setup chapter first, especially Build Setup, for basic understanding.
Additional Files
Apps
At build time an apps.json file can be provided. This specifies additional Frappe framework compatible apps to include in custom images.
Build
A workflow file for your CI platform and environment is required.
Build Cache
Unlike manual builds, automated build commands should generally not use --no-cache.
Reusing cached layers can greatly reduce build times, disk usage, and bandwidth usage when pushing to image registries.
Instead, CACHE_BUST can be used to control cache invalidation of the Frappe layer when rebuilding is desired.
This is especially relevant because apps.json is provided as a secret. Secret contents are not part of Docker layer cache keys and therefore cannot trigger cache invalidation automatically.
As a result, Docker may reuse an older cached layer even when the custom app definition has changed.
Exception: Newer releases of the Frappe framework may still trigger rebuilding the layer.
Possible techniques for cache invalidation using CACHE_BUST:
- No override: normal Docker layer caching is used - not recommended in this use case
- Timestamp: force a rebuild on every pipeline run - since the value will change every run
- Pipeline run ID: rebuild once per CI run
- Commit SHA: rebuild once per commit
- apps.json hash: rebuild only when the custom app definition changes - additional requirements, see below example
Examples:
1. No override - not recommended
This will reuse a previously build layer and won't check for app updates except Frappe framework
- name: Build Docker image
shell: sh
run: |
docker build \
--build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
--build-arg=FRAPPE_BRANCH=version-16 \
--secret=id=apps_json,src=apps.json \
--tag=custom:16 \
--file=images/layered/Containerfile .2. Timestamp
- name: Build Docker image
shell: sh
run: |
docker build \
--build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
--build-arg=FRAPPE_BRANCH=version-16 \
--build-arg=CACHE_BUST="$(date +%s)" \
--secret=id=apps_json,src=apps.json \
--tag=custom:16 \
--file=images/layered/Containerfile .3. Pipeline run ID from GitHub
- name: Build Docker image
shell: sh
run: |
docker build \
--build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
--build-arg=FRAPPE_BRANCH=version-16 \
--build-arg=CACHE_BUST="$GITHUB_RUN_ID" \
--secret=id=apps_json,src=apps.json \
--tag=custom:16 \
--file=images/layered/Containerfile .4. Commit SHA from GitHub
- name: Build Docker image
shell: sh
run: |
docker build \
--build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
--build-arg=FRAPPE_BRANCH=version-16 \
--build-arg=CACHE_BUST="$GITHUB_SHA" \
--secret=id=apps_json,src=apps.json \
--tag=custom:16 \
--file=images/layered/Containerfile .5. apps.json hash
Note: When using branch references in apps.json, the hash only changes when the file content changes, not when an upstream app branch receives updates. This method works best when pinning specific commits or releases.
- name: Build Docker image
shell: sh
run: |
docker build \
--build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
--build-arg=FRAPPE_BRANCH=version-16 \
--build-arg=CACHE_BUST="$(sha256sum apps.json | awk '{print $1}')" \
--secret=id=apps_json,src=apps.json \
--tag=custom:16 \
--file=images/layered/Containerfile .Automated deployment
Automate site migration
After updating a custom image or deploying new app versions, a database migration must be executed using bench migrate.
Without running migrations, the site may become inconsistent or fail to start properly.
For automated deployments, this step should not be performed manually.
Consider using the dedicated migrator service provided as a Compose override. It ensures that migrations are executed automatically when the stack starts.
This approach is especially useful in CI/CD pipelines where no interactive access to the backend container is available.
See Compose override
