Skip to main content

Dify 1.15.0 Docker Upgrade Runbook

Status: prepared only. Do not run against production or shared customer data without explicit approval and a maintenance window.

Primary source: Dify 1.15.0 release notes

Why This Upgrade Matters

Dify 1.15.0 includes a security fix for plugin-daemon path traversal (CVE-2026-41948), new database migrations, and a required backfill for plugin auto-upgrade settings. It also changes Docker Compose and environment examples, so local compose and .env overrides must be reconciled before services are recreated.

Prior Upgrade Lessons To Reuse

From the previous 1.12.1 to 1.14.2 upgrade:

  • Active self-hosted Docker config is docker/.env; docker/envs/**/*.env.example are references unless copied to real .env files.
  • Back up docker-compose.yaml, .env, and volumes before changing tags.
  • If local compose edits block checkout, stash them first, switch tag, then reapply only required local overrides.
  • Keep known local overrides only when still needed:
    • PERSISTENCE_STORAGE_MAX_SIZE=5368709120 in docker/.env.
    • Postgres shm_size: "1gb" in compose if target DB still needs it. This is an expected local diff under services.db_postgres; do not mistake it for accidental upstream drift.
  • Do not carry old local api or web build: overrides into official image upgrades.
  • Use sudo consistently on the remote host when Docker socket or /opt/services/dify permissions require it.
  • Check CPU flags before blaming Dify config. The 1.14.2 image previously failed on a VM exposing Common KVM processor because NumPy required x86-64-v2 style flags.
  • Self-hosted HTTPS collaboration mode must not point browsers to ws://localhost. Keep:
    • ENABLE_COLLABORATION_MODE=true
    • NEXT_PUBLIC_SOCKET_URL=wss://chat.menzzo.com
    • API_WEBSOCKET_WORKER_CLASS=geventwebsocket.gunicorn.workers.GeventWebSocketWorker
  • If the external logs integration still depends on it, reconnect retrive_logs_network to docker-db_postgres-1 after compose recreation.

1.15.0 Release Deltas To Check

Required upgrade actions:

  • Run database migrations: flask db upgrade.
  • Run plugin auto-upgrade backfill after migrations: flask backfill-plugin-auto-upgrade.
  • Review environment variable changes: 19 added, 2 removed, 1 modified in release notes.
  • Review Docker Compose changes: docker/docker-compose.yaml and docker/docker-compose.middleware.yaml changed; docker/docker-compose.pytest.ports.yaml was added.

High-signal env changes from the release notes:

  • Added OpenAPI and OAuth/device-flow vars:
    • OPENAPI_ENABLED
    • OPENAPI_CORS_ALLOW_ORIGINS
    • OPENAPI_KNOWN_CLIENT_IDS
    • OPENAPI_RATE_LIMIT_PER_TOKEN
    • ENABLE_OAUTH_BEARER
    • DEVICE_FLOW_APPROVE_RATE_LIMIT_PER_HOUR
  • Added server/web feature vars:
    • SERVER_CONSOLE_API_URL
    • NEXT_PUBLIC_ENABLE_FEATURE_PREVIEW
    • ENABLE_LEARN_APP
    • PLUGIN_MODEL_PROVIDERS_CACHE_TTL
    • DIFY_ENV_NACOS_CONNECT_TIMEOUT
    • DIFY_ENV_NACOS_REQUEST_TIMEOUT
  • Added SSRF and sandbox proxy vars:
    • SSRF_PROXY_ALLOW_PRIVATE_DOMAINS
    • SSRF_PROXY_ALLOW_PRIVATE_IPS
    • SSRF_SANDBOX_PROXY_HOST
    • SSRF_SANDBOX_PROXY_PORT
  • Added Milvus TLS vars:
    • MILVUS_SECURE
    • MILVUS_SERVER_NAME
    • MILVUS_SERVER_PEM_PATH
  • Added plugin daemon mirror vars:
    • PIP_MIRROR_AUTO_DETECT
    • PIP_MIRROR_URL
  • Removed:
    • SSRF_REVERSE_PROXY_PORT
    • SSRF_SANDBOX_HOST
  • Modified:
    • UV_CACHE_DIR moved from /tmp/.uv-cache to /tmp/uv_cache in release notes. Verify the rendered compose value because current Docker examples may contain both older and newer references.

