Docker Deployment and Upgrades#
Overview#
Dify ships a Docker Compose stack in the docker/ directory. The docker-compose.yaml is auto-generated from .env.example and docker-compose-template.yaml and must not be edited directly . All customization goes into docker/.env, which overrides the defaults baked into the compose file .
Core Services#
The stack defines these primary services :
| Service | Image | Purpose |
|---|---|---|
api | langgenius/dify-api:<version> | Flask API server (Gunicorn) |
api_websocket | langgenius/dify-api:<version> | WebSocket server for collaboration |
worker / worker_beat | langgenius/dify-api:<version> | Celery task queue and scheduler |
web | langgenius/dify-web:<version> | Next.js frontend |
plugin_daemon | langgenius/dify-plugin-daemon:<version> | Plugin execution engine (Go, port 5002) |
agent_backend | langgenius/dify-agent-backend:<version> | Agent execution backend |
db_postgres / db_mysql | postgres / mysql | Main application database |
redis | redis | Cache, Celery broker, migration lock |
sandbox / local_sandbox | dify-sandbox | Code execution isolation |
nginx | nginx | Reverse proxy |
Vector store backends (Weaviate, Qdrant, Milvus, OpenSearch, PGVector, etc.) are optional and activated via COMPOSE_PROFILES .
Environment Variable Management#
Configuration Precedence#
Variables are resolved in this order (later source wins) :
docker/envs/**/*.envfiles (optional per-service overrides; copy the matching*.env.examplewithout the.examplesuffix to enable)docker/.env— the primary operator config file (always loaded last, takes precedence)- Hardcoded
environment:block defaults indocker-compose.yaml(fallback when a variable is absent from all env files)
Initial setup: cp docker/.env.example docker/.env, then edit docker/.env. No other files need to be touched for a standard deployment .
Key Variables by Domain#
Security :
SECRET_KEY— leave blank to auto-generate; must be set explicitly in production multi-instance deploymentsPLUGIN_DAEMON_KEY— shared secret between the API and plugin daemon; defaults are insecure and must be rotated in productionPLUGIN_DIFY_INNER_API_KEY— inner API key for plugin-to-API callbacks
Workers :
SERVER_WORKER_AMOUNT=1— Gunicorn workers for the API; each blocks for the full duration of plugin daemon calls, so increase this for concurrent agent workloadsCELERY_WORKER_AMOUNT=4— async task workers
Database :
DB_TYPE,DB_HOST,DB_PORT,DB_DATABASE,DB_USERNAME,DB_PASSWORDDB_PLUGIN_DATABASE=dify_plugin— separate database used exclusively by the plugin daemon ; must exist before the daemon starts
Plugin daemon :
PLUGIN_DAEMON_URL=http://plugin_daemon:5002— use the Docker service name, notlocalhostPLUGIN_MAX_EXECUTION_TIMEOUT=600— per-plugin execution timeout (seconds)PLUGIN_STORAGE_TYPE=local— plugin file storage backend (local, S3, Azure, Aliyun OSS, Tencent COS, Volcengine TOS)PLUGIN_INSTALLED_PATH=plugin— relative subdir underPLUGIN_STORAGE_LOCAL_ROOTfor installed pluginsFORCE_VERIFYING_SIGNATURE=true— enforce plugin signature verification
The entire plugin daemon storage tree binds to ./volumes/plugin_daemon → /app/storage inside the container .
Nginx :
NGINX_HTTPS_ENABLED,NGINX_PORT=80,NGINX_SSL_PORT=443,NGINX_CLIENT_MAX_BODY_SIZE=100M
Upgrade Procedure#
The canonical sequence for any major version bump:
1 — Pull and restart#
cd docker
git pull
docker compose pull
docker compose up -d
2 — Run database schema migration#
flask upgrade-db applies Alembic migrations to the main dify database using a Redis-backed distributed lock (60 s TTL, auto-renewing heartbeat) to prevent concurrent migration races :
docker compose exec api flask upgrade-db
With MIGRATION_ENABLED=true (the default), the API container also runs this automatically on startup . In multi-instance setups, only one pod acquires the lock; others skip migration silently.
3 — Run plugin backfill#
After schema migration, populate missing per-category auto-upgrade strategy rows for all tenants :
docker compose exec api flask backfill-plugin-auto-upgrade
Supports --dry-run, --tenant-id, --limit, and --batch-size flags. See api/commands/plugin.py for the implementation.
4 — Verify plugin daemon version#
The plugin daemon is versioned in lockstep with the Dify API. Protocol changes between releases cause complete communication failure if versions diverge :
| Dify API | Plugin Daemon image tag |
|---|---|
| 1.13.x | 0.5.x |
| 1.14.x | 0.6.x |
| 1.15.0 | 0.6.3-local |
The image tag in the current compose file is langgenius/dify-plugin-daemon:0.6.3-local . Confirm the running daemon image matches the Dify API version before proceeding.
5 — Clean stale plugin directories#
On startup the daemon scans PLUGIN_INSTALLED_PATH and logs record not found for any directory whose version identifier lacks a matching database row. Remove stale subdirectories from ./volumes/plugin_daemon/plugin/ once plugins are confirmed working in the UI .
Known Upgrade Failure Patterns#
See Plugin Database Integrity for full diagnostic queries and SQL recovery scripts.
| Failure | Symptom | Fix |
|---|---|---|
| Version reconciliation (1.15.0+) | record not found / Plugin table missing in daemon logs | Run flask backfill-plugin-auto-upgrade; remove stale dirs from PLUGIN_INSTALLED_PATH |
model_type enum mismatch (1.14.x) | Models visible but uneditable; HTTP 400 "Credential with id … not found" | Manual SQL UPDATE on provider_models, provider_model_credentials, and 3 related tables |
Duplicate plugin_unique_identifier (1.13.x→1.14.x) | Daemon restart loop, SQLSTATE 23505 | Deduplicate dify_plugin.plugins table, then restart daemon |
| Daemon schema init sequence (fresh install) | dify_plugin tables not created | Install daemon 0.5.3-local first for schema baseline, then upgrade |
Key Source Files#
| File | Purpose |
|---|---|
docker/docker-compose.yaml | Auto-generated compose stack — do not edit |
docker/.env.example | Canonical list of all configurable variables |
api/commands/system.py | flask upgrade-db implementation |
api/commands/plugin.py | flask backfill-plugin-auto-upgrade and other plugin CLI commands |
api/libs/db_migration_lock.py | Redis distributed migration lock |