Moving from a local Frappe setup to Docker in production involves a fundamental shift. You cannot simply run bench get-app inside a running container: because containers are ephemeral, any in-place modifications disappear whenever a container restarts.
To deploy custom apps in production, your custom apps must be baked directly into the custom Docker image and orchestrated via Docker Compose alongside Redis, MariaDB, and Traefik reverse proxies.
Here is the straightforward, production-grade guide to deploying custom Frappe apps with Docker.
The Production Docker Workflow
Unlike monolithic bench installs, production Docker deployments use separated microservice containers:
backend(Gunicorn Python WSGI)frontend(Nginx serving static assets and proxying requests)websocket(Node.js socketio service)queue-default,queue-short,queue-long(Background Celery workers)scheduler(Cron tasks)
All these services share the same custom image containing your app code and static assets.
Step 1: Bake Your Custom App into the Production Image
In your deployment repository:
- Create a
compose.yamland reference your custom Dockerfile or pre-built image tag. - Ensure your custom image includes both your custom app and official apps like ERPNext or HRMS.
- Build and tag your image locally or via GitHub Actions:
docker compose build
Step 2: Running the Database Migrations
Once your containers are started:
docker compose up -d
You must run migrations to create the custom DocTypes, fields, and permissions in your MariaDB database:
docker compose exec backend bench --site yourdomain.com install-app your_custom_app
docker compose exec backend bench --site yourdomain.com migrate
Step 3: Rebuilding Assets for Production Nginx
If your custom app includes client scripts, custom web views, or bundled Vue/React bundles:
docker compose exec backend bench build --app your_custom_app
docker compose exec backend bench --site yourdomain.com clear-cache
Restart the frontend and backend services to ensure Gunicorn workers reload python bytecode:
docker compose restart backend frontend websocket
Key Production Gotchas
- Volume Persistence: Never store custom app code in ephemeral root directories. Persistent files, logs, and private/public uploads must be mounted to named Docker volumes (
sites). - Database Backups: Always run
bench --site yourdomain.com backup --with-filesbefore executing migrations on live production databases. - Environment Variables: Keep secret database passwords and encryption keys in
.envfiles with restricted600file permissions.
Summary
Containerizing Frappe apps gives you predictable deployments: straightforward horizontal scaling, fast rollbacks, and no host-level Python dependency conflicts.
Looking for dedicated Frappe & ERPNext cloud deployment, server migration, or custom ERP development? Connect with the engineers at Klickspell.
Need technical precision for your web stack?
Architected for performance & scale.
From modern headless commerce to enterprise infrastructure and clean frontend state systems, Klickspell delivers senior engineering craft without agency bloat.
Keep Reading
Related Engineering Articles
How to Install ERPNext on ARM64 (Apple Silicon & Ampere) Using Docker
A complete step-by-step guide to installing and running ERPNext on ARM64 / aarch64 architecture (Apple M1/M2/M3/M4 Macs and Oracle Ampere VPS) using Docker.
Frappe Docker: Custom Apps Installation Using Quick Build Image
A complete step-by-step developer guide on building and installing custom Frappe and ERPNext apps using the official frappe_docker quick build image in 2025/2026.