MHOSTMHOST

How to Install Docker on Ubuntu: A Step-by-Step Guide

Docker runs applications in isolated containers: a program and all its dependencies are packed into an image and behave the same on any server. You don't need to install the right versions of Python, Node.js or PostgreSQL by hand — just pull a ready-made image and run it with one command.

This guide installs Docker Engine and Docker Compose from Docker's official repository, the method the Docker team recommends. We'll also cover running Docker without sudo, keeping containers from bypassing your firewall, and stopping logs from filling your disk.

Requirements:

  • 64-bit Ubuntu 22.04, 24.04 or 26.04 — the versions Docker officially supports.
  • A user with sudo rights. If the server is new, complete the initial VPS setup first.
  • A VPS with KVM virtualization. Docker may not work on container-based virtualization (OpenVZ, LXC).

Step 1. Remove Old and Unofficial Packages

Ubuntu's repositories include unofficial Docker packages (docker.io, docker-compose and others). They conflict with the official version, so remove them first:

sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)

If none of these packages are installed, apt will say so — that's fine, move on to the next step.

Images, containers and volumes in /var/lib/docker are not removed. If you're installing Docker on a server where it was already used, your data stays.

Step 2. Add Docker's Official Repository

Install the required tools and add the key Docker uses to sign its packages:

sudo apt update

sudo apt 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

Add the repository. This is one long command — copy it in full:

printf '%s\n' "Types: deb" "URIs: https://download.docker.com/linux/ubuntu" "Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")" "Components: stable" "Architectures: $(dpkg --print-architecture)" "Signed-By: /etc/apt/keyrings/docker.asc" | sudo tee /etc/apt/sources.list.d/docker.sources

The command creates /etc/apt/sources.list.d/docker.sources and fills in your Ubuntu version and CPU architecture automatically.

Refresh the package list:

sudo apt update

You should see a line with download.docker.com in the output, which means the repository is connected.

Step 3. Install Docker Engine and Docker Compose

One command installs everything you need:

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

What gets installed:

  • docker-ce — Docker Engine itself, the service that runs containers.
  • docker-ce-cli — the docker command for managing it.
  • containerd.io — the container runtime Docker is built on.
  • docker-buildx-plugin — the image builder.
  • docker-compose-plugin — Docker Compose, for running multi-container apps from a single file.

Docker starts automatically and is enabled at boot. Check its status:

sudo systemctl status docker

It should say active (running). If the service isn't running, enable it: sudo systemctl enable --now docker.

Step 4. Verify the Install and Run Docker Without sudo

Run a test container:

sudo docker run hello-world

Docker pulls a tiny image and prints "Hello from Docker!". That means everything works.

Check the versions:

docker --version

docker compose version

Running without sudo. By default, Docker commands need sudo. To work without it, add your user to the docker group:

sudo usermod -aG docker $USER

Reconnect over SSH so the change takes effect, then check:

docker run hello-world

A security note: members of the docker group effectively have root rights on the server — a container can be used to reach any file on the system. Only add trusted users to this group.

Quick Method: Install with One Script

Docker has an official script that adds the repository and installs all packages for you. It's handy for a test server:

curl -fsSL https://get.docker.com -o get-docker.sh

sudo sh get-docker.sh

To preview what the script will do without installing anything, run it with the --dry-run flag.

The Docker team doesn't recommend the script for production servers: you can't pick a version, and it may unexpectedly install a new major Docker release. For production, the manual install in steps 1–3 is more reliable. After installing with the script, update Docker through apt, not by rerunning the script.

Your First Container and Basic Docker Commands

Run the Nginx web server in a container:

docker run -d --name web -p 127.0.0.1:8080:80 --restart unless-stopped nginx

What the options mean:

  • -d — run in the background.
  • --name web — a container name so you can refer to it.
  • -p 127.0.0.1:8080:80 — the container's port 80 is available on the server's port 8080, locally only. Why locally — see the next section.
  • --restart unless-stopped — the container comes back on its own after a crash or a server reboot.

Check that Nginx responds: curl http://127.0.0.1:8080.

Commands you'll use every day:

  • docker ps — running containers. Add -a to see all, including stopped ones.
  • docker images — downloaded images.
  • docker logs -f web — container logs in real time.
  • docker exec -it web bash — open a shell inside the container. If bash isn't there, use sh.
  • docker stop web and docker start web — stop and start.
  • docker restart web — restart.
  • docker rm web — remove a stopped container.
  • docker pull nginx — pull the latest version of an image.
  • docker stats — container CPU and memory usage.

