m.ac

Install Docker on Debian and Ubuntu

621 words3 min read

#Introduction

##What is Docker?

Docker 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 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 (either oldstable or stable releases, sid is not supported) or Ubuntu LTS (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:

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 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.

Share