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

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.

  • Docker
  • Ubuntu
  • Containers