55.10 Docker contexts: control remote Docker over SSH
Friends, until now we logged in to each server with SSH and typed docker there. A Docker context lets the docker command on your laptop talk to Docker on a remote server, over plain SSH, safely. Today, step by step: create a context, switch between laptop, staging and production, deploy a Compose app remotely, use it from CI, and the safety rules so you never run a command on production by mistake.
मित्रांनो, आत्तापर्यंत आपण प्रत्येक server वर SSH ने login करून तिथे docker टाइप केलं. Docker context मुळे तुमच्या laptop वरचा docker command remote server वरच्या Docker शी साध्या SSH वरून, सुरक्षितपणे बोलतो. आज step by step: context बनवणं, laptop, staging आणि production मध्ये switch करणं, Compose app remote deploy करणं, CI मधून वापरणं, आणि चुकून production वर command चालू नये म्हणून safety नियम.
दोस्तों, अब तक हम हर server पर SSH से login करके वहाँ docker टाइप करते थे. Docker context से आपके laptop का docker command remote server के Docker से सादे SSH पर, सुरक्षित तरीके से बात करता है. आज step by step: context बनाना, laptop, staging और production के बीच switch करना, Compose app remote deploy करना, CI से इस्तेमाल करना, और गलती से production पर command न चले इसके safety नियम.
1. Start from a story
At Urban Company, a developer named Shruti deployed a small internal dashboard to an EC2 server. Her routine was: scp the compose file, ssh in, git pull, docker compose up, and hope she was on the right server.
One day she opened the wrong terminal tab and ran docker compose down on the production server instead of staging. The dashboard was down for ten minutes.
Her lead showed her Docker contexts. She created two contexts, staging and prod, both over SSH with her key. Now she deploys from her laptop with docker --context staging compose up -d, and her shell prompt shows the active context in red when it is prod.
No more copying files to servers, no Docker port open to the internet, and no wrong-server surprises. Today we set it up.
2. Prepare SSH step by step
Step 1: Key login first
Why: Contexts use your normal SSH.
What: The context runs ssh user@host docker system dial-stdio. If plain ssh works without a password prompt, the context will work.
Example 1: Test SSH
Type this:
ssh -i ~/.ssh/shop-staging.pem ubuntu@13.233.10.20 'docker version --format {{.Server.Version}}'
You will see:
27.3.1
What it means: Docker is reachable over SSH.
Example 2: Add to ssh config
Type this:
printf '%s\n' 'Host staging' ' HostName 13.233.10.20' ' User ubuntu' ' IdentityFile ~/.ssh/shop-staging.pem' 'Host prod' ' HostName 13.233.50.60' ' User ubuntu' ' IdentityFile ~/.ssh/shop-prod.pem' >> ~/.ssh/config && chmod 0600 ~/.ssh/config && ssh staging hostname
You will see:
ip-172-31-5-10
What it means: Short names, safe 0600 permissions.
Example 3: Key permissions
Type this:
chmod 0600 ~/.ssh/shop-staging.pem ~/.ssh/shop-prod.pem && ls -l ~/.ssh/*.pem
You will see:
-rw------- 1 me me 1674 shop-prod.pem
-rw------- 1 me me 1674 shop-staging.pem
What it means: SSH refuses keys others can read.
Step 2: User in the docker group
Why: No sudo over the context.
What: The remote user must be able to run docker without sudo. Remember that the docker group is root-equivalent, so only trusted users.
Example 4: Add the user
Type this:
ssh staging 'sudo usermod -aG docker ubuntu' && echo added
You will see:
added
What it means: Log in again for it to apply.
Example 5: Check
Type this:
ssh staging 'id -nG'
You will see:
ubuntu adm sudo docker
What it means: docker is listed.
Example 6: No TCP port
Type this:
ssh staging 'sudo ss -lntp | grep -c 2375'
You will see:
0
What it means: Docker is not listening on the network; SSH is the only way in.
Step 3: Speed up with SSH multiplexing
Why: Reuse one connection.
What: Each docker command opens a new SSH session. ControlMaster keeps one open and reuses it.
Example 7: Add to config
Type this:
printf '%s\n' 'Host *' ' ControlMaster auto' ' ControlPath ~/.ssh/cm-%r@%h:%p' ' ControlPersist 10m' >> ~/.ssh/config && echo ok
You will see:
ok
What it means: Connections kept for 10 minutes.
Example 8: Before
Type this:
time docker -H ssh://staging ps -q >/dev/null
You will see:
real 0m1.42s
What it means: Slow handshake each time.
Example 9: After
Type this:
time docker -H ssh://staging ps -q >/dev/null
You will see:
real 0m0.21s
What it means: Much faster.
3. Contexts step by step
Step 4: docker context ls
Why: The default context is local.
What: Every Docker CLI has a default context that points to the local socket.
Example 10: List
Type this:
docker context ls
You will see:
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
What it means: The star marks the active one.
Example 11: Show active
Type this:
docker context show
You will see:
default
What it means: Name only.
Example 12: Where stored
Type this:
ls ~/.docker/contexts/meta | wc -l
You will see:
0
What it means: No custom contexts yet.
Step 5: docker context create
Why: Point a name at a server.
What: Create one context per server with an ssh:// endpoint and a description.
Example 13: Staging
Type this:
docker context create staging --description 'shop staging EC2' --docker host=ssh://staging
You will see:
staging
Successfully created context "staging"
What it means: Uses the ssh config name.
Example 14: Production
Type this:
docker context create prod --description 'shop PRODUCTION EC2' --docker host=ssh://prod
You will see:
prod
Successfully created context "prod"
What it means: Description in capitals as a warning.
Example 15: List again
Type this:
docker context ls --format '{{.Name}} {{.DockerEndpoint}}'
You will see:
default unix:///var/run/docker.sock
prod ssh://prod
staging ssh://staging
What it means: Three targets.
Step 6: Run one command on a context
Why: --context for a single command.
What: --context NAME runs just that command against the remote server. This is the safest habit.
Example 16: Remote ps
Type this:
docker --context staging ps --format '{{.Names}} {{.Status}}'
You will see:
dashboard-web-1 Up 2 days
What it means: Containers on staging.
Example 17: Remote info
Type this:
docker --context prod info --format '{{.Name}} {{.ServerVersion}} {{.NCPU}} CPUs'
You will see:
ip-172-31-9-44 27.3.1 2 CPUs
What it means: Production details.
Example 18: Remote run
Type this:
docker --context staging run --rm alpine:3.20 hostname
You will see:
b41c2d9e0f11
What it means: The container ran on staging, not your laptop.
Step 7: docker context use
Why: Switch the default.
What: use changes the active context for every later command until you switch back. Handy, but risky with prod.
Example 19: Switch
Type this:
docker context use staging && docker ps -q | wc -l
You will see:
staging
Current context is now "staging"
3
What it means: Now every command goes to staging.
Example 20: Env override
Type this:
DOCKER_CONTEXT=default docker ps -q | wc -l
You will see:
0
What it means: For one command, the env var wins.
Example 21: Back home
Type this:
docker context use default
You will see:
default
Current context is now "default"
What it means: Always switch back.
4. Deploy with a context
Step 8: Compose over a context
Why: The file stays on your laptop.
What: Compose reads the file locally and sends API calls to the remote Docker. Images are pulled by the server. Bind mounts refer to paths on the server.
Example 22: The app
Type this:
printf '%s\n' 'services:' ' web:' ' image: nginx:1.27-alpine' ' ports: ["80:80"]' ' restart: unless-stopped' > compose.yaml && echo ok
You will see:
ok
What it means: A simple file.
Example 23: Deploy to staging
Type this:
docker --context staging compose -p dashboard up -d
You will see:
[+] Running 2/2
✔ Network dashboard_default Created
✔ Container dashboard-web-1 Started
What it means: Running on the server.
Example 24: Check
Type this:
curl -s -o /dev/null -w '%{http_code}\n' http://13.233.10.20/ && docker --context staging compose -p dashboard ps --format '{{.Name}} {{.Status}}'
You will see:
200
dashboard-web-1 Up 10 seconds
What it means: Served from staging.
Step 9: Build on the remote server
Why: The build context is uploaded.
What: docker --context staging build sends your folder to the remote daemon and builds there. Keep .dockerignore tight so the upload is small.
Example 25: Ignore file
Type this:
printf '%s\n' '.git' 'node_modules' '*.log' '.env' > .dockerignore && echo ok
You will see:
ok
What it means: Only needed files go up.
Example 26: Remote build
Type this:
docker --context staging build -q -t dashboard:1.1 .
You will see:
sha256:4e2a...
What it means: Built on staging's CPU.
Example 27: Better for prod
Type this:
printf 'For production, build in CI, push to ECR, and let the server pull by tag and digest.\n'
You will see:
For production, build in CI, push to ECR, and let the server pull by tag and digest.
What it means: Remote builds are fine for staging.
Step 10: Use a context from CI
Why: Deploy from Jenkins over SSH.
What: The CI agent gets the SSH key from its credential store, creates a context, deploys, and removes it.
Example 28: Pipeline step
Type this:
printf '%s\n' 'docker context create prod --docker host=ssh://deploy@13.233.50.60' 'docker --context prod compose -p dashboard pull' 'docker --context prod compose -p dashboard up -d' 'docker context rm -f prod' > deploy.sh && chmod 0755 deploy.sh && cat deploy.sh
You will see:
docker context create prod --docker host=ssh://deploy@13.233.50.60
docker --context prod compose -p dashboard pull
docker --context prod compose -p dashboard up -d
docker context rm -f prod
What it means: Pull first, then up.
Example 29: Known hosts
Type this:
ssh-keyscan -H 13.233.50.60 >> ~/.ssh/known_hosts 2>/dev/null && echo pinned
You will see:
pinned
What it means: Otherwise SSH waits for a yes prompt and the job hangs.
Example 30: Jenkins wrapper
Type this:
printf '%s\n' "sshagent(['prod-deploy-key']) {" ' sh "./deploy.sh"' '}' > Jenkinsfile.part && cat Jenkinsfile.part
You will see:
sshagent(['prod-deploy-key']) {
sh "./deploy.sh"
}
What it means: The key stays in Jenkins credentials.
Step 11: Safety rules for prod
Why: Make mistakes hard.
What: Show the context in your prompt, prefer --context over use, and never leave prod as the active context.
Example 31: Prompt helper
Type this:
printf '%s\n' 'dctx(){ c=$(docker context show 2>/dev/null); [ "$c" = prod ] && printf "[PROD] " || printf "[%s] " "$c"; }' 'PS1="$(dctx)$PS1"' >> ~/.bashrc && echo added
You will see:
added
What it means: A visible warning.
Example 32: Read-only check first
Type this:
docker --context prod compose -p dashboard ps && docker --context prod compose -p dashboard config -q && echo ok-to-deploy
You will see:
NAME STATUS
dashboard-web-1 Up 3 days
ok-to-deploy
What it means: Look before you change.
Example 33: Separate user
Type this:
printf 'Use a deploy user on prod with its own key, and limit who has that key.\n'
You will see:
Use a deploy user on prod with its own key, and limit who has that key.
What it means: Remember: docker access is root access.
Step 12: Context for a swarm and cleanup
Why: Manage a cluster remotely.
What: Point a context at a swarm manager to run docker stack and docker service commands from your laptop.
Example 34: Manager context
Type this:
docker context create swarm --docker host=ssh://ubuntu@10.0.1.10 && docker --context swarm node ls --format '{{.Hostname}} {{.Status}}'
You will see:
swarm
Successfully created context "swarm"
mgr1 Ready
wrk1 Ready
wrk2 Ready
What it means: The whole cluster from the laptop.
Example 35: Stack deploy remotely
Type this:
docker --context swarm stack deploy -c stack.yaml --with-registry-auth shop | tail -1
You will see:
Updating service shop_web (id: k8m2...)
What it means: Same file as the last lesson.
Example 36: Export and remove
Type this:
docker context export staging staging.dockercontext && docker context rm swarm && docker context ls --format '{{.Name}}'
You will see:
Written file "staging.dockercontext"
swarm
default
prod
staging
What it means: Export to share the endpoint; keys are not inside it.
5. Common mistakes and how Shruti avoids them
Mistake one: opening the Docker TCP port 2375 to use the CLI remotely. Shruti only uses SSH contexts.
Mistake two: leaving prod as the active context. She uses --context prod for each command and switches back to default.
Mistake three: bind mounts with laptop paths. Over a context, paths are on the server, so she uses named volumes or server paths.
Mistake four: building production images remotely. She builds in CI and only pulls on the server.
Mistake five: CI jobs hanging on an SSH host-key prompt. She adds the host to known_hosts in the pipeline.
Check yourself
Q1. What transport does an ssh:// context use?
SSH, using your normal key and ssh config.
Q2. Difference between --context and docker context use?
--context affects one command; use changes the default for all later commands.
Q3. With a remote context, whose disk does a bind mount use?
The remote server's.
Q4. Why not open port 2375?
It gives unauthenticated root-level Docker access over the network.
Friends, set up SSH key login, create one context per server, use --context for each command, deploy Compose files from your laptop, and keep production behind clear safety rules. Did you understand? Let us try it once more. Create a staging context for a VM, deploy nginx with compose up from your laptop, and check it with curl.
मित्रांनो, SSH key login सेट करा, प्रत्येक server साठी एक context बनवा, प्रत्येक command साठी --context वापरा, laptop वरून Compose files deploy करा, आणि production ला स्पष्ट safety नियमांमागे ठेवा. एका VM साठी staging context बनवा, laptop वरून compose up ने nginx deploy करा, आणि curl ने तपासा.
दोस्तों, SSH key login सेट कीजिए, हर server के लिए एक context बनाइए, हर command के लिए --context इस्तेमाल कीजिए, laptop से Compose files deploy कीजिए, और production को साफ़ safety नियमों के पीछे रखिए. एक VM के लिए staging context बनाइए, laptop से compose up से nginx deploy कीजिए, और curl से जाँचिए.
Ravindra Bagale's Tip
Type --context prod every single time instead of docker context use prod. The extra words are your seatbelt.
docker context use prod ऐवजी प्रत्येक वेळी --context prod टाइप करा. हे जास्तीचे शब्द म्हणजे तुमचा seatbelt.
docker context use prod की जगह हर बार --context prod टाइप कीजिए. ये ज़्यादा शब्द आपका seatbelt हैं.
Lab: try at home
- Set up SSH key login and an ssh config entry for a VM.
- Confirm Docker on the VM is not listening on TCP.
- Create a staging context and run docker --context staging ps.
- Deploy nginx with docker --context staging compose up -d.
- Add SSH multiplexing and time the difference.
- Write the CI deploy script with known_hosts pinning.
- Add the context name to your shell prompt.
Recap
- A context points the CLI at another Docker.
- ssh:// contexts need only SSH, no open Docker port.
- --context for one command, use for the default.
- Compose files stay local; images and mounts are remote.
- CI creates a context, deploys, then removes it.
- Guard prod with prompts, a deploy user and read-only checks.
Lab
- Run an app with --user, --cap-drop ALL and --read-only and fix what breaks.
- Scan an image with Trivy and fix one CVE.
- Set log rotation in daemon.json.
- Start the monitoring stack and view a dashboard.
- Create a 1-node swarm and do a rolling update and a rollback.
- Deploy a stack with a secret and a config, then rotate the secret.
- Deploy a Compose app to a VM through an SSH context.
Practice task
Harden one of your images: non-root, read-only, dropped capabilities, no secrets in history, zero CRITICAL in Trivy. Show the commands and output.
Ravindra Bagale's Tip
The security rule: least privilege, least software, and never a secret in an image. Check every image with a scan before it reaches production.
Security चा नियम: कमी अधिकार, कमी software, आणि secret कधीही image मध्ये नाही. Production मध्ये जाण्यापूर्वी प्रत्येक image scan करा.
Security का नियम: कम अधिकार, कम software, और secret कभी image में नहीं. Production में जाने से पहले हर image scan कीजिए.
In short
- Run as non-root with dropped capabilities.
- Make the root filesystem read-only.
- Secrets come as files, never ENV.
- Scan every image in CI.
- Rotate logs and ship them centrally.
- Swarm gives simple clustering built into Docker.