Skip to main content
Xsec

Installing Docker on Debian

Published on 9 min read

Updated on

Part 1 of 6
In this series40 min read in total
  1. Installing Docker on Debian
  2. Install an OpenSSL self-signed certificate with nginx
  3. Create a DNS server on Debian
  4. Install Nginx + Webdav on debian 11
  5. Create an OpenVPN server on debian
  6. SSH connection with public key

Docker runs an application in an isolated environment, with its own libraries and its own files, but on the host machine’s kernel. That last point is the whole difference with a virtual machine: no second operating system to boot, hence a container that starts in one second and weighs a few dozen megabytes.

Virtual machineContainer
Kernelits own, completethe host’s, shared
Startuptens of secondsunder a second
Disk footprintseveral gigabytestens of megabytes
Isolationhardware-level, very strongthrough kernel namespaces
SummaryWhat you will be able to do by the end
  • Install Docker from the official repository, with a verified signature.
  • Use the docker command without sudo, knowing what that implies.
  • Run a container, expose it on a port and attach a host folder to it.
  • List, inspect, stop and delete containers and their images.
An issue with sudo?
DangerPlease don't use the root account

If you configure your server directly as root, don’t forget to remove sudo from each command. If you set a password for the root account, the sudo command won’t be accepted. Connect directly as root to execute commands. You can also reinstall your system leaving the root password empty during installation. sudo will install and work properly.

Step 1: install Docker from the official repository

Debian does ship a docker.io package in its repositories, but it often trails several versions behind and does not provide the modern plugins. The official Docker repository takes four more steps and yields an up-to-date, complete installation.

  1. Install the prerequisites

    Terminal window
    sudo apt update && sudo apt install ca-certificates curl

    ca-certificates provides the certificate authorities needed to validate the repository’s HTTPS, curl downloads the key.

  2. Fetch the signing key

    Terminal window
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc
    ExplanationWhat this key is for

    Every package in the Docker repository is cryptographically signed. Without this public key, apt refuses to install anything, because it cannot verify that the package really comes from Docker and was not tampered with on the way. This is what protects against a compromised repository or a network interception.

    Noteapt-key is deprecated

    Tutorials predating Debian 11 use apt-key add, which installs the key into a global keyring: it then becomes entitled to sign any package on the system, including those from the Debian repositories. The current method stores the key in /etc/apt/keyrings and binds it explicitly to a single repository with the signed-by option, which limits its reach.

  3. Declare the repository

    Terminal window
    echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
    $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

    The two substitutions make the line portable: dpkg --print-architecture returns the machine architecture (amd64, arm64), and VERSION_CODENAME the Debian release name (bookworm, trixie). You can copy this block as is onto any machine.

  4. Install the packages

    Terminal window
    sudo apt update && sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    PackageRole
    docker-cethe daemon, which runs the containers
    docker-ce-clithe docker command you type
    containerd.iothe low-level runtime used by the daemon
    docker-buildx-pluginimage building, including multi-architecture
    docker-compose-pluginthe docker compose command, to describe several containers in one file
    TipClient and daemon are two distinct things

    The docker command does nothing by itself: it passes your orders to the daemon, which runs in the background as root. That separation explains why sudo is needed by default, and it is the subject of the next step.

  5. Check that the service runs

    Terminal window
    sudo systemctl enable --now docker
    sudo systemctl status docker

    enable --now starts the service and schedules it at boot, which avoids finding out after a reboot that the containers never came back.

  6. Validate the installation

    Terminal window
    sudo docker run hello-world
    sudo docker run hello-world
    Hello from Docker!
    This message shows that your installation appears to be working correctly.
    To generate this message, Docker took the following steps:
    1. The Docker client contacted the Docker daemon.
    2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
    3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
    4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.
    To try something more ambitious, you can run an Ubuntu container with:
    $ docker run -it ubuntu bash
    Share images, automate workflows, and more with a free Docker ID:
    https://hub.docker.com/
    For more examples and ideas, visit:
    https://docs.docker.com/get-started/

    That message is not a plain “it works”: it describes exactly the chain Docker just walked, from client to daemon, daemon to registry, then registry to container. Seeing it means the four installed components talk to each other correctly.

