E-NO
Docker Containers upgrade 10 Min Read

Docker Container Upgrades and Migration: A Practical Guide with Commands and Rollback Plans

calendar_today Published: 2026-10-25
update Last Updated: 2026-10-25
analytics SEO Efficiency: 100%
Technical guide illustration for Docker Container Upgrades and Migration: A Practical Guide with Commands and Rollback Plans.

Intro

Upgrading or migrating Docker containers is not just about pulling a newer image and restarting the container. Done wrong, it can cause data loss, configuration drift, silent compatibility failures, or extended downtime. Done right, it is a controlled sequence of observation, scoped change, verification, and recovery.

This guide walks through a practical upgrade and migration workflow for Docker containers, aimed at developers, DevOps engineers, and technical startup teams. We cover version inventory, safe configuration handling, pre-flight checks, migration steps with concrete commands, verification, rollback, and common pitfalls. Each stage includes executable commands, expected outputs, failure signals, and recovery actions so you can apply the process directly to your environment.

The core principle is operational safety: observe before changing, limit the blast radius, keep secrets out of the command line, verify at every step, and always have a tested rollback path.

Version and Environment Inventory

Start by understanding exactly what you are running. This is a read-only activity; do not change anything yet.

Check Docker and Compose versions

docker version --format '{{.Server.Version}}'
docker compose version

Example output:

24.0.7
Docker Compose version v2.21.0

If you are using an older Docker Engine or Compose file format, upgrading the container may require upgrading the host tools first. For example, Compose file version 3.x requires Docker Engine 1.13.0+; some newer syntax (like depends_on with conditions) requires Compose v2.20+.

List running containers and images

docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
docker images

Note which containers are managed by Compose vs. standalone. For Compose projects, always use docker compose commands to ensure the desired state is applied consistently.

Inspect critical configuration

docker inspect <container> --format '{{json .Mounts}}' | jq
docker inspect <container> --format '{{json .Config.Env}}' | jq
docker inspect <container> --format '{{json .Config.Labels}}' | jq

Replace <container> with the actual name or ID. This shows volumes, bind mounts, environment variables, and labels. Pay special attention to:

  • Volumes vs. bind mounts: named volumes (e.g., app_data:/var/lib/app) are managed by Docker and easier to backup/restore; bind mounts (e.g., ./data:/var/lib/app) map host paths and can cause portability or permission issues on other hosts.
  • Environment variables: do not expose secrets in the command. Use an env file or a secret manager.
  • Healthcheck: if the image defines a healthcheck, note it; we will use it for verification later.

Know where data lives

Before any upgrade or migration, verify that your application data is persistent and not written into the container's writable layer. A quick test:

docker stop <container>
docker rm <container>
docker run -d --name <container> [same options] <image>
docker exec <container> ls /var/lib/app

If files appear, data is persistent. If the directory is empty, the application was writing to the container filesystem and will lose data on every recreate. Fix that by adding a volume before proceeding.

Safe Configuration Path

Treat configuration as code. Keep configuration in files under version control, inject it via environment variables or mounted config files, and never hardcode secrets.

Extract current configuration

For an existing container, export its configuration for review:

docker inspect <container> --format '{{json .Config}}' | jq > container-config.json

Review the JSON. Note healthcheck, entrypoint, command, environment, and volumes. For Compose services, the docker-compose.yml file is the source of truth; ensure it matches reality.

Prepare new configuration

Create or update configuration files in a safe directory. Example docker-compose.yml for a web app:

services:
  web:
    image: nginx:1.25
    ports:
      - "8080:80"
    environment:
      - NGINX_HOST=example.com
      - NGINX_PORT=80
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - app_data:/usr/share/nginx/html
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
volumes:
  app_data:

Notice the healthcheck; this will be used for verification.

Secrets handling

Never pass secrets via -e on the command line or commit them to files. Use Docker secrets (for Swarm), a .env file not under version control, or a secret manager. For example, with a .env file:

echo "DB_PASSWORD=$(openssl rand -hex 16)" >> .env

Then reference it in Compose:

environment:
  DB_PASSWORD: ${DB_PASSWORD}

Ensure .env is in .gitignore.

Validate configuration

For Compose files:

docker compose config

This parses and validates the file, merges overrides, and shows the final configuration. Fix any warnings before proceeding.

Quick check 1 of 2