Preflight Checklist

Run read-only checks first:

cd /opt/services/dify
sudo git status --short
sudo git describe --tags --always --dirty
cd docker
sudo docker compose ps
sudo docker compose images
df -h
lsblk
lscpu | grep -E 'Model name|Flags'
sudo du -sh volumes

Confirm before continuing:

  • Target host and domain are correct.
  • Maintenance window is approved.
  • Current running version is known.
  • Disk has room for image pulls and volumes backup.
  • DB/volume backup destination has enough space.
  • VM exposes CPU flags needed by official images.
  • Current docker/.env has no accidental ws://localhost for public HTTPS deployment.
  • Secrets will not be printed into chat, logs, tickets, or docs.

Backup

From /opt/services/dify:

ts=$(date +%Y%m%d-%H%M%S)
backup_root="/opt/services/dify/backups/pre-1.15.0-$ts"
umask 077
sudo install -d -m 700 "$backup_root"
sudo cp docker/docker-compose.yaml "$backup_root/docker-compose.yaml.bak"
sudo cp docker/.env "$backup_root/.env.bak"

Optional DB logical backup, if Postgres is healthy and credentials are available from the target environment:

cd /opt/services/dify/docker
backup_root=${backup_root:-"/opt/services/dify/backups/pre-1.15.0-manual"}
sudo install -d -m 700 "$backup_root"
sudo sh -c 'docker compose exec -T db_postgres sh -lc '\''pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"'\'' > "$1/dify-db.sql"' sh "$backup_root"

If pg_dump is not available or the DB service name differs on the target, stop and identify the actual DB service before proceeding.

During the approved maintenance window, stop services before taking the volume archive so database files are consistent:

cd /opt/services/dify/docker
sudo docker compose stop
sudo tar -C /opt/services/dify/docker -czf "$backup_root/volumes.tgz" volumes

Upgrade Command Order

From /opt/services/dify:

sudo git status --short --untracked-files=no
sudo git diff -- docker/docker-compose.yaml
sudo git stash push -m "pre-1.15.0-compose-overrides" -- docker/docker-compose.yaml
sudo git stash list -n 1
sudo git fetch --tags
sudo git checkout 1.15.0

If git status --short --untracked-files=no shows unexpected tracked files, stop and decide whether they are real local fixes before stashing. docker/.env is normally ignored, so preserve it via the backup copy and merge it manually. Reapply only required local overrides. Do not blindly restore the old compose file.

After reapplying local compose changes, this should be the only intentional tracked diff unless another target-specific override is approved:

sudo git diff HEAD -- docker/docker-compose.yaml

Expected known diff:

@@ services:
db_postgres:
restart: always
+ shm_size: "1gb"
environment:

Minimum review set:

cd /opt/services/dify/docker
sed -n 's/^\([A-Z0-9_][A-Z0-9_]*\)=.*/\1/p' .env.example | sort > /tmp/dify-1.15-example.keys
sed -n 's/^\([A-Z0-9_][A-Z0-9_]*\)=.*/\1/p' .env | sort > /tmp/dify-current.keys
comm -13 /tmp/dify-current.keys /tmp/dify-1.15-example.keys
comm -23 /tmp/dify-current.keys /tmp/dify-1.15-example.keys
grep -nE '^(NEXT_PUBLIC_SOCKET_URL|ENABLE_COLLABORATION_MODE|API_WEBSOCKET_WORKER_CLASS|SERVER_CONSOLE_API_URL|OPENAPI_ENABLED|OPENAPI_CORS_ALLOW_ORIGINS|OPENAPI_KNOWN_CLIENT_IDS|OPENAPI_RATE_LIMIT_PER_TOKEN|SSRF_SANDBOX_PROXY_HOST|SSRF_SANDBOX_PROXY_PORT|SSRF_PROXY_ALLOW_PRIVATE_IPS|SSRF_PROXY_ALLOW_PRIVATE_DOMAINS|UV_CACHE_DIR)=' .env
sudo docker compose config >/tmp/dify-compose-1.15.0.yaml
grep -nE 'image: langgenius/dify-(api|web)|build:|shm_size|NEXT_PUBLIC_SOCKET_URL|ENABLE_COLLABORATION_MODE|API_WEBSOCKET_WORKER_CLASS|SERVER_CONSOLE_API_URL|OPENAPI_|SSRF_SANDBOX|SSRF_PROXY_ALLOW|UV_CACHE_DIR' /tmp/dify-compose-1.15.0.yaml

