Intro
Upgrading Docker Compose is more than installing a new binary. A safe Docker Compose upgrade means moving from an observed problem to a verified result without losing data, breaking dependent services, or guessing at runtime behavior. This guide provides practical steps for Docker Compose upgrade, migration, rollback, version upgrade, and validation, with concrete commands and expected outputs.
This article is written for developers, DevOps consultants, and technical startup teams who manage containerized applications in development, staging, or production. It focuses on the operational workflow: checking prerequisites, capturing the current state, applying a minimal change, verifying the result, and knowing how to recover if something goes wrong.
The goal is operational safety. Observe before changing, limit the blast radius, use placeholders instead of secrets in configuration files, verify every step, and document recovery commands before you need them. Each section includes a practical example so you can follow along on your own systems.
Version and Environment Inventory
Before touching any running service, you need a clear inventory of the installed Docker Compose version, the active project state, and the deployment environment. This inventory helps you choose the right upgrade path and anticipate compatibility issues.
Check the current Docker Compose version
Run the following command to see the currently installed version:
docker compose version
Example output:
Docker Compose version v2.24.5
If you are using the older standalone docker-compose (v1), check with:
docker-compose version
Example output:
docker-compose version 1.29.2, build 5becea4c
Knowing whether you are on v1 or v2 matters because the command syntax differs. Docker Compose v2 is a plugin invoked as docker compose, while v1 is a separate binary. Most modern systems use v2, but older servers may still have v1. The upgrade process from v1 to v2 involves installing the plugin and possibly adjusting scripts that call docker-compose.
Inspect the running project state
List the current containers and their status:
docker compose ps
Example output:
NAME COMMAND SERVICE STATUS PORTS
web-1 "docker-entrypoint.s…" web running 0.0.0.0:8080->80/tcp
db-1 "docker-entrypoint.s…" db running 5432/tcp
This shows the service names, container names, and port mappings. Compare this with your docker-compose.yml to ensure the running state matches the intended configuration.
Check recent logs to detect any ongoing issues:
docker compose logs --tail 50 web
Look for error messages or warnings that might affect the upgrade.
Identify volumes and data storage
Data safety is critical during an upgrade. Determine where your application data is stored by inspecting the volumes:
docker volume ls
Example output:
DRIVER VOLUME NAME
local myapp_db_data
local myapp_uploads
Then inspect a volume to see its mountpoint and labels:
docker volume inspect myapp_db_data
Example output (partial):
[
{
"CreatedAt": "2024-03-01T12:00:00Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "myapp",
"com.docker.compose.volume": "db_data"
},
"Mountpoint": "/var/lib/docker/volumes/myapp_db_data/_data",
"Name": "myapp_db_data",
"Options": null,
"Scope": "local"
}
]
If your docker-compose.yml uses a bind mount, the path will appear under Mounts in docker inspect for the container:
docker inspect db-1 --format '{{ json .Mounts }}'
Example output:
[{"Type":"bind","Source":"/home/user/myapp/data","Destination":"/var/lib/postgresql/data","Mode":"rw","RW":true,"Propagation":"rprivate"}]
Make a note of all volumes and bind mounts. During an upgrade, containers may be recreated, and if data is stored on ephemeral container layers, it will be lost.
Verify data persistence with a restart test
Before any upgrade, perform a controlled restart test to confirm that data persists across container recreation. Stop and restart a service:
docker compose stop db
docker compose start db
Then check that the application can still read its data. For example, if it is a PostgreSQL database, run:
docker compose exec db psql -U myuser -c "SELECT count(*) FROM mytable;"
Expected output:
count
-------
123
(1 row)
If the count is zero or the table is missing, the data was likely stored in the container layer, and you need to correct the volume configuration before proceeding.
Safe Configuration Path
A safe configuration path means editing your docker-compose.yml with a clear understanding of what each change does and having a rollback plan. Avoid making multiple unrelated changes at once.
Backup the current configuration
Make a timestamped copy of your current docker-compose.yml and any related .env files:
cp docker-compose.yml docker-compose.yml.bak.$(date +%Y%m%d%H%M%S)
Example:
docker-compose.yml.bak.20250315120000
Also backup any .env file:
cp .env .env.bak.$(date +%Y%m%d%H%M%S)
This ensures you can restore the previous configuration quickly if needed.
Update the Compose file version key (if applicable)
For legacy Compose files, the top-level version key was used to specify the schema version. In Docker Compose v2, this key is obsolete but still accepted for backward compatibility. If you are migrating from v1 to v2, you can remove the version key or leave it; it will be ignored.
Example old-style file:
version: '3.8'
services:
web:
image: nginx:1.25
ports:
- "8080:80"
You can safely delete the version line. The new file would start directly with services:.
Validate configuration before applying
Use the config command to parse and validate your Compose file:
docker compose config
This prints the normalized configuration. If there are syntax errors, it will report them. Example of a successful output (truncated):
name: myapp
services:
web:
image: nginx:1.25
ports:
- mode: ingress
target: 80
published: "8080"
protocol: tcp
networks:
default:
name: myapp_default
Check for any warnings or unexpected changes in the output. Also run:
docker compose config --quiet
If this command exits with code 0 and no output, the configuration is valid.
Manage environment variables safely
Never hardcode secrets in docker-compose.yml. Use an .env file for environment variables. For example, in your Compose file:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
Create an .env file with the actual value (but never commit this file to version control):
DB_PASSWORD=change_me_strong_password
Validate that the variable is substituted correctly:
docker compose config | grep POSTGRES_PASSWORD
Expected output shows the actual value. To avoid exposing secrets in logs, consider using Docker secrets in production (Swarm) or external secret management.
Apply the change with a rolling update or recreate
For a simple change like updating an image tag, you can run:
docker compose up -d --no-deps web
This recreates only the web service without touching dependencies. If you need to force recreation:
docker compose up -d --force-recreate web
Observe the output:
[+] Running 1/1
✔ Container myapp-web-1 Recreated
Then verify the service is healthy:
docker compose ps
Example migration from v1 to v2
If you are on the old docker-compose binary, migrate to the v2 plugin. First, check if v2 is installed:
docker compose version
If not installed, follow the official Docker installation guide for your OS. On Ubuntu, install the docker-compose-plugin package:
sudo apt-get update
sudo apt-get install docker-compose-plugin
After installation, update any scripts that call docker-compose to use docker compose (space instead of hyphen). Test with your project:
cd /path/to/project
docker compose up -d
Verification and Diagnostics
After applying any configuration change or upgrade, verify that the application is functioning correctly. This involves checking service health, logs, and connectivity.
Check service status and health
Use docker compose ps to see the status:
docker compose ps
Example:
NAME COMMAND SERVICE STATUS PORTS
web-1 "docker-entrypoint.s…" web running (healthy) 0.0.0.0:8080->80/tcp
db-1 "docker-entrypoint.s…" db running (healthy) 5432/tcp
The (healthy) indicator appears if the container has a healthcheck defined. If your services lack healthchecks, add them to your Compose file for better observability. Example healthcheck for a web service:
services:
web:
image: nginx:1.25
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
After adding, recreate the service:
docker compose up -d web
Then check again.
Examine logs for errors
View logs for specific services:
docker compose logs --tail 100 web
Look for startup errors, failed connections, or configuration errors. For example, a database connection error might appear as:
web-1 | [error] 23#23: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 172.18.0.1, server: localhost, request: "GET / HTTP/1.1", upstream: "http://172.18.0.2:5432/"
This indicates the web service cannot reach the database, likely due to a network or service name change.
Test connectivity and service endpoints
For web applications, use curl from the host or inside another container. From the host:
curl -I http://localhost:8080
Expected output:
HTTP/1.1 200 OK
Server: nginx/1.25.3
Date: Sat, 15 Mar 2025 12:00:00 GMT
Content-Type: text/html
If you need to test from within the network, run a temporary container:
docker run --rm --network myapp_default curlimages/curl curl -s http://web:80
Replace myapp_default with your project's network name (check docker network ls).
Validate configuration against the running state
Compare the current configuration with the deployed state:
docker compose config --services
This lists all services defined. Then compare with docker compose ps --services to see which ones are running. Any discrepancy may indicate a problem.
Check resource usage and performance
Use docker stats to monitor resource consumption:
docker stats --no-stream
Example output (partial):
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
abc123def456 web-1 0.50% 25.5MiB / 1.95GiB 1.28% 1.2kB / 0B 0B / 0B 3
def456abc123 db-1 2.10% 102.4MiB / 1.95GiB 5.12% 850kB / 1.1MB 9.8MB / 12MB 27
Unexpected high CPU or memory usage may indicate a misconfigured service after upgrade.
Failure Modes and Recovery
Every upgrade should be planned with failure in mind. Here are common failure modes and specific recovery steps.
Configuration syntax error
If docker compose up fails with a YAML error, revert to the backup configuration.
Example error:
yaml: line 5: could not find expected ':'
Recovery: restore the backup file:
cp docker-compose.yml.bak.YYYYMMDDHHMMSS docker-compose.yml
Then validate again:
docker compose config --quiet
Incompatible image version
If the new image version fails to start due to missing dependencies or changed entrypoint, check logs, then rollback the image tag.
Example: you changed image: myapp:2.0 to myapp:3.0 and the container exits with error. Logs show:
web-1 | Error: Cannot find module 'express'
Recovery: change the image tag back to the previous version in docker-compose.yml, then run:
docker compose up -d --force-recreate web
Service dependency failure
If a dependent service fails to start because another service is unhealthy, check healthchecks and startup order. Compose v2 supports depends_on with conditions:
services:
web:
depends_on:
db:
condition: service_healthy
Ensure the db service passes its healthcheck. If not, inspect db logs and fix the underlying issue.
Volume or data loss
If after an upgrade the application reports missing data, check the volume mounts. Use docker inspect to see mount points:
docker inspect db-1 --format '{{ json .Mounts }}'
If the volume is missing or points to a different location, restore data from backups. Always take volume backups before major changes. Example backup for a PostgreSQL volume:
docker run --rm -v myapp_db_data:/data -v $(pwd):/backup alpine tar czf /backup/db_data_backup.tar.gz -C /data .
Restore:
docker run --rm -v myapp_db_data:/data -v $(pwd):/backup alpine sh -c "cd /data && tar xzf /backup/db_data_backup.tar.gz"
Rollback procedure
A general rollback procedure:
- Stop the affected services:
docker compose stop web db
- Restore the previous configuration from backup:
cp docker-compose.yml.bak.TIMESTAMP docker-compose.yml
- Recreate the services with the old configuration:
docker compose up -d --force-recreate
- Verify the application is healthy.
Operations Checklist
Use this checklist to ensure a safe Docker Compose upgrade or migration. Fill in the specific values for your environment.
| Step | Command / Action | Expected Result | Notes |
|---|---|---|---|
| 1. Record current version | docker compose version | Output shows version (e.g., v2.24.5) | Note v1 vs v2 |
| 2. Capture running state | docker compose ps --services | List of running services | Compare to config |
| 3. Backup config and env | cp docker-compose.yml docker-compose.yml.bak.$(date +%Y%m%d%H%M%S) | File created | Also backup .env |
| 4. Backup volumes (if any) | docker run --rm -v VOLUME:/data -v $(pwd):/backup alpine tar czf /backup/volume_backup.tar.gz -C /data . | Tar archive created | Replace VOLUME with actual volume name |
| 5. Validate config | docker compose config --quiet | Exit code 0, no output | Fix any errors |
| 6. Apply change | docker compose up -d or specific service | Services recreated | Use --no-deps to limit |
| 7. Check status | docker compose ps | All services running, healthy if healthchecks defined | Look for restarts |
| 8. Check logs | docker compose logs --tail 50 SERVICE | No fatal errors | Investigate warnings |
| 9. Test functionality | curl -I http://localhost:PORT or appropriate command | HTTP 200 or expected response | Check critical endpoints |
| 10. Monitor resources | docker stats --no-stream | CPU and memory within normal range | Watch for leaks |
| 11. Confirm data persistence | Restart test: docker compose stop then start | Data intact | If lost, fix volumes |
| 12. Document rollback plan | Write down restore commands | Clear steps saved | Test rollback in staging if possible |
Example checklist filled for a sample project
Assume a project named myapp with services web and db, using a named volume myapp_db_data, and upgrading image tag from web:1.0 to web:2.0.
- Current version:
docker compose versionreturnsDocker Compose version v2.24.5. - Running services:
docker compose ps --servicesreturnswebanddb. - Backup config:
cp docker-compose.yml docker-compose.yml.bak.20250315120000. - Backup volume:
docker run --rm -v myapp_db_data:/data -v $(pwd):/backup alpine tar czf /backup/myapp_db_data_backup.tar.gz -C /data . - Validate config:
docker compose config --quietreturns no error. - Apply change: edit image tag to
web:2.0, thendocker compose up -d web. - Check status:
docker compose psshowswebrunning and healthy. - Check logs:
docker compose logs --tail 50 webshows startup success. - Test functionality:
curl -I http://localhost:8080returnsHTTP/1.1 200 OK. - Monitor resources:
docker stats --no-streamshows web using 30MB RAM, normal. - Confirm data persistence:
docker compose stopthendocker compose start, thendocker compose exec db psql -U myuser -c "SELECT count(*) FROM mytable;"returns 123. - Document rollback:
cp docker-compose.yml.bak.20250315120000 docker-compose.yml && docker compose up -d --force-recreate.
Conclusion
Upgrading and migrating Docker Compose is a routine but risky operation. A systematic approach reduces downtime and data loss. By inventorying your environment, backing up configuration and data, validating changes, and verifying functionality after each step, you maintain control over the process.
Remember to version-scope every recommendation, observe before changing, and always have a rollback plan. Start with a low-risk verification on a staging environment or a non-critical service, record the current state, apply the change, and compare the result against the expected signal.
With these practices, Docker Compose upgrades become predictable and safe, even in production. Use the checklist as a living document that evolves with your infrastructure and team experience.
For further learning, explore Docker's official documentation on Compose file reference, networking, and volumes, and consider automating these steps with CI/CD pipelines in your development workflow.