All tutorials
10 min read

How to Install Docker and Docker Compose on Ubuntu 24.04 and 26.04

Install Docker on Ubuntu 24.04 or 26.04 with the Compose plugin from Docker's apt repository, bind container ports to localhost, and run a stack in 9 steps.

TL;DR:

  • Install five packages from Docker's apt repository: docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin and docker-compose-plugin.
  • The same steps work on Ubuntu 24.04 LTS and 26.04 LTS and install Docker Engine 29.8.2 and Docker Compose 5.6.0 (October 2026).
  • The repository key goes in /etc/apt/keyrings/docker.asc and the source in /etc/apt/sources.list.d/docker.sources, which replace the older apt-key and docker.list steps.
  • Members of the docker group get root-level access to the host, so add only your own sudo user.
  • Docker-published ports bypass UFW. Bind containers to 127.0.0.1 and put a reverse proxy on ports 80 and 443.
  • Cap container logs in /etc/docker/daemon.json at 3 files of 10 MB per container.

Applies to: Ubuntu 24.04 LTS and 26.04 LTS · Docker Engine 29.8.2 · Docker Compose 5.6.0 · checked October 2026

Docker Engine is the open-source container runtime that builds and runs containers on Linux. Docker Compose is the Docker CLI plugin that starts multi-container apps from one compose.yaml file. This tutorial shows how to install Docker on Ubuntu 24.04 or 26.04 from Docker's official apt repository, then covers the docker group, log rotation, the UFW port bypass and a first Compose stack. On a headless server, install Docker Engine. Docker Desktop is a graphical app for desktop computers.

Prerequisites

  • A VPS running Ubuntu 24.04 LTS or 26.04 LTS (64-bit) with SSH access. Arct Cloud Linux VPS images include Ubuntu 26.04 LTS, with root SSH, and accept an SSH public key at deploy.
  • A non-root user with sudo privileges who logs in with an SSH key. To make a key, see How to Generate an SSH Key on Windows, macOS, and Linux.
  • At least 1 GB of free disk: the Docker packages list about 365 MB of installed size in Docker's repository index. Docker's install docs set no RAM minimum, so size RAM for the containers you plan to run.

If you log in as root with an SSH key, create the sudo user once and copy your key to it. adduser asks for a password, which sudo uses later:

adduser deploy
usermod -aG sudo deploy
install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
install -m 600 -o deploy -g deploy ~/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys

Log out and connect again with ssh deploy@YOUR_SERVER_IP. Every command from Step 1 on runs as deploy. Replace values written in capitals, such as YOUR_SERVER_IP, with your own.

Step 1: Remove conflicting packages

Ubuntu's own docker.io, docker-compose-v2 and containerd packages conflict with Docker's packages. Remove any that are installed:

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

On a fresh server, dpkg finds no matching packages and apt removes nothing. Data in /var/lib/docker stays on disk.

Step 2: Add Docker's apt repository

Docker signs its packages with its own key. Download the key into the apt keyrings directory:

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

Create the repository source. The command fills in your release codename and architecture:

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
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
EOF

The output looks similar to this:

Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: noble
Components: stable
Architectures: amd64
Signed-By: /etc/apt/keyrings/docker.asc

On Ubuntu 26.04 the Suites line reads resolute. Refresh the package index:

sudo apt update

Docker's docs do not recommend the get.docker.com convenience script for production servers.

Step 3: Install Docker on Ubuntu with the Compose plugin

Install the five packages in one command, and type Y when apt asks to continue:

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

apt also installs docker-ce-rootless-extras and recommended tools such as git and pigz when they are missing.

PackageWhat it installs
docker-ceDocker Engine, the dockerd daemon
docker-ce-cliThe docker command
containerd.iocontainerd and runc, the runtime under Docker Engine
docker-buildx-plugindocker buildx, the BuildKit image builder
docker-compose-plugindocker compose

On Ubuntu, the Docker service starts after the install and on every boot. Check both:

