#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.
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
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:
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:
sudo apt install extrepo -y
sudo extrepo enable docker-ce
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" | sudo tee /etc/apt/sources.list.d/docker.list
##2.3 Install
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Check the installed Docker version:
$ 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:
$ 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:
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:
sudo systemctl restart docker
Test IPv6 support by creating a Docker network named ipv6_network:
docker network create --ipv6 --subnet=fd00::/64 ipv6_network
Then use the curlimages/curl image to make an IPv6 request:
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:
sudo apt install uidmap dbus-user-session docker-ce-rootless-extras
Note: The
docker-ce-rootless-extraspackage is already included in the Docker installation.
Create a new user named showfom, or use another name if you prefer:
sudo adduser --disabled-password --gecos "" --home /home/showfom showfom
Disable the currently running Docker services:
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:
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:
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:
$ 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:
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:
systemctl --user enable --now docker
systemctl --user status docker
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/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:
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:
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:
docker compose pull
After the Nginx image has been pulled, start the container:
docker compose up -d
Use docker ps to check that the container is 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
Test Nginx over both IPv4 and IPv6.
IPv4:
curl localhost:8080 -4
IPv6:
curl localhost:8080 -6
If both commands print Hello World, Compose is doing its job. Stop the stack with docker compose down.