How to Install Docker on Ubuntu 22.04 (with Docker Compose)

Add Docker's official repository to an Ubuntu 22.04 server and install Docker Engine and Docker Compose, then set up doc…

Ubuntu 22.04Docker 29.9x86_64About 8 min

Verified guide

This guide was verified by creating a cloud server and running every command in it, from start to finish, exactly as written.

OS
Ubuntu 22.04.5 LTS · x86_64
Installed version
Docker 29.9.0 (docker-ce), Docker Compose 5.6.0, buildx 0.38.0, containerd 2.4.1
Test server
DigitalOcean · 1 vCPU / 1 GB RAM / 25 GB SSD (s-1vcpu-1gb, sgp1)
Last checked
2026-10-13

This guide contains affiliate links. If you sign up through them, Codesand may earn a commission. This doesn't affect the content or our assessments.

Add Docker's official repository to an Ubuntu 22.04 server and install Docker Engine and Docker Compose, then set up docker without sudo, run a container with Compose, deal with ports published by Docker bypassing the ufw firewall, and remove everything.

1. Requirements

  • One Ubuntu 22.04 LTS server (1 GB of memory or more recommended)
  • SSH access with a regular user account that has sudo rights
  • A PC for checking access from outside (used in the firewall step)

If you do not have a server, create the smallest DigitalOcean Droplet (1 vCPU · 1 GB). It is enough to install Docker and run containers. Every step in this guide was tested on a Droplet (1 vCPU · 1 GB memory · 25 GB SSD · Ubuntu 22.04 · Singapore) with a regular user account that has sudo rights.

2. Choose the package to install

The default Ubuntu 22.04 repository also has a Docker package (docker.io).

apt-cache policy docker.io | head -3
docker.io:
  Installed: (none)
  Candidate: 29.1.3-0ubuntu3~22.04.2

This guide installs docker-ce from the official repository that Docker publishes itself. This is the method in Docker's official documentation, and you get new releases as soon as they are out. Docker Compose is installed together as the docker compose command.

Packages from the default repository or other container tools can conflict if they are already installed, so remove them. Packages that are not installed show a line such as Package 'docker.io' is not installed, so not removed and are skipped, so you can run this on a fresh server too.

for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt remove -y $pkg; done

3. Add Docker's official repository

Install the tools needed to add the repository. They are usually already installed.

sudo apt update
sudo apt install -y ca-certificates curl

Download and save the signing key of the Docker repository. The signing key is used to check that the packages you download were made by Docker. -m 0755 sets the folder permissions, and a+r lets every user read the key file.

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

Register the repository address. The command in the Suites line is replaced with the server's Ubuntu codename. For Ubuntu 22.04, it is jammy.

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
Signed-By: /etc/apt/keyrings/docker.asc
EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: jammy
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc

Refresh the package list and check the version that will be installed. If Candidate is the version from the Docker repository, everything is fine.

sudo apt update
apt-cache policy docker-ce | head -3
docker-ce:
  Installed: (none)
  Candidate: 5:29.9.0-1~ubuntu.22.04~jammy

4. Install Docker

Install Docker Engine (docker-ce), the command-line tool (docker-ce-cli), the container runtime (containerd.io), the image build tool (docker-buildx-plugin), and Docker Compose (docker-compose-plugin) together.

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

Check the installed versions.

docker --version
Docker version 29.9.0, build f415da8
docker compose version
Docker Compose version v5.6.0
docker buildx version
github.com/docker/buildx v0.38.0 0dbe87f36ed472fe36b6796c3f70b4210a731fc5

5. Check the service status

When installation finishes, the Docker service starts automatically and is registered to start again when the server reboots.

sudo systemctl status docker --no-pager | head -3
● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2026-10-13 02:15:35 UTC; 9s ago

Check that both docker and the container runtime containerd are set to start automatically.

systemctl is-enabled docker containerd
enabled
enabled

Download and run the test image hello-world. If the message below appears, Docker works.

sudo docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

6. Use docker without sudo

Right now, running it without sudo gives the error below. The docker command uses the Docker service's socket file (/var/run/docker.sock), and only root and the docker group can use that file.

docker run hello-world
permission denied while trying to connect to the docker API at unix:///var/run/docker.sock

Add the current user to the docker group. -aG adds the group while keeping the existing groups, and $USER is the name of the user who is logged in now.

sudo usermod -aG docker $USER

The group change takes effect after you log in again. Close the SSH connection, connect again, and check. You are done when docker appears in the group list.

groups
devuser sudo docker
docker run hello-world | head -3

Hello from Docker!
This message shows that your installation appears to be working correctly.

This is the list of containers run so far. -a also shows containers that have finished running.

docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED         STATUS                              PORTS     NAMES
0590eaaddfa5   hello-world   "/hello"   1 second ago    Exited (0) Less than a second ago             sweet_banach
19e77a86a647   hello-world   "/hello"   5 seconds ago   Exited (0) 4 seconds ago                      gifted_wu

7. Run a container with Docker Compose