systemctl is-active docker
systemctl is-enabled docker
active
enabled

Check the installed versions:

docker --version
docker compose version

The output looks similar to this. Your versions may be newer:

Docker version 29.8.2, build 7fc2dff
Docker Compose version v5.6.0

Step 4: Run the hello-world test

The hello-world image pulls from Docker Hub, prints a message and exits:

sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
...
Hello from Docker!
This message shows that your installation appears to be working correctly.
...

Step 5: Run Docker without sudo

The Docker daemon listens on /var/run/docker.sock, which only root and the docker group can open. The package creates the group with no members. Add your user:

sudo usermod -aG docker $USER

A docker group member can start a container that mounts any host directory, which is root-level access. Add only users you would also give sudo. Log out of SSH and back in so the group applies, then check:

id -nG
docker run --rm hello-world

The group list must include docker. For a daemon that runs without root, Docker documents a separate rootless mode.

Step 6: Configure the Docker daemon

/etc/docker/daemon.json sets defaults for new containers. This file caps logs and binds published ports to loopback unless a compose file names another address:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "ip": "127.0.0.1",
  "default-network-opts": {
    "bridge": {
      "com.docker.network.bridge.host_binding_ipv4": "127.0.0.1"
    }
  }
}
EOF
  • log-opts: the json-file driver has no size limit by default. These values keep 3 files of 10 MB per container, written as quoted strings.
  • ip: ports published on the default bridge network without an address bind to 127.0.0.1.
  • default-network-opts: the same default for user-defined networks, including the ones Compose creates.
  • A container that must answer on the internet, such as a reverse proxy container, needs an explicit address in its port mapping: "0.0.0.0:443:443".

If the file already existed, merge these keys into it. Validate it, then restart Docker:

sudo dockerd --validate --config-file /etc/docker/daemon.json
sudo systemctl restart docker
configuration OK

The settings apply to containers and networks created after the restart.

Step 7: Enable UFW and keep container ports private

The Uncomplicated Firewall (UFW) filters the INPUT chain. Docker routes published ports through NAT rules that run before that chain, so a port published on a public address stays reachable even when UFW denies it. The Step 6 defaults and an explicit 127.0.0.1 in each compose file keep containers on loopback.

Allow SSH before you enable the firewall, or UFW blocks new SSH logins:

sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status

Type y when UFW asks to confirm. The output looks similar to this:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

To serve a container on the internet, run a reverse proxy on the host and allow 80/tcp and 443/tcp. The Nginx, PostgreSQL, and Redis setup guide covers Nginx as a reverse proxy. A port you publish on purpose with 0.0.0.0:PORT:PORT skips UFW. To restrict it, add rules to the DOCKER-USER iptables chain and match the host port with -m conntrack --ctorigdstport PORT, as Docker's iptables docs show.

Step 8: Start a test Compose stack

Compose names the project, network and containers after the directory:

mkdir -p ~/hello-compose
cd ~/hello-compose
nano compose.yaml

Paste this file, then save with Ctrl+O and exit with Ctrl+X:

services:
  web:
    image: nginx:1.30.5-alpine
    ports:
      - "127.0.0.1:8080:80"
    restart: unless-stopped

Current Compose releases ignore the old version: key, so the file has none. restart: unless-stopped starts the container again after a reboot. Start the stack:

docker compose up -d

Compose pulls the image, creates the hello-compose_default network and starts the hello-compose-web-1 container.

Step 9: Verify the stack and the firewall

Check that the service runs, answers on loopback, binds to 127.0.0.1 and uses the log limits:

docker compose ps --services --status running
curl -sI http://127.0.0.1:8080 | head -n 1
docker port hello-compose-web-1
docker inspect --format '{{json .HostConfig.LogConfig}}' hello-compose-web-1
web
HTTP/1.1 200 OK
80/tcp -> 127.0.0.1:8080
{"Type":"json-file","Config":{"max-file":"3","max-size":"10m"}}

