Intro
Docker contexts let you switch between different Docker hosts, such as a local engine, a remote server, or a cloud provider, without reconfiguring your CLI every time. They store connection details like the host URL, TLS settings, and credentials in a simple named profile. Upgrading or migrating these contexts becomes necessary when you move to new infrastructure, rotate certificates, or adopt a newer Docker version with different context behavior.
This guide is written for developers, DevOps engineers, and technical startup teams who manage Docker in production or shared environments. It covers the full lifecycle of a context change: inventorying your current setup, planning a safe configuration path, validating the new context, handling failures, and following a repeatable checklist. You will find real commands, expected output, and recovery steps so you can adapt the examples to your own hosts.
The core principle is operational safety: observe before changing, limit the blast radius, use placeholders instead of secrets, verify every result, and document how to recover if the expected state is not reached. Context upgrades often fail not because of Docker itself but because of unnoticed assumptions about file paths, environment variables, or certificate expiry. This article helps you surface those assumptions early.
Version and Environment Inventory
Before touching any context, you need a complete inventory of what you have. Run these read-only commands and record the output. Do not skip this step; many migration failures come from starting with an incomplete picture.
Check your Docker CLI and engine versions:
docker version
Expected output includes a Client section and a Server section. For example:
Client: Docker Engine - Community
Version: 27.3.1
API version: 1.47
Go version: go1.21.13
Git commit: ce12230
Built: Fri Sep 20 11:41:00 2024
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 27.3.1
API version: 1.47 (minimum version 1.24)
Go version: go1.21.13
Git commit: 41ca978
Built: Fri Sep 20 11:41:00 2024
OS/Arch: linux/amd64
Experimental: false
Note the client and server versions. Docker contexts are stored client-side, so the client version matters most for context features. If your client is older than your remote engine, some context fields may not be interpreted correctly.
List all existing contexts:
docker context ls
Example output:
NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
default * moby Current DOCKER_HOST based configuration unix:///var/run/docker.sock swarm
remote-prod ssh Production server via SSH ssh://[email protected]
desktop-linux moby Docker Desktop unix:///home/user/.docker/desktop/docker.sock
The asterisk marks the active context. Any commands you run now will target that context. If you are about to migrate, you likely want to switch to a different context or create a new one, but first capture the details of the current one.
Inspect the active context in depth:
docker context inspect
This prints the full configuration as JSON, including TLS material paths and host settings. Here is a trimmed example:
[
{
"Name": "remote-prod",
"Metadata": {
"Description": "Production server via SSH"
},
"Endpoints": {
"docker": {
"Host": "ssh://[email protected]",
"SkipTLSVerify": false
}
},
"TLSMaterial": {},
"Storage": {
"MetadataPath": "/home/user/.docker/contexts/meta/remote-prod/meta.json",
"TLSPath": "/home/user/.docker/contexts/tls/remote-prod"
}
}
]
Pay attention to the storage paths. Contexts are stored as files on disk, typically under ~/.docker/contexts/. The meta subdirectory holds the context definition, and the tls subdirectory holds certificates and keys. When you upgrade or migrate, you may need to move or copy these files to a new machine.
Check whether any containers or services are currently running on the active context:
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
For a production host, you might see:
NAMES STATUS PORTS
web-1 Up 3 hours 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
db-1 Up 3 hours 5432/tcp
cache-1 Up 3 hours 6379/tcp
Record these names and statuses. If something breaks during migration, you can compare against this baseline to see what changed.
If you use Docker Compose, also run:
docker compose ps
And to see recent logs without changing anything:
docker compose logs --tail=100
Now check where your data lives. Persistent data should be in named volumes or bind mounts, not inside the container filesystem. List volumes:
docker volume ls
And inspect a specific volume used by a container:
docker inspect db-1 --format '{{range .Mounts}}{{.Name}} -> {{.Destination}}{{println}}{{end}}'
Example output:
db_data -> /var/lib/postgresql/data
That means the db_data volume is mounted at the expected location. If instead you see no mount and data is written directly to the container layer, you will lose that data when the container is recreated. Fix that before any migration.
Finally, run a restart test on a non-critical container to confirm data persistence:
docker stop cache-1
docker start cache-1
docker logs cache-1 --tail=20
If the application comes back and can read its data, the storage is configured correctly. If it fails, you have found a problem before the migration, not after.
Safe Configuration Path
The safe path for a context upgrade or migration is to make changes on a copy, test thoroughly, and only then switch the active context. Never modify the only working context in place without a fallback.
Step 1: Export the current context
You can export a context to a file for backup or transfer. The command is:
docker context export remote-prod
This creates a file named remote-prod.dockercontext in your current directory. It contains the context metadata and TLS material. Store this file somewhere safe, such as a versioned backup location.
To import it on another machine or after a reset:
docker context import remote-prod remote-prod.dockercontext
But be careful: importing with the same name will overwrite the existing context. Use a new name for testing:
docker context import remote-prod-test remote-prod.dockercontext
Now you have a copy to experiment with.
Step 2: Create a new context targeting the upgraded host
Suppose you are moving from an old server to a new one with a different hostname or Docker version. Create a fresh context:
docker context create new-prod --docker "host=ssh://[email protected]"
Or if you use TLS with certificates:
docker context create new-prod-tls \
--docker "host=tcp://new-prod.example.com:2376,ca=/path/to/ca.pem,cert=/path/to/cert.pem,key=/path/to/key.pem"
The context stores these values. They are not encrypted at rest, so ensure file permissions are restricted (e.g., chmod 600 on the key file).
Step 3: Test the new context without switching globally
You can use a context for a single command with the --context flag:
docker --context new-prod ps
This queries the new host but leaves your default context unchanged. Verify that you can see containers:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
If it is empty, that is fine if it is a fresh host. Then run a harmless command to test connectivity and permissions:
docker --context new-prod info --format '{{.ServerVersion}}'
Expected output like 27.3.1. If you get a TLS error, check your certificate paths and hostname match.
Step 4: Validate Docker Compose on the new context
If you use Compose, point it at the new context:
docker --context new-prod compose -f docker-compose.yml config
This parses and validates the Compose file without starting anything. It will show the merged configuration. If your Compose file uses environment variables that are only on your local machine, you will see errors here. Fix them before proceeding.
Step 5: Switch the active context
Once you are confident, switch your shell to the new context:
docker context use new-prod
Then verify:
docker context ls
The asterisk should now be next to new-prod. Run docker ps to see the containers on the new host.
Step 6: Keep the old context as a fallback
Do not delete the old context immediately. Keep it for at least one full deployment cycle or a defined rollback window. If something goes wrong on the new host, you can switch back instantly:
docker context use remote-prod
Only remove the old context after you have confirmed the new one works for all use cases and you have updated any scripts or CI pipelines that referenced the old context name.
Verification and Diagnostics
After switching contexts, you need to verify that the environment is fully functional. Rely on objective signals, not just "it feels okay."
Run the Docker health check
docker info
Look for warnings or errors. A healthy output includes:
Server Version: 27.3.1
Storage Driver: overlay2
...
WARNING: No memory limit support
Warnings are not necessarily fatal, but note them. If you see ERROR: Cannot connect to the Docker daemon, your context is misconfigured.
Check container status on the new host
docker ps -a
Compare the list to your baseline inventory. If containers are missing, ensure your images are available on the new host. You may need to pull them or push them from a registry.
Check logs for application errors
docker logs web-1 --tail=100
Look for stack traces, database connection failures, or certificate errors. These often surface only after migration because the new host has different network settings or DNS.
Verify network connectivity
If your application relies on service discovery or internal DNS, test from inside a container:
docker exec web-1 curl -I http://db-1:5432
The exact command depends on your application, but the principle is to reproduce the communication path that matters. For a web app talking to a database, a simple TCP check can be:
docker exec web-1 sh -c 'nc -zv db-1 5432'
Expected output: Connection to db-1 5432 port [tcp/postgresql] succeeded!
If it fails, check your Docker network configuration on the new host. You may need to recreate networks or adjust firewall rules.
Validate file permissions and volumes
On a new host, user IDs and file permissions often differ. Check that your containers can write to their volumes:
docker run --rm -v app_data:/data alpine sh -c 'touch /data/test && ls /data'
If the touch fails with a permission error, the volume was created with a different ownership on the old host. You may need to fix ownership inside the container or use a named volume with a defined user.
Review context configuration for secrets
Run docker context inspect again and confirm no secrets or wrong paths are present. In particular, ensure the TLS key path is correct and the certificate has not expired:
openssl x509 -enddate -noout -in /path/to/cert.pem
This prints the certificate expiry date. If it is near or past, renew it before relying on the context.
Failure Modes and Recovery
Even with careful planning, things can go wrong. Here are the most common failure modes and how to recover from each.
Failure 1: Certificate expired or hostname mismatch
Symptom: docker ps returns x509: certificate has expired or is not yet valid or cannot validate certificate for 10.0.0.5 because it doesn't contain any IP SANs.
Cause: The TLS certificate used by the Docker daemon has expired or was generated for a different hostname than the one you are connecting to.
Recovery: Regenerate the certificate on the Docker host, or update the context to point to the correct hostname that matches the certificate. If you used a self-signed certificate, you can also add skipTLSVerify: true to the context for testing, but never use that in production. Better to fix the certificate.
Failure 2: Context file corruption or missing TLS material
Symptom: docker context use my-context works, but docker ps fails with failed to load TLS files: open /home/user/.docker/contexts/tls/my-context/ca.pem: no such file or directory.
Cause: The context metadata references TLS files that were deleted or not moved during migration.
Recovery: Re-import the context from your exported backup file, which includes the TLS material. If you do not have a backup, you must recreate the context manually with the correct certificate paths.
Failure 3: Docker Desktop context conflicts
Symptom: After upgrading Docker Desktop, your custom contexts disappear or the default context changes to desktop-linux unexpectedly.
Cause: Docker Desktop manages its own contexts and may overwrite the default context during upgrades.
Recovery: Recreate your custom contexts from exports or by using docker context create. To prevent this, always use specific context names for remote hosts and avoid relying on the default context for production.
Failure 4: Permissions issues on new host
Symptom: Containers start but applications cannot write to mounted volumes, logging errors like Permission denied.
Cause: The user ID inside the container does not have write permission to the host directory mounted as a volume.
Recovery: Adjust the ownership of the volume content on the host, or run the container with a matching user. For example, if the container runs as UID 1000, ensure the volume directory is owned by UID 1000: sudo chown -R 1000:1000 /path/to/volume.
Failure 5: Network connectivity issues after migration
Symptom: Containers cannot reach each other by name, or external network access fails.
Cause: The new host has different firewall rules, subnet conflicts, or Docker network configurations.
Recovery: Compare docker network ls on the old and new hosts. Recreate custom networks with the same settings. Check host firewall rules (e.g., iptables -L) and ensure Docker's rules are present. If using cloud infrastructure, verify security group or network ACL settings.
Common Pitfalls and How to Avoid Them
Many context upgrades fail due to avoidable mistakes. Here are the most frequent pitfalls and how to steer clear.
Pitfall 1: Not exporting contexts before making changes
What happens: You modify or delete a context, then realize you needed the old configuration to roll back.
Why it happens: Contexts feel like simple configuration, so people skip the backup step.
How to avoid: Always run docker context export <name> before any change. Store the file in version control or a secure backup location. Make this a habit.
Pitfall 2: Using the same context name for old and new hosts
What happens: You create a new context with the same name as the old one, overwriting it. When the new host fails, you have no old context to switch back to.
Why it happens: You want a clean name, so you reuse the old one.
How to avoid: Use distinct names with a version or date, e.g., prod-old-20250101 and prod-new-20250115. Only rename or delete after the new one is proven.
Pitfall 3: Ignoring client-server version differences
What happens: You upgrade your Docker CLI but the remote engine is older. Some context features or flags may not work, causing confusing errors.
Why it happens: You assume the client and server versions are always in sync, but remote hosts often lag.
How to avoid: Run docker version against each context and compare. If there is a major version difference, consider upgrading the engine or pinning your CLI to a compatible version. Document the supported version range for your team.
Pitfall 4: Hardcoding context names in scripts and CI
What happens: After migration, your CI pipeline still references the old context name, so deployments fail silently or go to the wrong host.
Why it happens: Context names are embedded in shell scripts and CI configuration, and no one greps for them during migration.
How to avoid: Before switching, search your codebase and CI configs for the old context name. Use a configuration variable instead of a hardcoded string where possible. After switching, update all references in the same change set and run a test deployment.
Pitfall 5: Keeping secrets in context files without protection
What happens: You create a context with TLS key paths or environment variables containing secrets, then copy the context to another machine, and the secrets end up in a public repository or shared filesystem.
Why it happens: Context files are not encrypted, and people forget they contain sensitive material.
How to avoid: Treat context files as secrets. Restrict file permissions, never commit them to version control, and use secrets management (e.g., environment variables or a vault) for anything that is truly sensitive. Prefer SSH contexts over TLS when possible to avoid certificate files.
Operations Checklist
Use this checklist before, during, and after any context upgrade or migration. Assign each item to a single accountable owner (not a group) and set a review cadence.
Before Migration
- [ ] Owner: DevOps Engineer (e.g., Priya Shah) – Run
docker versionanddocker context lson the current setup, and record output in the migration document. Review frequency: once per migration. - [ ] Owner: Lead Developer (e.g., Alex Chen) – Identify all applications and services that use the current context, including scripts and CI pipelines. Review frequency: once per migration.
- [ ] Owner: DevOps Engineer – Export all existing contexts using
docker context exportand store the files in a secure, versioned location. Review frequency: once per migration. - [ ] Owner: Security Engineer (e.g., Jordan Lee) – Verify that no secrets are stored in plaintext in context files or environment variables. Replace with secure references. Review frequency: before every migration or quarterly.
- [ ] Owner: DevOps Engineer – Create the new context on a test machine or using a new name, and verify connectivity with
docker --context new-name ps. Review frequency: once per migration.
During Migration
- [ ] Owner: DevOps Engineer – Switch the active context using
docker context use new-nameand immediately rundocker psanddocker infoto confirm the new host is reachable. Review frequency: at the moment of cutover. - [ ] Owner: Application Owner (e.g., Maria Garcia) – Run smoke tests on the new host, including a restart test for stateful services. Review frequency: immediately after cutover.
- [ ] Owner: DevOps Engineer – Keep the old context available and do not delete it until the rollback window closes (e.g., 7 days). Review frequency: after cutover, daily check until window closes.
After Migration
- [ ] Owner: DevOps Engineer – Update all CI/CD pipelines and scripts to reference the new context name. Review frequency: within 24 hours of cutover.
- [ ] Owner: Lead Developer – Run a full deployment pipeline against the new context and confirm success. Review frequency: within 48 hours of cutover.
- [ ] Owner: DevOps Engineer – After the rollback window expires, remove the old context and update documentation. Review frequency: after rollback window (e.g., day 8).
- [ ] Owner: Security Engineer – Rotate any credentials or certificates that were used during migration. Review frequency: immediately after migration, then every 90 days.
Conclusion
Docker context upgrade and migration is a manageably small task when you treat it as a controlled operation rather than a quick command. The key is to observe first, change one scoped thing at a time, verify with concrete commands and expected output, and always keep a rollback path.
You now have a complete workflow: inventory your environment, create and test a new context on a copy, switch only after validation, monitor for common failures, and follow a checklist with explicit ownership and review timelines. The next step is to apply this to your own setup. Start with the inventory commands in the first section. Record what you see. Then export a context as a backup. Only after that should you make any changes.
A reliable technical workflow makes failure visible, protects sensitive values, limits changes to the intended resource, and defines recovery verification before an incident forces a decision. With Docker contexts, that means every migration should end with a verified new environment, a documented rollback, and no lingering doubts about where your containers are actually running.