According to the experimental automatic migration feature, what happens to your overlay2 data after the daemon switches to the containerd image store?

Passage [1] states that after the daemon switches to the containerd image store, your overlay2 data remains on disk but becomes hidden.

Migration and Upgrade Steps

Now we can proceed with the actual upgrade or migration. We will assume a typical scenario: upgrading an application container from version 1.0 to 2.0, possibly with a configuration change, and moving from one host to another. The steps are similar for both.

1. Pull the new image

docker pull myapp:2.0

Check the digest to ensure you have the correct image:

docker images --digests

2. Test the new image in isolation

Run the new image on a different port or with a temporary container name to avoid disrupting the running service.

docker run -d --name myapp-test -p 8081:80 --env-file .env myapp:2.0

Point some test traffic at it or run smoke tests manually. Check logs:

docker logs myapp-test --tail 50

If the application has database migrations, run them against a copy of the database first.

3. Back up data and configuration

Before switching traffic, back up volumes and current config.

For a named volume:

docker run --rm -v app_data:/data -v $(pwd):/backup alpine tar czf /backup/app_data_backup_$(date +%Y%m%d_%H%M%S).tar.gz -C /data .

For bind mounts, simply cp -a the directory.

Also save the current container's config:

docker inspect myapp > myapp_current_config_$(date +%Y%m%d).json

4. Update the service

If using Compose, modify the image: tag in docker-compose.yml, then run:

docker compose up -d --no-deps myapp

The --no-deps flag avoids recreating dependent containers unnecessarily.

If standalone, stop, remove, and recreate:

docker stop myapp
docker rm myapp
docker run -d --name myapp [same options but new image] myapp:2.0

5. Verify the new version

Check that the container is running and healthy:

docker ps --filter name=myapp

Look for the health status column becoming healthy. If your image has no healthcheck, use the previously noted healthcheck or check logs and application endpoints manually.

For Compose:

docker compose ps

Check application logs:

docker logs myapp --tail 50

Watch for any new errors or warnings.

6. Migration scenario: moving to a new host

If you are migrating to a new host, after performing the backup, transfer the backup and configuration to the new host. Options include scp, rsync, or object storage.

On the new host, ensure Docker and Compose versions are compatible. Restore the volume data:

docker volume create app_data
docker run --rm -v app_data:/data -v $(pwd):/backup alpine sh -c "cd /data && tar xzf /backup/app_data_backup_*.tar.gz"

Then start the service with the updated configuration. Test thoroughly before decommissioning the old host.

Verification and Diagnostics

After the upgrade or migration, verify not just that the container is running, but that the application is functioning correctly.

Healthcheck endpoints

If your application exposes a health endpoint, use curl or a script:

curl -f http://localhost:8080/health

Expected output: OK or similar.

Check container resource usage

docker stats --no-stream myapp

Compare CPU and memory usage with the previous version to detect regressions.

Inspect logs for errors

docker logs myapp --since 30m | grep -i error

No output means no errors in the last 30 minutes.

Run automated smoke tests

If you have integration tests or scripts, run them against the new service. For example, a simple curl loop:

for i in {1..5}; do curl -s http://localhost:8080/api/items | jq .; sleep 2; done

Check that responses are as expected.

Compare configuration

Use docker inspect and diff against the saved JSON to ensure no unintended changes:

diff <(docker inspect myapp --format '{{json .Config.Env}}' | jq -S .) <(jq -S '.[0].Config.Env' myapp_current_config.json)

Adjust the jq expression based on the saved file structure.

Failure Modes and Recovery

Even with careful planning, failures happen. Here are common failure modes, how to detect them, and recovery actions.

Image pull fails

Symptom: docker pull returns authentication or network errors.

Recovery: Check registry credentials (docker login), network connectivity, DNS, and available disk space (df -h). If the image is not available, restore from backup or use the previous version.

Container fails to start

Symptom: docker run or docker compose up exits with a non-zero code.

Diagnosis: Check docker logs for application errors. Verify that required volumes or environment variables are present. Compare with the previous container's configuration.

Recovery: If the issue is configuration, fix and re-run. If the new image is broken, roll back to the previous image (see below).

Application starts but behaves incorrectly

Symptom: Healthcheck fails, endpoints return errors, or data is missing.

Diagnosis: Check logs, database connectivity, and volume mounts. If data is missing, verify that volumes are correctly attached and not empty.