Expected before starting services:

  • API and web use official 1.15.0 images, not local build:.
  • Required local storage and DB overrides are present.
  • Public HTTPS deployment uses NEXT_PUBLIC_SOCKET_URL=wss://chat.menzzo.com.
  • Web has SERVER_CONSOLE_API_URL=http://api:5001 unless this deployment has a specific internal route.
  • Removed SSRF vars are gone from active config.
  • New SSRF sandbox vars render as intended.

Start upgrade:

sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
sudo docker compose ps

Run required Dify 1.15.0 upgrade commands:

sudo docker compose exec api flask db upgrade
sudo docker compose exec api flask backfill-plugin-auto-upgrade

If flask db upgrade fails before Flask command resolution, inspect the first import traceback before retrying. If it resembles the previous NumPy CPU failure, re-check VM CPU exposure.

If log integration needs the external DB network:

sudo docker network connect retrive_logs_network docker-db_postgres-1

Ignore "already exists" style errors only after confirming the network is already attached.

Validation

Backend and containers:

cd /opt/services/dify/docker
sudo docker compose ps
sudo docker compose logs --tail=120 api
sudo docker compose logs --tail=120 api_websocket
sudo docker compose logs --tail=120 worker
sudo docker compose logs --tail=120 web
sudo docker compose exec api flask db current

HTTP and browser:

curl -I https://chat.menzzo.com
curl -I https://chat.menzzo.com/console/api/setup

Manual smoke:

  • Log in to console.
  • Open one existing app.
  • Run one chat or workflow.
  • Open workflow editor and confirm no browser console calls to ws://localhost.
  • Check plugin page and existing plugin auto-upgrade settings.
  • If human input file upload is used, test one paused workflow with file upload in staging first.
  • If Excel knowledge imports matter, test one small spreadsheet with embedded image in staging first.

Pass criteria:

  • flask db upgrade exits 0.
  • flask backfill-plugin-auto-upgrade exits 0.
  • docker compose ps shows expected services healthy/running.
  • No API import-time traceback.
  • Browser socket URL is public wss:// domain, not localhost.
  • Existing apps, workflows, plugins, and knowledge bases load.

Rollback

Rollback is only safe if backup exists and upgrade impact is understood. DB migrations may not be trivially reversible.

Basic rollback shape:

backup_root=/opt/services/dify/backups/pre-1.15.0-<timestamp>
cd /opt/services/dify/docker
sudo docker compose down
cd /opt/services/dify
sudo git checkout <previous-version-tag-or-commit>
sudo cp "$backup_root/.env.bak" docker/.env
sudo cp "$backup_root/docker-compose.yaml.bak" docker/docker-compose.yaml

Restore volumes or DB from the backup only with explicit approval. Do not restore over live data without confirming the target, timestamp, and expected data loss.

Open Questions Before Execution

  • Exact current production version and commit?
  • Is target chat.menzzo.com still the deployment domain?
  • Is this production, staging, or dev?
  • Is retrive_logs_network still required?
  • Does target use Milvus, OpenAPI/difyctl, OAuth bearer flow, human input uploads, or Excel image extraction?
  • Where should DB logical backups be stored on the host?

Sources

  • Dify 1.15.0 release notes
  • Local previous upgrade memory: 1.12.1 to 1.14.2 Docker upgrade, CPU issue, and collaboration WebSocket fix.