All articles
Guides

How to install Docker and Docker Compose on Ubuntu and Debian

In short

Install Docker from Docker's official repository rather than the docker.io package: that gets you a current Engine and the Compose plugin. Right after installing, cap container log size, and remember that Docker publishes ports around the UFW firewall - expose only what should be reachable from the internet.

Key takeaways
  • On Ubuntu 22.04/24.04 and Debian 12, Docker Engine is installed from Docker's official repository as docker-ce with docker-compose-plugin, not as the distribution's docker.io package - that brings a current Engine and the docker compose command.
  • Docker publishes container ports through its own iptables rules around the UFW firewall: a port published as 8080:80 is reachable from the internet even if UFW blocks it, so internal services should be published only on 127.0.0.1.
  • By default Docker keeps container logs with no size limit; setting max-size: 10m and max-file: 3 in /etc/docker/daemon.json caps logs at 30 MB per container.
  • A user in the docker group effectively has root on the server, so only your own administrator account should be added to that group.
  • Docker and Docker Compose run without restrictions on a KVM VPS; Docker itself is fine with 2 GB of RAM, and the containers determine the rest.

Step 1. Add the Docker repository and install the packages

The same commands work on Ubuntu and Debian: /etc/os-release fills in the distribution name and release. This is the official method from the Docker documentation:

bash
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
. /etc/os-release
sudo curl -fsSL "https://download.docker.com/linux/$ID/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/$ID ${UBUNTU_CODENAME:-$VERSION_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Step 2. Check the installation

Run the hello-world test container and check the Compose version - both commands should finish without errors:

bash
sudo docker run --rm hello-world
docker compose version

If hello-world prints its greeting, the Engine works. The Docker service is already enabled at boot and will come back after a server reboot.

Step 3. Run without sudo

To stop typing sudo before every docker command, add your user to the docker group and log in over SSH again:

bash
sudo usermod -aG docker $USER
# log out of SSH and back in, then:
docker ps

Step 4. A first Docker Compose project

Create a project directory and a compose.yaml file - the example runs nginx reachable only from the server itself on port 8080:

compose.yaml
services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"
    restart: unless-stopped
bash
docker compose up -d
docker compose ps
docker compose logs -f

restart: unless-stopped brings the container back after a crash and after a server reboot - it needs no separate systemd service.

Docker and UFW: ports open around the firewall

Docker writes its own iptables rules for published ports, and they match before UFW rules do. A port published as 8080:80 is reachable from the internet even if UFW keeps it closed. So:

  • publish internal services (databases, admin panels, APIs without auth) only on 127.0.0.1, as in the example above;
  • expose a single reverse proxy (nginx, Caddy, Traefik) on ports 80 and 443 and let it forward requests to containers - the HTTPS guide shows how to set up nginx with a free certificate;
  • do not publish ports at all for containers that need no outside access - within one Compose project they reach each other by service name.

Step 5. Cap log size

By default Docker keeps container logs with no size limit, and a chatty container can fill the whole disk. Cap them at three 10 MB files per container. The setting applies to containers created after it - recreate existing ones with docker compose up -d --force-recreate.

bash
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}
EOF
sudo systemctl restart docker

How to free disk space

Old images pile up after every update. The first command shows what takes the space, the second removes images no container uses:

bash
docker system df
docker image prune -a
Launch a server in 2 minutes

AMD Ryzen 9, NVMe and DDoS protection in Germany, Finland and Poland. Pay with crypto or card.

Order a Server

FAQ

How is the docker.io package different from docker-ce?

docker.io is built by the distribution itself and is usually older. docker-ce comes from Docker's official repository with a current Engine and the Compose plugin; it is what the Docker documentation recommends.

Does Docker work on a Tihost VPS?

Yes. Servers run on KVM - a full virtual machine with its own kernel - so Docker, Compose and container networking work without restrictions.

Why is a container reachable from the internet even though UFW is closed?

Docker publishes ports through its own iptables rules, which match before UFW. Publish internal services on 127.0.0.1 or do not publish them at all.

What is the difference between docker compose and docker-compose?

docker-compose is the old standalone Python program and is no longer developed. docker compose (with a space) is a Docker CLI plugin installed by the docker-compose-plugin package.

How much memory does Docker need?

Docker itself is fine with 2 GB of RAM. The rest depends on your containers: several services with a database are more comfortable on 4-8 GB.