Recovery: Roll back to the previous version, then investigate.

Data loss or corruption

Symptom: Empty directories, database errors, or missing files.

Prevention: Always back up before making changes.

Recovery: Restore from backup as soon as possible. To avoid future issues, use named volumes or bind mounts and test persistence before deployment.

Rollback procedure

Always have a rollback plan. For Compose:

docker compose down
git checkout <previous-commit>   # if config is in version control
docker compose up -d

If using a previous image tag, change the tag back and re-run up.

For standalone containers:

docker stop myapp
docker rm myapp
docker run -d --name myapp [same options as original] myapp:1.0

Restore data from backup if necessary.

Quick check 2 of 2

What environment variable can be set to make the daemon automatically switch to the containerd image store when there are no containers and image layers are below a size threshold?

Passage [1] describes setting the DOCKER_MIGRATE_SNAPSHOTTER_THRESHOLD environment variable to trigger automatic switching based on total image layer size.

Common Pitfalls

Here are pitfalls that often cause failed upgrades or migrations, with why they happen and how to avoid them.

1. Skipping the backup

Why it happens: Overconfidence or time pressure. How to avoid: Make backup a mandatory step in your runbook. Automate it if possible.

2. Changing too many things at once

Why it happens: Combining image upgrade, config changes, and environment moves in one big step. How to avoid: Split changes into separate, independently verifiable steps. For example, first migrate with the same version, then upgrade the image.

3. Not testing data persistence

Why it happens: Assuming the application is configured to use volumes without checking. How to avoid: Perform the restart test described in the inventory section. Ensure data survives container recreation.

4. Hardcoding secrets in configuration or commands

Why it happens: Convenience or lack of awareness. How to avoid: Use env files, Docker secrets, or a secret manager. Never commit secrets to version control.

5. Ignoring healthcheck status

Why it happens: Relying only on container "running" state. How to avoid: Define a robust healthcheck for your application and monitor it during and after upgrade.

6. Not having a rollback plan

Why it happens: Optimism bias or lack of preparation. How to avoid: Write down the exact commands to roll back before starting the upgrade. Test the rollback in a staging environment.

Operations Checklist

Use this checklist before, during, and after any Docker container upgrade or migration. Assign each item a single owner (e.g., a specific engineer) and review the checklist at the beginning and end of the operation. For routine upgrades, review it monthly to ensure it remains current.

StepActionOwner (example)Verification
Pre-flightCheck Docker/Compose versions and prerequisitesPriya Shah, DevOps Leaddocker version, docker compose version output matches requirements
InventoryList containers, images, mounts, and environmentAlex Chen, Ops Engineerdocker ps, docker inspect records saved
ConfigExtract and review current configurationMaria Gomez, App DeveloperJSON/Compose file reviewed and backed up
BackupBack up volumes and configurationAlex Chen, Ops EngineerBackup files exist and are readable
TestRun new image in isolated containerMaria Gomez, App DeveloperSmoke tests pass, logs clean
ChangeUpdate service to new image with config changesPriya Shah, DevOps Leaddocker compose up -d succeeds
VerifyCheck container health, logs, endpointsAlex Chen, Ops EngineerHealthcheck healthy, endpoints return expected data
Rollback (if needed)Execute documented rollback procedurePriya Shah, DevOps LeadService restored to previous version, verified
Post-op reviewDocument what happened, lessons learnedAll ownersReview meeting minutes, updated runbook

Review frequency: For production services, review this checklist with the team monthly or before each major upgrade. For quarterly or ad-hoc migrations, review at the start of the migration project.

Conclusion

Upgrading and migrating Docker containers safely is a disciplined process that combines preparation, execution, and verification. By following the steps outlined here - inventorying your environment, managing configuration carefully, backing up data, testing before cutover, verifying after change, and having a rollback plan - you can avoid common pitfalls and keep your services reliable.

Remember: every command you run should have a known expected result and a recovery path if the result is different. Treat your container infrastructure as cattle, not pets, but keep the data and configuration as precious. With the right practices, upgrades and migrations become routine operations rather than risky events.

As a next step, pick one low-risk container in your environment and go through the inventory and backup steps. Then create a plan for upgrading it using the checklist. Build confidence in the process before tackling critical workloads.

Related Research

Article Quality Score

Reader usefulness 100%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL