Ubuntu ships a docker.io package, but it usually lags behind upstream. For a server you’ll run for years, install Docker Engine from Docker’s own apt repository: you get current releases, the Compose and Buildx plugins, and security fixes as soon as they ship.
These steps work on Ubuntu 22.04, 24.04 and later LTS releases, on a VPS or a physical machine. You need a user with sudo.
1. Remove conflicting packages
If anything Docker-related was installed earlier from Ubuntu’s repositories, remove it first. Nothing is deleted from /var/lib/docker, so existing images survive.
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
sudo apt-get remove -y $pkg
done
“Package not installed” messages are fine.
2. Add Docker’s apt repository
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
The signed-by option means the Docker key is trusted only for this repository, not for every package source on the system.
3. Install Docker Engine and plugins
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
What each package does:
| Package | Purpose |
|---|---|
docker-ce |
The Docker daemon (dockerd) |
docker-ce-cli |
The docker command |
containerd.io |
Container runtime used by Docker |
docker-buildx-plugin |
Modern image builder (docker buildx, multi-arch builds) |
docker-compose-plugin |
docker compose (Compose v2) |
The service starts automatically and is enabled at boot. Check it:
sudo systemctl status docker --no-pager
docker --version
docker compose version
4. Test it
sudo docker run --rm hello-world
Docker pulls a tiny image, runs it and prints a “Hello from Docker!” message. If you see that, the engine, networking and registry access all work.
5. Run Docker without sudo (optional)
Add your user to the docker group, then log out and back in (or run newgrp docker):
sudo usermod -aG docker $USER
newgrp docker
docker ps
Be aware: membership of the docker group is equivalent to root. Anyone in it can start a container that mounts / and edit any file. Only add trusted admin accounts. If that’s a concern, look at rootless Docker or keep using sudo.
6. Sensible daemon defaults
Container logs grow without limit by default and can fill a small disk. Set a log size cap in /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
sudo systemctl restart docker
This applies to containers created after the change; recreate existing ones (docker compose up -d --force-recreate) to pick it up.
7. A note on Docker and UFW
Docker writes its own iptables rules. A port published with -p 8080:80 is reachable from the internet even if UFW denies it, because Docker’s rules are evaluated before UFW’s. Two safe habits:
- Bind internal services to localhost:
-p 127.0.0.1:8080:80, then put a reverse proxy in front. - Don’t publish database ports at all — containers on the same Docker network can reach each other without
-p.
Updating Docker
Docker comes from an apt repository, so normal updates include it:
sudo apt-get update && sudo apt-get upgrade
Running containers are restarted when the daemon restarts. Containers with a restart policy such as unless-stopped come back automatically; others stay stopped.
To pin a version, list what’s available and install it explicitly:
apt-cache madison docker-ce | head
sudo apt-get install docker-ce=<VERSION> docker-ce-cli=<VERSION>
Uninstalling Docker
sudo apt-get purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo rm -rf /var/lib/docker /var/lib/containerd
sudo rm /etc/apt/sources.list.d/docker.list /etc/apt/keyrings/docker.asc
The second line deletes all images, containers and volumes — back up any volume data you need first.
Troubleshooting
permission denied while trying to connect to the Docker daemon socket — your user isn’t in the docker group yet, or you haven’t logged out and in. Use sudo or see fixing the Docker socket permission error.
Cannot connect to the Docker daemon. Is the docker daemon running? — start it and read the log:
sudo systemctl start docker
sudo journalctl -u docker --no-pager -n 50
A common cause is invalid JSON in /etc/docker/daemon.json.
docker: 'compose' is not a docker command — the Compose plugin isn’t installed: sudo apt-get install docker-compose-plugin. The old standalone docker-compose (with a hyphen) is deprecated.
Pulls fail with toomanyrequests — Docker Hub rate limits anonymous pulls per IP. Log in with docker login, or use a mirror.
What’s next
- Learn the everyday commands in the Docker commands cheat sheet.
- Define multi-container apps in YAML with the Docker Compose tutorial.
- Build your own images: Dockerfile tutorial.
Docker runs on any KVM virtual server. Our Docker VPS plans come with full root access, so you can install it in a couple of minutes using the steps above.