Skip to content
selfhostr

guide · 6 min read

Installing Docker

The official repository rather than Ubuntu's, the compose plugin, and the six commands that are enough to run a home lab.

Docker is what makes a home lab sustainable. Without it, installing six services means six ways to configure, six ways to update, and six chances to break the one next door. With it, each service lives in its own box, and one text file describes the whole thing.

This guide starts from a freshly installed Ubuntu Server — see Installing Ubuntu Server if that is not done yet. Ten minutes.

Do not take Ubuntu's

apt install docker.io works and installs a version packaged by Ubuntu, often months behind, without the compose plugin. Docker's own repository takes five more lines, once, and puts you on the project's own updates.

That is the only decision in this guide. The rest is mechanical.

Step 1 — Remove what is lying around

On a new machine this finds nothing. On a machine that has seen use, it avoids package conflicts whose error messages never name the cause:

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

Your data and images are untouched: they live in /var/lib/docker, which this command does not remove.

Step 2 — Add Docker's repository

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

Then the repository itself:

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

sudo apt update

Two details that explain why this differs from most guides online:

  • The file is a .sources, not a line in docker.list. Docker moved to that format; both work, but following the current documentation beats following the 2022 one.
  • The Suites line does not hardcode your release: it reads it from /etc/os-release. On 26.04 that gives resolute.

If apt update complains it cannot find the repository, Docker has probably not published for your Ubuntu release yet — common in the weeks after a release. Check that your codename is listed at download.docker.com/linux/ubuntu/dists. As of 28 July 2026, resolute is there, for amd64 and arm64.

Step 3 — Install

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

Five packages, two of which deserve naming:

  • docker-compose-plugin provides docker compose, two words. The old hyphenated docker-compose was a separate Python program, now retired. A guide that has you type docker-compose up is over four years old, and the rest of its advice has aged the same way.
  • docker-buildx-plugin builds images. You will not need it immediately; it is on the list because its absence produces baffling warnings on your first build.

Step 4 — Check

sudo docker run hello-world

Docker pulls a tiny image, runs it, prints a paragraph and stops. If you see it, everything is in place.

docker --version
docker compose version

The second command is the real test: it is the one that fails when the compose plugin is missing, and you only notice at the moment you need it.

Step 5 — Drop the sudo

By default only root talks to the Docker daemon. To avoid sudo on every command:

sudo usermod -aG docker $USER

Log out and back in so the group membership is re-evaluated. An SSH session already open will not see it — the number one cause of "I ran the command and it still does not work".

One thing worth knowing, and Docker's own documentation says it: the docker group is equivalent to root. Anyone who can start a container can mount any directory of the machine into it. On a personal server with a single user that is of no consequence; on a shared machine it is worth knowing before you add someone to the group.

Step 6 — Your first docker-compose.yml

This site's builder hands you a ready-made file. Here is its mechanism, so you know what you are pasting.

One folder per stack:

mkdir -p ~/docker && cd ~/docker
nano docker-compose.yml
services:
  example:
    image: nginx:alpine
    container_name: example
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./example/data:/usr/share/nginx/html

Four lines decide everything:

  • image — where the software comes from. Prefer an explicit version (nginx:1.29) over latest for anything you care about: latest moves under you on the next pull.
  • restart: unless-stopped — the container comes back after a reboot, but not if you stopped it yourself. That is the value you want on an always-on server.
  • ports"8080:80" reads host:container. The first number is what you type in your browser, the second is what the software listens on inside its box. You only ever change the first.
  • volumes./example/data is a path on the machine, relative to the file's folder. Anything not in a volume disappears the first time the container is updated. It is the one mistake in this guide that costs data.

Then:

docker compose up -d

-d detaches: the command returns and the containers keep running. Without it, closing the terminal stops everything.

The six commands that are enough

docker compose up -d          # start, or apply a change to the file
docker compose down           # stop and remove the containers
docker compose ps             # what is running, in this folder
docker compose logs -f        # logs, following — Ctrl+C to leave
docker compose pull           # fetch newer images
docker stats                  # what it is consuming, live

Updating a stack is three commands in this order:

docker compose pull
docker compose up -d
docker image prune -f

The last one removes images nothing uses any more. Without it, a 250 GB system disk fills up in a year with versions nothing references — and the resulting error talks about a full disk, never about Docker.

What will happen to you, and what to do about it

permission denied on files in a volume. The container runs as a user that is not you. Most images accept PUID and PGID in their environment section: give them the output of id -u and id -g.

port is already allocated. Two services want the same host port. Change the first number in ports, never the second. sudo ss -tulpn | grep 8080 says who holds it.

A container restarting in a loop. docker compose logs will say why. Nine times out of ten it is a volume path that does not exist, or a required environment variable that is missing.

The disk filling up. docker system df shows where. docker system prune -a cleans it — careful, -a also removes unused images, which you will have to download again.

Next

Your services are running, but only from home. To reach them from outside there are two roads: open a port on your router, or use a tunnel that opens none — and the second is the one this site recommends.

Video

Pi My Life Up installs Docker on Ubuntu from the official repository and runs a first container — the part where you see what the commands actually produce.

Nothing is sent to YouTube until you press play. This site sets no cookies of its own.Watch on YouTube