Docker

How to Install Docker on Debian and Ubuntu (Step-by-Step Guide)

Learn how to install Docker on Debian and Ubuntu step by step. This guide covers installation commands, repository setup, and post-installation tips for beginners.

9 min read
How to Install Docker on Debian and Ubuntu (Step-by-Step Guide)
Docker Hub Container Image Library

Introduction

What is Docker?

Docker is a containerization platform that packages applications with all necessary dependencies into standardized units called containers. These lightweight containers ensure consistent application behavior across diverse environments, from development through production.

Docker: Accelerated Container Application Development
Docker is a platform designed to help developers build, share, and run container applications. We handle the tedious setup, so you can focus on the code.

What is Docker Compose?

Docker Compose is a tool designed to simplify the management of multi-container Docker applications. By using a single YAML file, developers can define, configure, and launch multiple interconnected containers with a single command, streamlining complex application deployments.”

Docker Compose
Learn how to use Docker Compose to define and run multi-container applications with this detailed introduction to the tool.

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

For the installation process, I will switch to the root user by sudo -i or su root.

First, update your system to the latest packages and install some required software.

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

Update system packages


2. Install Docker and Docker Compose

2.1 Add Docker GPG Key

curl -sSL https://download.docker.com/linux/debian/gpg | gpg --dearmor > /usr/share/keyrings/docker-ce.gpg

Add Docker GPG Key

2.2 Add Docker Repository

Debian:

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

Add Debian Repository

Ubuntu:

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

Add Ubuntu Repository

2.3 Install

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

Install Docker and Docker Compose

We can now use docker version and docker compose version to check whether the latest version of Docker is installed:

# 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:     true
 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

Docker Version

And

# docker compose version
Docker Compose version v2.35.1

Docker Compose Version


3. Add IPv6 Support

By default, Docker does not support IPv6 when using the bridge network mode. However, you can enable native IPv6 support by modifying the Docker configuration file:

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": "fdb::/64",
    "experimental":true,
    "ip6tables":true
}
EOF

Modify Docker configuration

Then, restart the Docker service for the changes to take effect:

systemctl restart docker

Restart Docker

Now we can test IPv6 support. First, create a Docker network named ipv6_network:

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

Create a Network

Then, use the curlimages/curl image to perform a test:

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

Docker run curl to test IPv6

If your public IPv6 address is displayed in the terminal output, everything is working correctly.

⚠️
Warning: Some Docker containers may have compatibility issues with IPv6. Proceed with caution and test thoroughly in your environment.

4. Use Rootless Mode

Rootless mode allows the Docker daemon and containers to run as a non-root user, helping to mitigate potential security vulnerabilities in both the daemon and the containers.

First, install the required system packages:

apt install uidmap dbus-user-session docker-ce-rootless-extras

Install required packages

πŸ“’
Note: The docker-ce-rootless-extras package is already included in the Docker installation.

Next, create a new user named showfom or whatever you like:

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

Create User

Then, disable the currently running Docker services:

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

Disable Docker Services

Now, switch to the showfom user and run the dockerd-rootless-setuptool.sh script:

su - showfom
cd ~
/usr/bin/dockerd-rootless-setuptool.sh install

Install Docker Rootless

You should see output similar to the following:

$ 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/[email protected]/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

Sample Output

Add the following environment variables:

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

Add Docker environment variables

After that, enable the Docker service for the user:

systemctl --user enable --now docker
systemctl --user status docker

Enable Docker service and check status

You will see output like this:

$ 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/[email protected]/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).

Sample output

Congratulations! You’ve successfully installed Docker. Now, let’s move on to using Docker Compose.

πŸ‘Š
Note: Docker Rootless Mode comes with certain limitations, and some containers may not be compatible with it.

5. Use Docker Compose

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

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 Nginx example file and configuration

Next, create a file named compose.yaml:

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

Create Docker Compose file

Then, run the following command to pull the required container images:

docker compose pull

Pull container images

Once the Nginx image has been pulled, start the container using:

docker compose up -d

Start the container

You can use the docker ps command to check if all containers are running:

$ 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

Sample output

Now, test whether Nginx is working by accessing it over both IPv4 and IPv6.

IPv4:

curl localhost:8080 -4

Test IPv4

IPv6:

curl localhost:8080 -6

Test IPv6

If you see Hello World, everything is working correctly! You can shut down the container using the docker compose down command.