Sometimes the package manager isn’t fast enough — a critical CVE just dropped, you need a specific patch version for compatibility testing, or you’re working on an air-gapped machine without repository access. In all of these cases, you need to know how to replace the Docker binaries directly, safely, without breaking the containers already running on the host. This guide covers exactly that.
Understanding What “The Docker Binary” Actually Is
“Docker” isn’t a single binary — it’s a small collection of them, each with a distinct role:
dockerd— the daemon itself, the long-running background servicedocker(ordocker-cli) — the CLI client you interact withcontainerd— the actual container runtime the daemon delegates torunc— the low-level OCI runtime that creates and runs the actual container processesdocker-proxy,docker-init— smaller support binaries for port forwarding and PID 1 duties inside containers
Replacing “the Docker binary” usually means replacing dockerd and/or the CLI, and possibly containerd/runc if you need a specific version of those too.
Before You Start: Check Your Current Version
docker version
Client: Docker Engine - Community
Version: 26.1.4
API version: 1.45
Server: Docker Engine - Community
Engine:
Version: 26.1.4
API version: 1.45 (minimum version 1.24)
Note both client and server versions — you’ll want to confirm the upgrade actually took effect afterward.
Step 1: Back Up What Matters
Before touching binaries, protect yourself from a bad upgrade:
# Note running containers so you can verify state afterward
docker ps -a > ~/docker-container-state-backup.txt
# Back up daemon configuration
sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak 2>/dev/null || echo "no daemon.json present"
# Optional: snapshot the data root if you can afford the downtime and disk space
sudo tar -czf ~/docker-data-backup.tar.gz -C /var/lib docker
Step 2: Download the Static Binaries
Docker publishes versioned static binary tarballs specifically for this kind of manual install/upgrade, separate from the OS package repos:
cd /tmp
curl -fsSL -o docker-27.3.1.tgz https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz
Verify the download (always check the checksum against Docker’s published values on the download page before trusting a binary you’re about to run as root):
sha256sum docker-27.3.1.tgz
Extract it:
tar xzvf docker-27.3.1.tgz
docker/
docker/containerd-shim-runc-v2
docker/containerd
docker/runc
docker/ctr
docker/dockerd
docker/docker-init
docker/docker-proxy
docker/docker
Step 3: Stop the Running Daemon
Containers managed by containerd can, in many configurations, keep running even if dockerd stops (this is intentional — it’s what allows Docker upgrades without killing your workloads). Still, plan for a maintenance window:
sudo systemctl stop docker
sudo systemctl stop docker.socket
Confirm nothing is still holding the socket:
sudo lsof /var/run/docker.sock 2>/dev/null
Step 4: Replace the Binaries
Copy the new binaries over the old ones. The standard install location is /usr/bin/:
sudo cp docker/* /usr/bin/
Confirm the files landed and check their versions directly:
/usr/bin/dockerd --version
/usr/bin/docker --version
Docker version 27.3.1, build 41ca978
Docker version 27.3.1, build 41ca978
Step 5: Restart the Daemon
sudo systemctl daemon-reexec
sudo systemctl start docker
sudo systemctl status docker
Expected healthy status output:
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled)
Active: active (running) since Wed 2026-07-29 10:02:11 UTC; 3s ago
Step 6: Verify the Upgrade and Container State
docker version
Confirm both client and server now report the new version. Then confirm your previously running containers survived:
docker ps -a
diff <(docker ps -a --format '{{.Names}}' | sort) <(awk '{print $NF}' ~/docker-container-state-backup.txt | sort)
No diff output means everything you had running before is still present (note: whether they auto-restarted depends on their restart policy — this diff only confirms the containers still exist, not that they’re in the running state you expect, so also check docker ps for actual running status).
Doing This on Docker Desktop (macOS/Windows) Instead
The above is for Linux servers running Docker Engine directly. On Docker Desktop, you don’t manually swap binaries — you install a specific release package instead:
# macOS example — download the specific version's .dmg from Docker's release archive,
# then replace the running app
brew install --cask docker
Or download a pinned version directly from Docker’s release notes page and reinstall over the existing app bundle.
Rolling Back If Something Breaks
Keep the old binaries around before you overwrite them — this is the actual safety net, more important than any backup step above:
sudo mkdir -p /opt/docker-backup-26.1.4
sudo cp /usr/bin/dockerd /usr/bin/docker /usr/bin/containerd /usr/bin/runc /opt/docker-backup-26.1.4/
To roll back:
sudo systemctl stop docker
sudo cp /opt/docker-backup-26.1.4/* /usr/bin/
sudo systemctl daemon-reexec
sudo systemctl start docker
docker version
Internal Working: Why Docker Upgrades Don’t Have to Kill Containers
This is the detail that makes manual binary replacement viable at all in production: dockerd doesn’t directly hold onto the container processes — containerd does, via containerd-shim processes, one per running container. When dockerd restarts (or is upgraded), it reconnects to the still-running containerd daemon and re-associates with the existing shims and their containers, rather than restarting them. This is why stopping docker.service alone, upgrading, and restarting it is generally non-disruptive to already-running containers — the actual container processes were never owned by the piece you replaced.
If you also replace containerd itself, this safety property goes away for that upgrade, since you’re now touching the component that directly supervises the container processes — plan for a full stop/start cycle in that case.
Automating This with a Script
For repeatable upgrades across a fleet, wrap the above in a script:
#!/usr/bin/env bash
set -euo pipefail
VERSION="${1:?Usage: $0 <docker-version>}"
BACKUP_DIR="/opt/docker-backup-$(docker version --format '{{.Server.Version}}' 2>/dev/null || echo unknown)"
echo "Backing up current binaries to $BACKUP_DIR"
sudo mkdir -p "$BACKUP_DIR"
sudo cp /usr/bin/dockerd /usr/bin/docker /usr/bin/containerd /usr/bin/runc "$BACKUP_DIR/" 2>/dev/null || true
echo "Downloading Docker $VERSION"
curl -fsSL -o "/tmp/docker-$VERSION.tgz" "https://download.docker.com/linux/static/stable/x86_64/docker-$VERSION.tgz"
echo "Stopping Docker"
sudo systemctl stop docker docker.socket
echo "Installing new binaries"
tar xzf "/tmp/docker-$VERSION.tgz" -C /tmp
sudo cp /tmp/docker/* /usr/bin/
echo "Starting Docker"
sudo systemctl daemon-reexec
sudo systemctl start docker
docker version
echo "Upgrade to $VERSION complete."
Best Practices
- Always keep the previous version’s binaries in a labeled backup directory before overwriting — this is your fastest rollback path.
- Verify checksums of downloaded static binaries; never pipe a curl download straight into execution as root.
- Schedule a maintenance window even though most upgrades are non-disruptive — some do require a full container restart, particularly major version jumps or
containerd/runcchanges. - Read the release notes for the target version, especially around deprecated flags,
daemon.jsonschema changes, and default behavior changes. - Prefer your distro’s package manager (
apt,yum,dnf) for routine upgrades where available; reserve manual static-binary replacement for cases where you need precise version control or lack repository access.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
docker: command not found after replacing binaries | Copied to wrong path, not on $PATH | Confirm binaries are in /usr/bin and it’s on PATH |
| Daemon fails to start after upgrade | Config schema in daemon.json incompatible with new version | Check journalctl -u docker for the exact error, consult release notes |
| Containers show as running but unreachable | docker-proxy binary version mismatch left stale port forwarding | Restart the affected containers explicitly |
Version mismatch between client and daemon | Only the CLI binary was replaced, not dockerd | Ensure you copy the entire extracted binary set, not just docker |
Summary
Replacing Docker’s binaries manually gives you precise control over exactly which version runs on a host, independent of your distro’s package repository timing. The key safety nets are keeping the old binaries backed up for instant rollback, verifying checksums before running anything as root, and understanding that containerd — not dockerd — is what actually keeps your containers alive across most Docker Engine upgrades.
References
- Docker Engine static binary downloads: https://download.docker.com/linux/static/stable/
- Docker Engine release notes: https://docs.docker.com/engine/release-notes/
- Docker Engine installation guide: https://docs.docker.com/engine/install/
- containerd documentation: https://containerd.io/docs/