From your own computer, test the public address:

curl --max-time 5 http://YOUR_SERVER_IP:8080

Replace YOUR_SERVER_IP with the server's IP address, such as 203.0.113.10. The request times out after 5 seconds; a page means the port is public. Remove the test stack:

docker compose down

Update Docker

Docker's repository is an apt source, so a system upgrade also updates Docker Engine and the plugins:

sudo apt update
sudo apt upgrade

Containers with restart: unless-stopped start again when the Docker service restarts. To stay on one release, list the versions in the repository, install one as Docker's install docs show, then hold both packages so apt upgrade skips them:

apt list --all-versions docker-ce
sudo apt install docker-ce=YOUR_VERSION docker-ce-cli=YOUR_VERSION
sudo apt-mark hold docker-ce docker-ce-cli

Replace YOUR_VERSION with a full version string from the list, such as 5:29.8.2-1~ubuntu.24.04~noble. Run sudo apt-mark unhold docker-ce docker-ce-cli when you want to upgrade again.

The Ubuntu release upgrader turns off third-party sources such as Docker's, and Suites keeps the old codename. After you upgrade Ubuntu 24.04 to 26.04, rerun the Step 2 source command and sudo apt update.

Troubleshoot common Docker errors

SymptomCauseFix
permission denied while trying to connect to the docker API at unix:///var/run/docker.sockYour user is not in the docker group, or the SSH session started before you joined itRun Step 5, then log out and back in
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is runningThe Docker service is stopped or failed to startsudo systemctl start docker, then sudo journalctl -u docker -n 50. Run the Step 6 validate command to check daemon.json
Conflicting values set for option Signed-By from apt updateAn older docker.list from another guide points at a different key fileDelete /etc/apt/sources.list.d/docker.list and keep docker.sources
docker-compose: command not foundCompose v1 used docker-compose; the supported plugin runs as docker composeRun docker compose, with a space
Warning that the attribute version is obsoleteA compose file written for Compose v1Delete the version: line

FAQ

Does Docker work on Ubuntu 24.04 and 26.04?

Yes. Docker's install docs list Ubuntu 26.04, 24.04 and 22.04 LTS as supported releases. The same repository steps install Docker Engine 29.8.2 and Compose 5.6.0 on both 24.04 and 26.04 as of October 2026.

Is Docker still relevant in 2026?

Yes. Docker Engine 29.8.2 shipped on September 30, 2026, and Compose 5.6.0 on October 2, 2026. Self-hosted tools such as n8n and Open WebUI ship official Docker images.

Should I install docker.io or docker-ce on Ubuntu?

Install docker-ce from Docker's repository for current releases. Ubuntu's docker.io package was at 29.1.3 on 24.04 and 26.04 in October 2026, while Docker's repository had 29.8.2. The two conflict, so remove docker.io first.

Is Docker Compose installed with Docker?

Yes, when you install the docker-compose-plugin package from Docker's repository, as in Step 3. Compose then runs as docker compose, with a space. The older docker-compose v1 command is no longer supported.

How do I fix docker permission denied on Ubuntu?

Add your user to the docker group with sudo usermod -aG docker $USER, then log out and back in. Only root and docker group members can open /var/run/docker.sock. Group members get root-level access to the host.

Does UFW block Docker ports?

No. Docker routes published ports through its own NAT rules before UFW filters traffic, so a published port stays reachable when UFW denies it. Publish containers on 127.0.0.1 and use a reverse proxy on ports 80 and 443.

Do I need Docker Desktop on an Ubuntu server?

No. Docker Desktop is a graphical app that runs Docker Engine inside a VM. It needs KVM, a desktop environment such as GNOME, KDE or MATE, and at least 4 GB of RAM. A headless server needs Docker Engine and the Compose plugin only.

Next steps

Compare Arct Cloud Linux VPS plans.

This work is licensed under CC BY-NC-SA 4.0.