Typing sudo in front of every command quickly gets tiresome. It can be fixed, provided you measure what it entails.

Step 2: use Docker without sudo

Terminal window
sudo usermod -aG docker $USER
newgrp docker

The first line adds your account to the docker group, the second applies the change to the current session without having to log out.

DangerThe docker group is equivalent to root

A member of the docker group can start a container that mounts the host’s root filesystem read-write, for instance docker run -v /:/host -it debian chroot /host. They then get full access to the system, with no password and without going through sudo, hence with no trace in the sudo logs.

Adding a user to that group is therefore exactly the same as granting them root. On a personal workstation that is a reasonable trade-off. On a shared server, keep sudo docker, or look into Docker’s rootless mode, which runs the daemon under your own account.

Once the group is in place, the command works directly:

Terminal window
docker run hello-world

Now on to a container that provides a real service.

Step 3: run a useful container

As an example, we will run an Apache web server.

Terminal window
docker run -d --name docker-apache -v /var/www/:/usr/local/apache2/htdocs/ -p 3000:80 httpd

Breaking down each part:

OptionRole
docker runcreates and starts a container, downloading the image if absent
-ddetached mode, the container runs in the background and gives the prompt back
--name docker-apachenames the container, otherwise Docker generates a random name
-v /var/www/:/usr/local/apache2/htdocs/mounts a host folder inside the container, host_path:container_path
-p 3000:80publishes a port, host_port:container_port
httpdthe image name, here Apache, replaceable with nginx

The site is then reachable at http://MACHINE_IP:3000, and its content is changed simply by editing /var/www on the host.

ExplanationWhy the volume changes everything

Without -v, everything the container writes disappears with it: a container is disposable by design. A volume ties a host folder to a container path, which makes the data live outside the container’s lifecycle. You can then destroy and recreate the container, or move to a newer image version, without losing the content.

WarningThe left-hand port is the host's

-p 3000:80 means “port 3000 on the machine leads to port 80 in the container”. Swapping the two is the most common mistake, and it produces a service that looks started but stays unreachable. The right-hand port is imposed by the image, the left-hand one is your choice.

TipVolume permissions are not translated

The container sees files with their numeric user and group identifiers, not with their names. A file owned by UID 1000 on the host will appear as owned by UID 1000 inside the container, whatever username carries that identifier on either side. This is the classic source of “permission denied” on a volume.

Day-to-day container handling

A handful of commands covers most of the operational work.

CommandWhat it does
docker pslists running containers
docker ps -alists every container, stopped ones included
docker logs -f docker-apacheshows and follows the container output
docker exec -it docker-apache bashopens a shell inside the container
docker stop docker-apachestops the container cleanly
docker start docker-apacherestarts it with the same configuration
docker rm docker-apachedeletes it, it must be stopped first
docker imageslists downloaded images
docker system dfshows the disk space Docker occupies
ImportantA stopped container is not a deleted one

docker stop freezes the container but keeps it, with its filesystem. That is handy for restarting it, and it is also why docker ps sometimes seems to lie: it only shows running containers. Use docker ps -a to see what is actually lying around, and docker system prune to clean up once you know what you are deleting.

TipOptions are frozen at creation time

-p, -v and --name are interpreted by docker run at creation time. They cannot be changed afterwards on an existing container: changing a published port means deleting the container and recreating it. That is harmless, provided the data lives in a volume, as covered above.

The Docker Hub

The Docker Hub is the public image registry, the equivalent of an app store. It holds official images, community-published ones, and above all the documentation for each of them.

Home page

Every image exposes its versions as tags, along with the available base variants.

nginx image

An image’s documentation states which paths to mount as volumes, which ports are exposed and which environment variables are expected. It is the first thing to read before putting a docker run command together.

Documentation

WarningNot all images are equal

A community-published image can contain anything and may be maintained by nobody. Prefer official images, recognisable by their badge, check the date of the last publication, and pin an explicit version in your commands: httpd:2.4 rather than httpd, which points at latest and can change major version without warning.

The installation works and you can drive a container. The following articles in the series build on that base to host real services.

Use with an AI

Actions