π How to Build, Ship, and Deploy a New Frappe Image with Updated Custom Apps (Without Affecting Existing Sites)
This guide explains how to add or update custom apps (e.g. ERPNext) in a Frappe Docker setup by:
- Building a new Docker image
- Exporting it as a tar archive
- Loading it on a server
- Restarting services safely (with scaling)
- Updating configuration correctly without deleting volumes, sites, or databases
This is the recommended and supported approach for production Frappe deployments.
π§ Core Principle (Very Important)
Apps live in the Docker image Sites & data live in Docker volumes
Because of this separation:
- You can safely change images
- Existing sites and databases remain untouched
- Apps can be installed on existing sites after restart
ποΈ PART 1 β Build a New Image with Updated Apps (Local Machine)
1οΈβ£ Define apps to include
Create or update apps.json:
[
{
"url": "https://github.com/frappe/erpnext",
"branch": "version-15"
}
]
(Add any custom apps here as additional entries.)
2οΈβ£ Encode apps.json as Base64
export APPS_JSON_BASE64=$(base64 -w 0 apps.json)
Verify:
echo $APPS_JSON_BASE64
3οΈβ£ Build the new custom image
Use the layered image approach:
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 \
--tag=customerp:15 \
--file=images/layered/Containerfile .
β This image now contains:
- Frappe
- ERPNext
- Any custom apps you listed
4οΈβ£ Verify image contents (important)
docker run --rm customerp:15 ls apps
Expected output:
frappe
erpnext
If the app is not listed here, do not proceed.
π¦ PART 2 β Export the Image for Server Deployment
Create a portable tar archive:
docker save -o frappe-images-erp.tar \
customerp:15 \
mariadb:11.8 \
redis:6.2-alpine \
traefik:v2.11
This tar file is self-contained and suitable for:
- Offline servers
- Air-gapped environments
- Disaster recovery
Copy it to the server:
scp frappe-images-erp.tar user@server:/path/to/frappe_docker/
π₯οΈ PART 3 β Load the Image on the Server
On the server:
docker load -i frappe-images-erp.tar
Verify:
docker image ls | grep customerp
βοΈ PART 4 β Update Configuration to Use the New Image
1οΈβ£ Edit custom.env
vi custom.env
Set:
CUSTOM_IMAGE=customerp
CUSTOM_TAG=15
β οΈ Do not change:
- Volume names
- Database credentials
- Redis settings
2οΈβ£ Re-generate the composed file
docker compose --env-file custom.env -p frappe \
-f compose.yaml \
-f overrides/compose.mariadb.yaml \
-f overrides/compose.redis.yaml \
-f overrides/compose.noproxy.yaml \
config > compose.custom.yaml
This ensures:
- Env changes are applied
- Image name is baked into the final compose file
π» PART 5 β Stop Services Safely
Stop all containers without deleting volumes:
docker compose -p frappe -f compose.custom.yaml down
Notes:
- This removes containers only
- Volumes (sites, DB, uploads) remain intact
- Scale information is reset (expected)
πΌ PART 6 β Start Services with Scaling Restored
If you previously ran multiple backend containers, you must re-apply scaling.
Example (2 backends):
docker compose -p frappe -f compose.custom.yaml up -d --scale backend=2
Verify:
docker compose -p frappe ps
Expected:
frappe-backend-1
frappe-backend-2
π§© PART 7 β Install the New App on Existing Sites
Even though the app code exists in the image, it must be installed per site.
1οΈβ£ Enter a backend container
docker compose -p frappe exec backend bash
2οΈβ£ List sites
ls sites
Example:
site1.local
site2.local
common_site_config.json
3οΈβ£ Install app on each site
bench --site site1.local install-app erpnext
Repeat for all required sites.
This:
- Runs database migrations
- Applies patches
- Registers doctypes
4οΈβ£ Enable scheduler (required for ERPNext)
bench --site site1.local set-config enable_scheduler 1
bench restart
π PART 8 β Access Verification
Depending on your setup:
- Use site name as hostname OR
- Set
FRAPPE_SITE_NAME_HEADERincustom.env
Example:
FRAPPE_SITE_NAME_HEADER=site1.local
Restart if changed:
docker compose up -d
β What NOT to Do (Critical Warnings)
| Action | Why |
|---|---|
| Delete Docker volumes | Data loss |
Run bench get-app in production |
Breaks immutability |
| Modify containers manually | Changes lost on restart |
Mix image names (custom vs customerp) |
App not found errors |
Skip install-app |
ERPNext wonβt work |
β Final Checklist
| Item | Status |
|---|---|
| New image built with apps | β |
Image verified (ls apps) |
β |
| Image loaded on server | β |
custom.env updated |
β |
| Compose regenerated | β |
| Services restarted | β |
| Scaling restored | β |
| App installed on site | β |
| Scheduler enabled | β |
π§ Summary
This workflow:
- Preserves all existing sites
- Preserves databases and uploads
- Allows safe app upgrades
- Is fully reproducible
- Works offline
- Matches Frappe Docker best practices
This is the correct way to manage apps in production Frappe environments.