---
title: "Install Docker on Debian and Ubuntu"
tags: ['Docker', 'Debian', 'Ubuntu']
published: 2025-04-30 15:00:00
updated: 2025-05-05 08:41:59
excerpt: "Install Docker and Docker Compose on Debian Stable or Ubuntu LTS, add the official repository, enable IPv6, and try a small Compose stack."
---
> This file is the raw Markdown source of an m.ac post; the canonical version lives at https://m.ac/install-docker-debian-ubuntu/
> The full index of posts and pages is at https://m.ac/llms.txt — read the index first before crawling further.

## Introduction

### What is Docker?

[Docker](https://www.docker.com/) packages an application and its dependencies into containers so it runs the same way on your laptop, a test box, and a production server. Fewer "it works on my machine" mysteries, which is usually worth the trouble.

### What is Docker Compose?

[Docker Compose](https://docs.docker.com/compose/) manages multi-container Docker applications from one YAML file. You define the services once, then start them with a single command instead of juggling a small circus of `docker run` flags.

---

## 1. Prerequisites

Make sure you have installed [**Debian Stable**](https://www.debian.org/releases/) (either **oldstable** or **stable** releases, **sid** is not supported) or [**Ubuntu LTS**](https://releases.ubuntu.com/) (Interim releases not supported).

The commands below use `sudo`. If you prefer a root shell, run `sudo -i` first and drop `sudo` from the commands. The `docker` commands are not prefixed with `sudo`: add your user to the `docker` group (`sudo usermod -aG docker $USER`, then log out and back in), or add `sudo` yourself.

Update your system and install the required tools.

```bash
sudo apt update -y
sudo apt upgrade -y
sudo apt full-upgrade -y
sudo apt install curl vim wget gnupg dpkg apt-transport-https lsb-release ca-certificates -y
```

---

## 2. Install Docker and Docker Compose

### 2.1 Add Docker's GPG key

```bash
curl -sSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-ce.gpg
```

### 2.2 Add the Docker repository

Debian:

```bash
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-ce.gpg] https://download.docker.com/linux/debian $(lsb_release -sc) stable" | sudo tee /etc/apt/sources.list.d/docker.list
```

On Debian, you can also enable it with [extrepo](https://m.ac/use-extrepo-add-third-party-repositories-debian/):

```bash
sudo apt install extrepo -y
sudo extrepo enable docker-ce
```

Ubuntu:

```bash
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-ce.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -sc) stable" | sudo tee /etc/apt/sources.list.d/docker.list
```

### 2.3 Install

```bash
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
```

Check the installed Docker version:

```bash
$ docker version
Client: Docker Engine - Community
 Version:           29.6.1
 API version:       1.55
 Go version:        go1.26.4
 Git commit:        8900f1d
 Built:             Fri Jun 26 11:40:34 2026
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          29.6.1
  API version:      1.55 (minimum version 1.40)
  Go version:       go1.26.4
  Git commit:       8ec5ab3
  Built:            Fri Jun 26 11:40:34 2026
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          v2.2.5
  GitCommit:        e53c7c1516c3b2bff98eb76f1f4117477e6f4e66
 runc:
  Version:          1.3.6
  GitCommit:        v1.3.6-0-g491b69ba
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad
```

Check Docker Compose too:

```bash
$ docker compose version
Docker Compose version v5.3.
```

---

## 3. Add IPv6 support

By default, Docker does not enable IPv6 for bridge networks. To turn it on, add the IPv6 settings to the Docker daemon configuration.

> Warning: Some containers do not behave well with IPv6. Test the applications you plan to run before relying on it in production.

Create or edit the Docker daemon configuration file:

```bash
sudo bash -c 'cat > /etc/docker/daemon.json << EOF
{
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "20m",
        "max-file": "3"
    },
    "userland-proxy": false,
    "ipv6": true,
    "fixed-cidr-v6": "fdba::/64",
    "experimental":true,
    "ip6tables":true
}
EOF'
```

Restart Docker so it reads the new configuration:

```bash
sudo systemctl restart docker
```

Test IPv6 support by creating a Docker network named `ipv6_network`:

```bash
docker network create --ipv6 --subnet=fd00::/64 ipv6_network
```

Then use the `curlimages/curl` image to make an IPv6 request:

```bash
docker run --rm --network ipv6_network curlimages/curl:latest curl -6 ip.sb -s
```

If the command prints your public IPv6 address, Docker's IPv6 bridge networking is working.

---

## 4. Use rootless mode (optional)

[Rootless mode](https://docs.docker.com/engine/security/rootless/) lets the Docker daemon and containers run as a non-root user. That reduces the damage if something goes wrong, although it does not make containers magically harmless.

> Warning: Some containers do not work in rootless mode. If a container behaves strangely, test it with regular Docker before blaming the application.

Install the required system packages:

```bash
sudo apt install uidmap dbus-user-session docker-ce-rootless-extras
```

> Note: The `docker-ce-rootless-extras` package is already included in the Docker installation.

Create a new user named `showfom`, or use another name if you prefer:

```bash
sudo adduser --disabled-password --gecos "" --home /home/showfom showfom
```

Disable the currently running Docker services:

```bash
sudo systemctl disable --now docker.service docker.socket
sudo rm /var/run/docker.sock
```

Enable lingering for the `showfom` user, so its systemd user manager (and the rootless Docker daemon under it) starts at boot and survives logout:

```bash
sudo loginctl enable-linger showfom
```

Switch to the `showfom` user and run the `dockerd-rootless-setuptool.sh` script. Use `machinectl shell` rather than `su`: the setup script talks to `systemctl --user`, which needs a real systemd login session that `sudo su -` does not provide:

```bash
sudo machinectl shell showfom@
/usr/bin/dockerd-rootless-setuptool.sh install
```

If `machinectl` is missing, install it with `sudo apt install systemd-container -y`.

You should see output similar to the following:

```bash
$ dockerd-rootless-setuptool.sh install
[INFO] Creating /home/showfom/.config/systemd/user/docker.service
[INFO] starting systemd service docker.service
+ systemctl --user start docker.service
+ sleep 3
+ systemctl --user --no-pager --full status docker.service
● docker.service - Docker Application Container Engine (Rootless)
     Loaded: loaded (/home/showfom/.config/systemd/user/docker.service; disabled; preset: enabled)
     Active: active (running) since Wed 2025-04-30 14:53:19 UTC; 3s ago
       Docs: https://docs.docker.com/go/rootless/
   Main PID: 227068 (rootlesskit)
      Tasks: 39
     Memory: 58.2M
        CPU: 398ms
     CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/docker.service
             ├─227068 rootlesskit --state-dir=/run/user/1000/dockerd-rootless --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --propagation=rslave /usr/bin/dockerd-rootless.sh
             ├─227080 /proc/self/exe --state-dir=/run/user/1000/dockerd-rootless --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --propagation=rslave /usr/bin/dockerd-rootless.sh
             ├─227104 slirp4netns --mtu 65520 -r 3 --disable-host-loopback --enable-sandbox --enable-seccomp 227080 tap0
             ├─227112 dockerd
             └─227132 containerd --config /run/user/1000/docker/containerd/containerd.toml

Apr 30 14:53:19 m dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393381898Z" level=warning msg="WARNING: No io.max (rbps) support"
Apr 30 14:53:19 m dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393387228Z" level=warning msg="WARNING: No io.max (wbps) support"
Apr 30 14:53:19 m dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393393279Z" level=warning msg="WARNING: No io.max (riops) support"
Apr 30 14:53:19 m dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393398720Z" level=warning msg="WARNING: No io.max (wiops) support"
Apr 30 14:53:19 m dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393415330Z" level=info msg="Docker daemon" commit=01f442b containerd-snapshotter=false storage-driver=overlay2 version=28.1.1
Apr 30 14:53:19 m dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393481325Z" level=info msg="Initializing buildkit"
Apr 30 14:53:19 m dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.420441982Z" level=info msg="Completed buildkit initialization"
Apr 30 14:53:19 m dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.425685368Z" level=info msg="Daemon has completed initialization"
Apr 30 14:53:19 m dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.425749069Z" level=info msg="API listen on /run/user/1000/docker.sock"
Apr 30 14:53:19 m systemd[226972]: Started docker.service - Docker Application Container Engine (Rootless).
+ DOCKER_HOST=unix:///run/user/1000/docker.sock /usr/bin/docker version
Client: Docker Engine - Community
 Version:           28.1.1
 API version:       1.49
 Go version:        go1.23.8
 Git commit:        4eba377
 Built:             Fri Apr 18 09:52:57 2025
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          28.1.1
  API version:      1.49 (minimum version 1.24)
  Go version:       go1.23.8
  Git commit:       01f442b
  Built:            Fri Apr 18 09:52:57 2025
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.27
  GitCommit:        05044ec0a9a75232cad458027ca83437aae3f4da
 runc:
  Version:          1.2.5
  GitCommit:        v1.2.5-0-g59923ef
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
 rootlesskit:
  Version:          2.3.4
  ApiVersion:       1.1.1
  NetworkDriver:    slirp4netns
  PortDriver:       builtin
  StateDir:         /run/user/1000/dockerd-rootless
 slirp4netns:
  Version:          1.2.0
  GitCommit:        656041d45cfca7a4176f6b7eed9e4fe6c11e8383
+ systemctl --user enable docker.service
Created symlink /home/showfom/.config/systemd/user/default.target.wants/docker.service → /home/showfom/.config/systemd/user/docker.service.
[INFO] Installed docker.service successfully.
[INFO] To control docker.service, run: `systemctl --user (start|stop|restart) docker.service`
[INFO] To run docker.service on system startup, run: `sudo loginctl enable-linger showfom`

[INFO] Creating CLI context "rootless"
Successfully created context "rootless"
[INFO] Using CLI context "rootless"
Current context is now "rootless"

[INFO] Make sure the following environment variable(s) are set (or add them to ~/.bashrc):
export PATH=/usr/bin:$PATH

[INFO] Some applications may require the following environment variable too:
export DOCKER_HOST=unix:///run/user/1000/docker.sock
```

Add the following environment variables:

```bash
echo 'export PATH=/usr/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix:///run/user/1000/docker.sock' >> ~/.bashrc
source ~/.bashrc
```

Enable the Docker service for the user:

```bash
systemctl --user enable --now docker
systemctl --user status docker
```

You will see output like this:

```bash
$ systemctl --user status docker
● docker.service - Docker Application Container Engine (Rootless)
     Loaded: loaded (/home/showfom/.config/systemd/user/docker.service; enabled; preset: enabled)
     Active: active (running) since Wed 2025-04-30 14:53:19 UTC; 1min 13s ago
       Docs: https://docs.docker.com/go/rootless/
   Main PID: 227068 (rootlesskit)
      Tasks: 39
     Memory: 58.3M
        CPU: 463ms
     CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/docker.service
             ├─227068 rootlesskit --state-dir=/run/user/1000/dockerd-rootless --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto ->
             ├─227080 /proc/self/exe --state-dir=/run/user/1000/dockerd-rootless --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=aut>
             ├─227104 slirp4netns --mtu 65520 -r 3 --disable-host-loopback --enable-sandbox --enable-seccomp 227080 tap0
             ├─227112 dockerd
             └─227132 containerd --config /run/user/1000/docker/containerd/containerd.toml

Apr 30 14:53:19 debian dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393381898Z" level=warning msg="WARNING: No io.max (rbps) suppor>
Apr 30 14:53:19 debian dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393387228Z" level=warning msg="WARNING: No io.max (wbps) suppor>
Apr 30 14:53:19 debian dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393393279Z" level=warning msg="WARNING: No io.max (riops) suppo>
Apr 30 14:53:19 debian dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393398720Z" level=warning msg="WARNING: No io.max (wiops) suppo>
Apr 30 14:53:19 debian dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393415330Z" level=info msg="Docker daemon" commit=01f442b conta>
Apr 30 14:53:19 debian dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.393481325Z" level=info msg="Initializing buildkit"
Apr 30 14:53:19 debian dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.420441982Z" level=info msg="Completed buildkit initialization"
Apr 30 14:53:19 debian dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.425685368Z" level=info msg="Daemon has completed initialization"
Apr 30 14:53:19 debian dockerd-rootless.sh[227112]: time="2025-04-30T14:53:19.425749069Z" level=info msg="API listen on /run/user/1000/docker>
Apr 30 14:53:19 debian systemd[226972]: Started docker.service - Docker Application Container Engine (Rootless).
```

Docker is installed. Time to make sure Compose works too.

> Note: Docker rootless mode has limits, and some containers are not compatible with it.

---

## 5. Try Docker Compose

A tiny Nginx site is enough to check that Docker Compose is behaving.

Create a new directory, generate an `index.html` file, and add a simple Nginx configuration file named `default.conf`:

```bash
mkdir html

echo 'Hello World' > ./html/index.html

echo 'server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name  _;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}' > ./default.conf
```

Create a file named `compose.yaml`:

```bash
echo 'name: nginx

services:
  nginx:
    image: nginx:latest
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
      - ./default.conf:/etc/nginx/conf.d/default.conf
    restart: always
    networks:
      - nginx-network
      
networks:
  nginx-network:
    name: nginx-network
    enable_ipv6: true
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.0.0/24
          gateway: 192.168.0.1
        - subnet: "fdba::/64"
          gateway: "fdba::1"' > ./compose.yaml
```

Pull the required container images:

```bash
docker compose pull
```

After the Nginx image has been pulled, start the container:

```bash
docker compose up -d
```

Use `docker ps` to check that the container is running:

```bash
$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                                     NAMES
2e11699fbc47   nginx:latest   "/docker-entrypoint.…"   5 seconds ago   Up 5 seconds   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   nginx-nginx-1
```

Test Nginx over both IPv4 and IPv6.

IPv4:

```bash
curl localhost:8080 -4
```

IPv6:

```bash
curl localhost:8080 -6
```

If both commands print `Hello World`, Compose is doing its job. Stop the stack with `docker compose down`.