Multi-container apps (for example, a site plus a database) use Docker Compose: all services are described in one docker-compose.yml file and started with docker compose up -d. A real-world example: how to install n8n on a VPS with Docker Compose.

Docker and the UFW Firewall: The Biggest Trap

This is the most common mistake on Docker servers. Ports published by Docker are exposed to the internet, bypassing UFW rules. Docker's official documentation warns about this.

Example: you start a database with docker run -p 5432:5432 postgres. UFW says port 5432 is closed, yet the database is reachable from the internet and bots start guessing the password. This happens because Docker writes its own iptables rules, and they fire before UFW's rules.

How to protect yourself:

  • Bind ports to localhost when a service shouldn't be reachable from outside: -p 127.0.0.1:5432:5432. In Docker Compose: "127.0.0.1:5432:5432".
  • Don't publish database ports at all if only other containers use them. Containers on the same Docker network reach each other by service name with no port mapping.
  • Expose only the web server (Nginx or Caddy on ports 80 and 443) and proxy other services through it.

To see which ports are actually open to the outside, run sudo ss -tlnp. An address of 0.0.0.0 or * means the port listens on all interfaces and is reachable from the internet.

Limit Logs and Watch Your Disk Space

By default, Docker keeps container logs without any limit. An active container can fill the whole disk within a few months, and the server stops working.

Log rotation. Create a Docker config file with a limit of 3 files of 10 MB per container:

echo '{"log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"}}' | sudo tee /etc/docker/daemon.json

sudo systemctl restart docker

This command overwrites the file: if /etc/docker/daemon.json already exists, add the settings to it by hand. The setting applies only to new containers. Recreate existing ones: docker compose up -d --force-recreate for Compose, or remove and start them again.

Freeing up space. See how much space Docker uses:

docker system df

Remove stopped containers, unused networks, dangling images and build cache:

docker system prune

Remove all images not used by any container:

docker image prune -a

Be careful with volumes. docker volume prune removes volumes not attached to containers, and volumes hold data such as databases. Before running it, make sure all needed containers are running and take a backup.

How to Update and Uninstall Docker

Updating. Docker updates along with the rest of your system packages:

sudo apt update

sudo apt install --only-upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

The Docker service restarts during the update, and so do your containers. Containers with --restart unless-stopped or restart: always come back on their own. Update during a quiet period.

Updating application images is a separate step. For Docker Compose:

docker compose pull

docker compose up -d

Uninstalling Docker completely. Remove the packages:

sudo apt purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

Images, containers and volumes stay on disk afterward. To remove them too — this permanently destroys all container data:

sudo rm -rf /var/lib/docker /var/lib/containerd

Remove the repository and key:

sudo rm /etc/apt/sources.list.d/docker.sources /etc/apt/keyrings/docker.asc

Common Errors

  • permission denied while trying to connect to the Docker daemon socket. Your user isn't in the docker group. Run sudo usermod -aG docker $USER and reconnect over SSH.
  • Cannot connect to the Docker daemon. Is the docker daemon running? The Docker service isn't running. Start it with sudo systemctl start docker and check the reason in sudo journalctl -u docker -n 50.
  • Bind for 0.0.0.0:80 failed: port is already allocated. The port is taken by another container or program, such as Nginx on the host. Find out what's using it: sudo ss -tlnp | grep :80.
  • no space left on device. The disk is full. Clean up unused Docker data and set up log rotation (see above).
  • toomanyrequests: You have reached your pull rate limit. You've hit Docker Hub's download limit for anonymous users. Sign in to Docker Hub: docker login.
  • Package conflicts during install. Unofficial Docker packages are still on the server. Repeat step 1.

Checklist

FAQ

How is Docker different from a virtual machine? A virtual machine emulates a whole computer with its own OS and kernel. A container uses the server's kernel and isolates only the application, so it starts in seconds and uses almost no extra memory.

Can I install Docker via snap or apt install docker.io? You can, but those versions are often older, and the snap version restricts access to system files. Docker's official repository gives you current versions and security updates.

Do I need to install Docker Compose separately? No, the docker-compose-plugin from step 3 already includes Compose. The command is written with a space: docker compose. The old standalone docker-compose tool with a hyphen is deprecated.

How many resources does Docker need? Docker itself uses very little. It all depends on your containers: a couple of small services run fine on a VPS with 1–2 GB RAM; for databases and several apps, get 4 GB or more.

Does Docker work on any VPS? Reliably on KVM-based VPS. On container-based virtualization (OpenVZ, LXC), Docker often won't start or runs with limitations.