Using Docker Context to Manage Remote Hosts
A Docker context is a saved connection profile that points your local docker CLI at a remote daemon, usually over SSH. Set up a Docker context remote host connection once and docker ps runs against that machine instead of your laptop, no shell login required. You switch hosts with a single command and every image, container, and volume you list belongs to that remote machine.
This is the cleanest way to manage a Docker host from your workstation. You keep your local tooling, your aliases, and your muscle memory, and the daemon on the other end does the work.
What a Docker context actually is
A context bundles three things: the endpoint (where the daemon lives), the connection method (SSH, TCP, or a local socket), and any TLS material if you use encrypted TCP. Docker ships with one context out of the box called default, which talks to the local daemon at /var/run/docker.sock.
When you run a command, the CLI reads the active context and forwards the request to that endpoint. Nothing about the command changes. docker logs -f api behaves identically whether the daemon is on your machine or a box in Frankfurt.
You can see what you have right now:
docker context ls
The active context is marked with an asterisk.
Creating a Docker context remote host connection over SSH
SSH is the method you want almost every time. It needs no daemon reconfiguration, no open TCP ports, and no certificate juggling. If you can ssh into the box, you can point Docker at it.
docker context create prod \
--docker "host=ssh://deploy@203.0.113.10"
That creates a context named prod pointing at the deploy user on 203.0.113.10. Switch to it:
docker context use prod
Now test it:
docker ps
You are looking at the remote host's containers. To go back to your machine:
docker context use default
A few requirements that trip people up. The SSH connection has to be non-interactive, so key-based auth is mandatory. Password prompts will break the CLI. The remote user also needs permission to reach the Docker socket, which usually means it belongs to the docker group:
sudo usermod -aG docker deploy
Log out and back in for the group change to take effect. And you need a reasonably recent SSH client locally, since Docker relies on connection multiplexing under the hood.
Use your SSH config, not long connection strings
If your host runs on a non-standard port or behind a jump host, do not cram that into the context string. Put it in ~/.ssh/config where it belongs:
Host prod-frankfurt
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/prod_ed25519
ProxyJump bastion
Then the context stays short and readable:
docker context create prod --docker "host=ssh://prod-frankfurt"
Docker hands the connection to your SSH client, so anything SSH understands, the context inherits. Jump hosts, custom ports, specific keys, connection timeouts. This keeps one source of truth for how you reach a machine. If the setup itself is new to you, the complete guide to remote Docker management over SSH walks through the connection layer in more depth.
Switching between multiple hosts
Most people who use contexts have more than one. Create them all up front:
docker context create staging --docker "host=ssh://deploy@staging.internal"
docker context create prod-eu --docker "host=ssh://deploy@eu.example.com"
docker context create prod-us --docker "host=ssh://deploy@us.example.com"
Switch with docker context use <name>, or run a one-off command against a context without switching at all:
docker --context prod-us ps
The --context flag is the safer habit. It leaves your default untouched, which matters a lot when you have a production and a staging host that look almost identical in the terminal.
There is also an environment variable if you want to scope a context to a single shell session:
export DOCKER_CONTEXT=staging
Every docker command in that terminal now targets staging until you close it or unset the variable.
The mistake that will bite you
Context switches are silent. Your prompt does not change. Run docker context use prod, get distracted for an hour, come back, and docker compose down now stops production instead of the local stack you thought you were in.
Before any destructive command, check where you are:
docker context show
Better, add the active context to your shell prompt so it is always visible. A short function in your .zshrc or .bashrc reading docker context show into the prompt is enough. Slower, since it shells out on every prompt render, but the safety is worth it on machines where you touch production.
This is also where a second pair of eyes helps. Docker HQ shows every host you manage as its own labeled entry, so switching from staging to prod is a deliberate tap on a named server rather than an invisible CLI state change. When a container on any of those hosts exits or a host stops responding, you get a push alert instead of finding out from a customer.
Context vs SSH tunnel vs exposed TCP
Three ways exist to reach a remote daemon, and context over SSH is the default choice for good reason.
Raw TCP (host=tcp://...) exposes the Docker daemon on a network port. An unprotected Docker socket over TCP is root on the host for anyone who can reach it, so this demands mutual TLS and a locked-down firewall. It is more moving parts and more ways to get breached. Skip it unless you have a specific reason.
A manual SSH tunnel with port forwarding works, but you are managing the tunnel lifecycle yourself. Context over SSH does the same thing and cleans up after itself.
So for a single admin or a small team, SSH contexts win on setup cost and safety. TCP with TLS only earns its place in automated pipelines where SSH agent forwarding is awkward.
Contexts and Docker Compose
Compose respects the active context too. From your laptop:
docker --context prod-eu compose -f docker-compose.yml up -d
That deploys the stack to the remote host. The compose file, the build context, and the .env all read from your local directory, while the containers run remotely. Bind mounts are the one gotcha. A path in a bind mount refers to the remote filesystem, not yours, so a ./config:/etc/app mount looks for ./config on the server. Named volumes sidestep this entirely and are the right call for remote deploys.
Frequently asked questions
Does Docker context work over SSH without exposing any ports?
Yes. That is the main appeal. An SSH context tunnels the Docker API over your existing SSH connection, so the remote daemon never needs a public TCP port. As long as port 22 (or your custom SSH port) is reachable and you have key-based auth, nothing else has to open up.
Why do I get a permission denied error when using a remote context?
Almost always the remote user cannot reach the Docker socket. Add the user to the docker group with sudo usermod -aG docker <user>, then reconnect so the group membership applies. If you still see it, confirm the Docker daemon is actually running on the host with sudo systemctl status docker.
Can I manage remote Docker hosts from my phone?
The CLI needs a terminal, so pure docker context is a laptop tool. For phone access you want an app that speaks to the daemon over SSH for you. That is exactly what Docker HQ does for managing a VPS from your phone, and if you just need shell access, here is how to SSH into a server from your phone.
How do I remove a context I no longer use?
Run docker context rm <name>. If it is the active context, switch away first with docker context use default, otherwise Docker refuses to delete it. Removing a context only deletes the local connection profile. It never touches anything on the remote host.
Where to go from here
Set up one SSH context for a non-critical host and get comfortable with docker --context <name> ps before you ever switch your default. The --context flag habit is cheap insurance, and it is the single thing that separates people who never fat-finger a production compose down from people who have a story about the time they did.