Intro
Docker Compose is a staple for defining and running multi-container applications, especially in development and smaller production environments. But when you need to upgrade the Compose tool itself, migrate to a new major version, or move your production stack to a different host, doing so without a clear plan invites downtime and difficult rollbacks. A structured approach keeps your services available and your team confident.
This guide walks through a practical upgrade and migration path for Docker Compose in production. You will learn how to inventory your current environment, prepare safe configuration changes, validate the upgrade with concrete commands, handle failure modes with rollback procedures, and follow a repeatable checklist. Every step includes real commands and expected outputs. The focus is on a single-host Compose deployment, not Swarm or Kubernetes.
By the end, you will be able to execute a low-risk Compose upgrade or migration, and be ready to recover quickly if something goes wrong.
Version and Environment Inventory
Before touching anything in production, you need a complete picture of what you have. This prevents surprises caused by version mismatches, missing dependencies, or environment drift. A clear inventory is the foundation for a safe migration.
Check Docker and Compose Versions
On your production host, run:
docker version
Expected output includes client and server versions. Look for the API version compatibility. Example (trimmed):
Client: Docker Engine - Community
Version: 24.0.7
API version: 1.43
Server: Docker Engine - Community
Engine:
Version: 24.0.7
API version: 1.43 (minimum version 1.12)
Next, check the Docker Compose version:
docker compose version
Note whether you are using the standalone docker-compose (v1) or the plugin docker compose (v2). The output tells you:
Docker Compose version v2.23.0
If you have docker-compose (with hyphen) installed, check that separately:
docker-compose version
Inventory Running Services
List all running containers for your Compose project:
docker compose ps
Or, if you are using an older standalone version:
docker-compose ps
Expected output shows service names, state, and ports. Example:
NAME COMMAND SERVICE STATUS PORTS
myapp_web_1 "nginx -g 'daemon of..." web running 0.0.0.0:80->80/tcp
myapp_db_1 "docker-entrypoint.s..." db running 5432/tcp
Record this state. If something breaks later, you can compare.
Capture Current Configuration
Always back up your current working configuration. Copy the entire project directory, which typically contains docker-compose.yml, .env, and any bind-mounted files. For example:
tar -czf compose-backup-$(date +%Y%m%d).tar.gz /opt/myapp
Store this archive off the server, perhaps on a local machine or object storage.
Identify Environment Variables and Secrets
Compose files often reference environment variables from a .env file or from the shell. List them without exposing secrets:
docker compose config
This command validates and prints the fully resolved configuration. It is a crucial tool. The output is long, so redirect to a file for review:
docker compose config > resolved-compose.yml
Open resolved-compose.yml and look for any values that seem incorrect, missing, or hardcoded. This also confirms which Compose file version is being interpreted.
Prerequisites for Upgrade/Migration
Before proceeding, ensure:
- You have SSH access to the host and sudo privileges if needed.
- The host has enough disk space for new images. Check with
df -h. - You know the current state of persistent volumes:
docker volume lsanddocker volume inspect <volume>. - You have a rollback point: the backup archive and a snapshot of the database if applicable.
- You understand any external dependencies (load balancers, DNS, monitoring) that point to this host.
Document all of the above in a simple table or notes. Here is an example inventory table:
| Item | Current Value | Notes |
|---|---|---|
| Docker Engine version | 24.0.7 | Supports Compose v2 |
| Compose version | v2.23.0 | Plugin, not standalone |
| Compose file version | 3.8 | Top-level version field |
| Running services | web, db, cache | All healthy |
| Persistent volumes | myapp_db_data | PostgreSQL data |
| Environment file | /opt/myapp/.env | Contains DB password |
This inventory will guide every subsequent step.
Safe Configuration Path
Now that you know the current state, you can plan the upgrade or migration safely. The key is to make small, reviewable changes and test them before applying to production.
Decide on the Target Versions
Determine which Docker Engine and Compose versions you want to move to. Check compatibility matrices from Docker documentation. For example, Compose v2 requires Docker Engine 20.10 or later. Upgrading Compose alone is often simpler than upgrading the engine.
If you are also migrating to a new host, the versions on the new host should be at least the same as the old, preferably newer.
Use a Staging or Pre-Production Environment
Ideally, you have a staging environment that mirrors production. But even without one, you can test the upgrade on a separate virtual machine or a copy of the production host (e.g., snapshot on cloud provider). This is where you run the first pilot.
Create a test environment as close to production as possible. Use the same OS, Docker versions, and the same resolved Compose configuration. Then apply the upgrade steps there and observe the results.
Upgrade Docker Compose (if target is Compose only)
If you only need to upgrade Compose itself on the existing host, follow the official installation method for your OS. For Linux with the plugin, you can update via package manager, for example on Ubuntu:
sudo apt update && sudo apt upgrade docker-compose-plugin
If you installed the binary manually, download the new binary and replace the old one:
sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
After installation, verify:
docker compose version
Expected output shows the new version.
Upgrade Docker Engine (if required)
If the engine also needs an upgrade, refer to the official Docker upgrade guide for your distribution. Typically, you stop all containers gracefully first:
docker compose down
Then upgrade Docker packages, and restart Docker. After the engine is upgraded, bring the stack back up:
docker compose up -d
Always test this on staging first.
Migration to a New Host
If you are migrating to a new server, the safest sequence is:
- Set up the new host with the desired Docker and Compose versions.
- Copy the project directory (including
.envand any bind mounts) to the new host. - On the old host, stop the application to ensure data consistency:
docker compose down(but keep volumes if you need to copy data). - Copy persistent volumes from the old host to the new host. One way is to use
docker runwith a tar pipe:
From the old host, for each volume (e.g., myapp_db_data):
docker run --rm -v myapp_db_data:/data -v /tmp:/backup alpine tar czf /backup/myapp_db_data.tar.gz -C /data .
Then copy the tarball to the new host and restore:
docker run --rm -v myapp_db_data:/data -v /tmp:/backup alpine sh -c "cd /data && tar xzf /backup/myapp_db_data.tar.gz"
Note: This method is acceptable for small volumes. For large or critical databases, use native database backup tools (e.g., pg_dump) instead.
- On the new host, start the stack:
docker compose up -d. - Verify everything works, then switch traffic (DNS or load balancer) to the new host.
- Keep the old host for a rollback period, then decommission.
Modify Compose File for New Version
If the new Compose version introduces changes (e.g., removal of version field is now recommended in v2), update your docker-compose.yml accordingly. Test with docker compose config to ensure no warnings or errors.
Example: Remove the top-level version line if present (Compose v2 no longer uses it).
Use Environment Overrides for Safer Changes
Instead of editing the main file directly, use an override file for changes specific to the environment. For instance, docker-compose.override.yml can set resource limits or logging. This keeps your base file unchanged and reduces mistakes.
Verification and Diagnostics
After the upgrade or migration, you must confirm that the application works as expected. Rely on both Docker commands and application-level checks.
Check Service Status
Run:
docker compose ps
All services should show "running" or "running (healthy)" if healthchecks are defined. Example:
NAME COMMAND SERVICE STATUS PORTS
myapp_web_1 "nginx -g 'daemon of..." web running (healthy) 0.0.0.0:80->80/tcp
myapp_db_1 "docker-entrypoint.s..." db running (healthy) 5432/tcp
View Logs for Errors
docker compose logs --tail=100
Look for any stack traces, connection errors, or configuration warnings. If using -f to follow logs, stop with Ctrl+C.
Test HTTP Endpoints
If the application serves web traffic, use curl to check a known URL:
curl -I http://localhost
Expected output includes HTTP/1.1 200 OK or a redirect. If you expect a specific page content, fetch it and grep for a known string:
curl -s http://localhost | grep -i "welcome"
Check Container Resource Usage
Ensure containers are not consuming excessive CPU or memory:
docker stats --no-stream
Example output:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
abc123def456 myapp_web_1 0.50% 25MiB / 1GiB 2.44% 1.2kB / 0B 0B / 0B 5
Compare with pre-upgrade baselines if you recorded them.
Verify Persistent Data
For databases, run a quick query to ensure data is intact. For PostgreSQL inside the container:
docker compose exec db psql -U myuser -c "SELECT count(*) FROM mytable;"
Expected output shows the row count. If you migrated volumes, confirm the count matches the old host.
Run Healthchecks
Docker healthchecks provide automatic status. If defined in your Compose file, you can inspect the last health status:
docker inspect --format='{{json .State.Health}}' myapp_web_1
Output includes "Status":"healthy" and recent log entries.
Monitor Network Connectivity
Ensure containers can reach each other. For example, from the web container, ping the database hostname:
docker compose exec web ping -c 2 db
Or test a TCP connection with nc if available:
docker compose exec web nc -zv db 5432
Expected: db (172.18.0.2:5432) open.
Validate with Integration Tests
If you have a test suite, run a smoke test against the production deployment (without destructive writes). This can be as simple as a script that hits key endpoints and verifies responses.
Failure Modes and Recovery
Even with careful planning, things can go wrong. Here are common failure scenarios during Compose upgrade or migration, and how to recover.
Container Fails to Start After Upgrade
Possible causes: incompatible Compose file syntax, missing environment variables, port conflicts, or image issues.
Recovery:
- Check logs:
docker compose logs <service>. - Validate configuration:
docker compose config. - If the issue is clear (e.g., typo), fix the Compose file and run
docker compose up -dagain. - If unsure, rollback to the previous version (see rollback below).
Application Starts but Behaves Incorrectly
This could be due to changed default settings in the new Compose version or environment differences.
Recovery:
- Compare the resolved configuration old vs new:
docker compose configoutput. - Check application logs for errors.
- Consider restoring the previous version and re-testing.
Data Corruption During Volume Migration
If you used the tar pipe method and the volume got corrupted, the application may fail to read data.
Recovery:
- Stop the stack on the new host:
docker compose down. - Re-copy the volume from the old host (which should still be intact if you kept it).
- Restart and verify.
Network Issues After Migration
If external traffic cannot reach the new host, check firewall rules, security groups, and DNS. Also ensure ports are published correctly in Compose.
Recovery:
- Verify with
docker compose pswhich ports are mapped. - Check host firewall:
sudo ufw statusorsudo iptables -L. - Temporarily switch back to old host if possible.
How to Rollback Compose Upgrade
If the Compose binary itself is problematic, you can revert to the previous version. For plugin installations, you can install a specific version. For manual installations, replace the binary with the previous one from backup. After rollback, restart the stack.
How to Rollback Migration
If you migrated to a new host and problems arise, you can switch traffic back to the old host if it is still running. If the old host was decommissioned, you may need to restore from backups to a new server.
Recovery Verification
After any rollback or fix, run through the verification steps again to confirm the stack is healthy.
Constructed Example: A Failed Upgrade and Recovery
Imagine you upgraded Compose from v2.23 to v2.24 and your docker-compose.yml used a deprecated feature removed in v2.24. When you run docker compose up -d, you see an error:
Error: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
Actually, that is a warning, not an error, so the stack may still come up. But if you see a hard error like:
ERROR: The Compose file './docker-compose.yml' is invalid because:
services.web.ports contains an invalid type, it should be a string, or an array
You would fix the ports definition. If you cannot fix immediately, rollback Compose binary and restart.
Common Pitfalls and How to Avoid Them
Over the years, certain mistakes come up repeatedly. Knowing these in advance can save you from a production incident.
Forgetting to Backup Volumes
Many operators back up the Compose file but forget the persistent volumes. The result is data loss when something goes wrong.
Why it happens: Volumes are not visible in the project directory; they live in Docker's internal storage.
How to avoid: Always list volumes with docker volume ls and include them in your backup plan. Automate backups with a cron job if possible.
Not Testing in Staging
Skipping staging because "it's a minor upgrade" often leads to unexpected issues.
Why it happens: Time pressure and overconfidence.
How to avoid: Even a quick test on a VM can catch configuration errors. Make staging a mandatory step in your change process.
Ignoring Deprecation Warnings
Warnings like "attribute version is obsolete" are easy to ignore, but they may become errors in a future release.
Why it happens: Warnings do not break the current deployment, so they get postponed.
How to avoid: Treat warnings as action items. Schedule time to address them after the upgrade.
Not Having a Rollback Plan
Without a rollback plan, recovery becomes guesswork under stress.
Why it happens: People assume the upgrade will succeed.
How to avoid: Write down the exact steps to revert, including commands and file locations, before starting. Test the rollback in staging.
Migrating Data with the Wrong Method
Using tar pipes for large databases can cause corruption or prolonged downtime.
Why it happens: The tar method is simple but not designed for live databases.
How to avoid: For databases, use native tools like pg_dump or mysqldump. For large file volumes, consider rsync with a maintenance window.
Operations Checklist
Use this checklist before, during, and after any Compose upgrade or migration in production.
Pre-Change Checklist
- [ ] Inventory complete: versions, services, volumes, environment variables.
- [ ] Backup of project directory and volumes taken and verified.
- [ ] Staging or test environment ready and matching production.
- [ ] Upgrade path tested in staging with no issues.
- [ ] Rollback plan documented and accessible.
- [ ] Maintenance window scheduled if downtime is possible.
- [ ] Monitoring and alerting configured to catch failures quickly.
During Change Checklist
- [ ] Execute upgrade steps exactly as tested in staging.
- [ ] After each major step, verify service health (e.g.,
docker compose ps, healthchecks). - [ ] If any step fails, stop and assess; do not force through.
- [ ] Keep a log of commands run and their outputs.
Post-Change Checklist
- [ ] Run full verification: logs, endpoints, database checks, resource usage.
- [ ] Compare new resolved configuration with expected.
- [ ] Monitor for at least 24-48 hours for delayed failures.
- [ ] Update documentation with new version numbers and any config changes.
- [ ] Test rollback procedure on a small scale to ensure it works.
- [ ] Remove old backups only after satisfied with stability.
Example Verification Table
| Check | Command | Expected | Pass/Fail |
|---|---|---|---|
| Service status | docker compose ps | All running | |
| Logs no errors | docker compose logs --tail=100 | No stack traces | |
| HTTP endpoint | curl -I http://localhost | 200 OK | |
| Database query | docker compose exec db psql ... | Row count > 0 | |
| Resource usage | docker stats --no-stream | Within limits | |
| Healthcheck | docker inspect ... | healthy |
Fill in Pass/Fail as you go.
Conclusion
Upgrading and migrating Docker Compose in production does not have to be risky. By following a structured path - inventory, safe configuration, verification, and recovery planning - you can minimize downtime and be prepared for issues. The practical commands and tables in this guide serve as a template you can adapt to your own stack.
The key takeaways:
- Always inventory your current environment, including versions and volumes.
- Make changes incrementally and test them in a staging environment first.
- Use
docker compose configand healthchecks to validate. - Have a clear rollback plan before you start.
- Keep a simple checklist to ensure nothing is missed.
With these practices, your next Docker Compose upgrade or migration will be smoother and more predictable.