Run Docker Compose as docker compose (with a space). The old docker-compose (with a hyphen) command is not installed. If another guide says docker-compose up, run docker compose up instead.

Create a compose.yaml file that runs the nginx web server. "8080:80" connects port 8080 on the server to port 80 in the container.

mkdir ~/hello-compose && cd ~/hello-compose
cat > compose.yaml <<EOF
services:
  web:
    image: nginx:1.29
    ports:
      - "8080:80"
EOF

Start the container. -d runs it in the background without holding the terminal. The first time, several lines appear while the nginx image downloads, and the lines below appear at the end.

docker compose up -d
 Container hello-compose-web-1 Creating
 Container hello-compose-web-1 Created
 Container hello-compose-web-1 Starting
 Container hello-compose-web-1 Started

Check the container status and connect from inside the server. If Welcome to nginx! appears, everything works.

docker compose ps
NAME                  IMAGE        COMMAND                  SERVICE   CREATED         STATUS        PORTS
hello-compose-web-1   nginx:1.29   "/docker-entrypoint.…"   web       3 seconds ago   Up 1 second   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
curl -s http://localhost:8080 | grep -o "<title>.*</title>"
<title>Welcome to nginx!</title>

8. The firewall (ufw) and Docker ports

Turn on the ufw firewall and allow only SSH. Always allow SSH before turning on the firewall. If you turn on the firewall without allowing SSH, your connection to the server is cut off. --force turns on the firewall without asking for confirmation.

sudo ufw allow OpenSSH
Rules updated
Rules updated (v6)
sudo ufw --force enable
Firewall is active and enabled on system startup
sudo ufw status
Status: active

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

The firewall has no rule for port 8080. Yet when you connect to port 8080 of the server from another PC, the nginx page opens. Replace 203.0.113.20 with your server's IP.

curl -sS --max-time 10 http://203.0.113.20:8080 | grep -o "<title>.*</title>"
<title>Welcome to nginx!</title>

This is because Docker adds its own rules to the Linux firewall (iptables) when it publishes a port, and those rules are applied before the ufw rules. You can see the rule Docker added for port 8080 with the command below.

sudo iptables -t nat -L DOCKER -n | grep 8080
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.18.0.2:80

For a service that only needs to be reached from inside the server, put 127.0.0.1: in front of the port so it is bound only to the server's own address. sed -i changes the file content and saves it right away. After changing the file, running docker compose up -d again recreates the container with the new settings.

sed -i 's/"8080:80"/"127.0.0.1:8080:80"/' compose.yaml
cat compose.yaml
services:
  web:
    image: nginx:1.29
    ports:
      - "127.0.0.1:8080:80"
docker compose up -d
 Container hello-compose-web-1 Recreate
 Container hello-compose-web-1 Recreated
 Container hello-compose-web-1 Starting
 Container hello-compose-web-1 Started

Port 8080 is now open only on 127.0.0.1, and it still works from inside the server.

sudo ss -tlnp | grep 8080
LISTEN 0      4096       127.0.0.1:8080      0.0.0.0:*    users:(("docker-proxy",pid=5816,fd=8))
curl -s http://localhost:8080 | grep -o "<title>.*</title>"
<title>Welcome to nginx!</title>

If you connect from another PC again, the connection now fails.

curl -sS --max-time 10 http://203.0.113.20:8080
curl: (28) Connection timed out after 10005 milliseconds

For a web service that must be public, we recommend putting a web server such as Nginx in front and binding container ports only to 127.0.0.1.

9. Troubleshooting

permission denied while trying to connect to the docker API at unix:///var/run/docker.sock

The current user is not in the docker group. Run sudo usermod -aG docker $USER as in step 6, then close the SSH connection and connect again. The group change does not take effect until you connect again. Check that docker appears in the output of groups.

docker-compose: command not found

With the method in this guide, Compose runs as the docker compose (with a space) command. Run docker compose up -d instead of docker-compose up -d.

A container port is reachable from outside even though ufw blocks it

The rules Docker adds directly to iptables are applied before ufw. As in step 8, change the port in compose.yaml to the form "127.0.0.1:8080:80" so that it can be reached only from inside the server.

10. Uninstall

First, stop and remove the running containers. docker compose down removes the containers and network created from compose.yaml.

cd ~/hello-compose
docker compose down
 Container hello-compose-web-1 Stopping
 Container hello-compose-web-1 Stopped
 Container hello-compose-web-1 Removing
 Container hello-compose-web-1 Removed
 Network hello-compose_default Removing
 Network hello-compose_default Removed

Remove the Docker packages and all data. Images, containers, and volumes are all deleted, so back up any data you need first. docker-ce-rootless-extras is a package that was installed together with Docker.

sudo apt purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras
sudo rm -rf /var/lib/docker /var/lib/containerd

Also delete the repository and signing key added in step 3, and refresh the package list.

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

Check that no Docker packages remain. If 0 appears, everything has been removed.

dpkg -l | grep -c -E "docker|containerd"
0

Try it yourself

No server yet? You can spin up an Ubuntu server in a few minutes with one of these providers. We verified this guide on DigitalOcean.