How to Upgrade Self-Hosted Dify Safely with Database Backups, Migration Checks, and Rollback
Upgrading a production Dify stack is not a simple git pull command; you need a real backup, a migration check, and a tested rollback path before you upgrade self-hosted Dify in production. This guide shows you how to safely upgrade a Dify installation to avoid data loss or corruption.
Table of Contents
Why Dify Upgrades Fail Without Preparation
Dify’s docker-compose stack bundles 13 to 15 containers, including API, worker, worker_beat, web, plugin_daemon, Postgres, Redis, and a vector store. Every minor release can add new environment variables, new services, or database migrations. Skipping the backup and validation steps when you upgrade self-hosted Dify is how teams end up with a corrupted messages table, mismatched .env variables, or a database stuck mid-migration.
If you haven’t set up your Dify instance yet, check out our guide on self-hosting Dify with Docker Compose before continuing with this upgrade tutorial.
Prerequisites to Upgrade Self-Hosted Dify Safely
Before you upgrade self-hosted Dify, confirm you have shell access to the server, enough free disk space for a full volume backup, and a maintenance window since Dify has no zero-downtime upgrade path in Docker Compose mode.
- Root or sudo access to the server hosting your Dify Docker setup.
- Enough free disk space, at least double the size of your current
volumes/folder for backups. - Know your current Dify version tag before starting:
git describe --tagsor checkdocker compose psimage tags. - A maintenance window, since upgrading takes the app offline for all users.
- The changelog for the version you’re upgrading to, reviewed in advance.
Step 1. Review Release Notes Before Choosing a Version
Never upgrade self-hosted Dify by jumping to main; always use a specific tagged release instead. Dify’s docs and community issues show that even minor version updates can bring breaking changes, like the Postgres hostname switching from db to db_postgres or new services becoming required.
cd ~/dify/docker
git fetch --tags
git tag -l | tail -20 # list recent stable tags
git describe --tags # confirm the currently running version
Read the changelog for every version between your current one and the target, not just the latest release; migrations build on top of each other. Pinning to a specific version this way keeps you from accidentally running untested code.
Step 2. Back Up the PostgreSQL Database
Every safe attempt to upgrade self-hosted Dify starts with a full PostgreSQL dump, separate from the volumes backup, because a pg_dump can be restored even if the data directory itself gets corrupted during copy.
Create a new backup folder named with the current date and time:
mkdir -p ~/dify-backups/$(date +%Y%m%d_%H%M)
Navigate to the Dify Docker directory and export the Dify database from inside the Postgres container into a backup file:
cd ~/dify/docker
docker compose exec db_postgres pg_dump -U postgres -d dify \
-F c -f /tmp/dify_backup.dump
Then, copy that backup file out of the container onto your server, into the folder you just created:
docker compose cp db_postgres:/tmp/dify_backup.dump \
~/dify-backups/$(date +%Y%m%d_%H%M)/dify_backup.dump
Before moving on, check that the backup file isn’t empty:
pg_restore --list dify_backup.dump
Never copy Postgres’s live data files while the container is still running. Doing this instead of a proper dump has caused real migration failures, since the database’s internal logs can end up inconsistent.
Step 3. Back Up the Docker Volumes
The second backup layer needed before you upgrade self-hosted Dify is a full archive of docker/volumes/. This directory holds Postgres data, Redis state, uploaded files, and plugin storage; deleting it means deleting the entire install.
cd ~/dify/docker
docker compose down # stop containers, keep volumes intact
tar -czvf ~/dify-backups/volumes-$(date +%s).tar.gz volumes/
docker compose up -d # bring the current version back up while you verify the backup
Note: Use tar instead of zip for backups. Zip can break symlinks in the Python environment and plugin folders, which has caused migration errors before.
For extra safety, you can send a copy of this archive offsite to S3 or MinIO. This guide on backing up Docker volumes to S3 or MinIO shows the process. That way, you can still roll back even if the server itself goes down.
Step 4. Validate Environment Variable Changes
Mismatched .env files are the most common reason teams fail to upgrade self-hosted Dify, because new releases regularly add required variables that don’t exist in an older .env file.
cd ~/dify/docker
cp .env .env.$(date +%s).bak # snapshot the current env before editing
diff .env.example .env | grep '^<' # variables in .env.example missing from .env
Add any new variables the diff shows, and check for values that changed defaults. For example, past upgrades changed which user the container runs as, which meant a one-time chown fix was needed on the storage folder:
sudo chown -R 1001:1001 ./volumes/app/storage
Compare each changed variable with the release notes rather than copying .env.example over your file.
Step 5. Run the Dify Migration on a Staging Copy First
Before you upgrade self-hosted Dify in production, run through the same upgrade steps on a clone of the server or a spare VM with your restored backups. This way, if anything breaks during migration, it happens somewhere safe instead of in front of real users.
git checkout <target-tag>
cd docker
docker compose down
docker compose pull
docker compose up -d
If the version you’re upgrading to doesn’t run migrations automatically, you’ll need to run the database and plugin migration commands yourself, in this exact order:
docker compose exec api uv run flask db upgrade
docker compose exec api uv run flask extract-plugins --workers=20
docker compose exec api uv run flask install-plugins --workers=2
docker compose exec api uv run flask migrate-data-for-plugin
Recent releases run flask db upgrade automatically on API container start when MIGRATION_ENABLED=true is set, so check your .env for that flag before running migrations manually and risking a duplicate run.
Step 6. Watch the Containers Startup Logs
Watching the containers start up is a key part to upgrade self-hosted Dify safely. Compose starts services in a specific order:
init_permissionsruns first and exits.- Postgres waits until it’s healthy.
- The API starts.
- Nginx comes up last.
List all containers with their name and current status:
docker compose ps --format "{{.Name}} {{.Status}}" | sort
Check the logs with:
docker compose logs -f api worker db_postgres
If any core service, including api, worker, or the database, is not marked healthy within a couple of minutes, stop before touching production traffic and check logs for migration errors, missing tables, or stuck indexes.
Step 7. Confirm Dify Apps Still Work After Upgrading
Just because containers start cleanly doesn’t mean your data survived correctly. You must test real workflows and plugins after upgrading, not just the login page.
- Log in with an admin account and check that your apps and datasets are still there.
- Open an existing chatflow or workflow and run it start to finish.
- Check that your model provider plugins still have valid API keys.
- Query the knowledge base to ensure it still connects to the vector store.
- If something looks off, run
docker compose exec api flask db historyand compare it withapi/migrations/versions/.
Roll Back to the Last Dify Version Without Losing Data
If tests fail, the safest way to roll back after a failed attempt to upgrade self-hosted Dify is to restore the previous volumes archive and database dump. To do this, you can use:
cd ~/dify/docker
docker compose down -v # stop and remove the failed upgrade's containers
git checkout <previous-tag> # return to the last known-good tag
rm -rf volumes/
tar -xzvf ~/dify-backups/volumes-<timestamp>.tar.gz -C .
docker compose exec db_postgres pg_restore -U postgres -d dify \
--clean ~/dify-backups/<timestamp>/dify_backup.dump
docker compose up -d
Note: Never restore volumes while containers are still running. Always stop the stack first, just like during the backup step. Restoring live risks the same kind of corruption that happens when Postgres files are copied without stopping the service.
Pre-Upgrade Checklist for Dify
This checklist is what actually prevents downtime when you upgrade self-hosted Dify, beyond just running git pull:
| Check | Why it matters |
|---|---|
Target version tag confirmed, not main | Avoids untested commits |
| Changelog read for every version in between | Catches renamed services, new required vars |
pg_dump completed and verified restorable | Protects against corrupted migrations |
volumes/ archived with tar, not zip | Preserves symlinks in plugin/venv paths |
.env diffed against .env.example | Prevents missing-variable startup failures |
| Migration staged on a clone/spare VM first | Isolates failures from production |
| Maintenance window scheduled | Compose upgrade requires full stack downtime |
| Disk space checked (2x current volumes size) | Backup + new images need headroom |
Dify Rollback Checklist
This rollback checklist assumes you already tried to upgrade self-hosted Dify and hit an error during migration, startup, or testing.
| Step | Command/action |
|---|---|
| Stop the failed stack | docker compose down -v |
| Check out previous tag | git checkout <previous-tag> |
| Restore volumes archive | tar -xzvf volumes-<timestamp>.tar.gz -C . |
| Restore database dump | pg_restore -U postgres -d dify --clean <dump> |
| Restart on old version | docker compose up -d |
| Re-verify workflows and plugins | Repeat Step 7 checks above |
Where to Run Production Dify
A safe rollback needs enough free disk space for backups and snapshots, so don’t run production Dify on a minimal server. Our dedicated server hosting plans give you dedicated NVMe storage built for this exact need, including enough room to keep both the old and new versions side by side during an upgrade, without worrying about running out of space.
With backups, tested migrations, and a working rollback plan in place, you can upgrade self-hosted Dify with confidence instead of just hoping nothing breaks.
Conclusion
Upgrading Dify safely is less about technical skill and more about discipline. You must pin your version, back up the database, archive the volumes, check the .env file, test the migration first, and verify workflows before you call it done. Follow this checklist every time, and upgrading turns from a risky task into something routine and reversible.
We hope you enjoy this guide. For version-specific changelogs and upgrade notes before every release, you can check the Dify official GitHub Releases page.
FAQs
Do I need to stop Dify before backing up volumes?
Yes, run docker compose down first, since copying live Postgres files can corrupt the database.
Can I skip the Dify database migration step?
No, recent versions require flask db upgrade and, for plugin-based releases, migrate-data-for-plugin, or the app will throw internal errors.
Is docker compose pull enough to upgrade Dify?
No, you must also git checkout the matching source tag first, since compose files and migrations are version-specific.