Module 9 · 9.2
English
Installing Docker and your first container
Module 9: Docker and docker compose · Reading time: 11 minutes
Learning objective: After this lesson you can install Docker Engine from the official repository, start, list, look inside and clean up containers, and you know why the docker group is equivalent to root.
Key concepts: Docker Engine, Docker Desktop, official apt repository, Debian's docker.io, docker group, rootless mode, docker run, -d, -p, -v, --name, docker ps, docker logs, docker exec, docker stop, docker rm, docker image prune, docker system df
Which Docker?
On a server you install Docker Engine: the background service (dockerd) plus the docker command. Docker Desktop is something else: a windowed program for Windows, macOS and Linux desktops. It doesn't belong on a server.
There are two clean sources for Docker Engine:
- Docker's own repository (
download.docker.com). Always the latest version, including thedocker composeplug-in you'll use in lesson 9.3. This is what the Docker documentation describes and what this course uses. - The
docker.iopackage from Debian or Ubuntu. Older, but kept up to date by the distribution through your normal security updates (lesson 4.2). Fine if you'd rather not have anything from outside the distribution; just check thatdocker compose(with a space) is available.
Pick one. Mixing them causes conflicts, which is why the Docker documentation tells you to remove unofficial packages such as docker.io, docker-compose and podman-docker first.
Installing from Docker's repository
These are the steps from the Docker documentation for Debian, with the key stored separately and its own Signed-By (lesson 4.1). Take a snapshot first if that makes you feel more comfortable.
anna@web1:~$ sudo apt update
anna@web1:~$ sudo apt install ca-certificates curl
anna@web1:~$ sudo install -m 0755 -d /etc/apt/keyrings
anna@web1:~$ sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
anna@web1:~$ sudo chmod a+r /etc/apt/keyrings/docker.ascThen the source file /etc/apt/sources.list.d/docker.sources (deb822 format):
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: trixie
Components: stable
Architectures: amd64
Signed-By: /etc/apt/keyrings/docker.ascFor Suites you fill in the codename of your release (cat /etc/os-release, lesson 4.3) and for Architectures the output of dpkg --print-architecture. On Ubuntu the URL is https://download.docker.com/linux/ubuntu and the suite is, for example, noble.
Then install:
anna@web1:~$ sudo apt update
anna@web1:~$ sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
anna@web1:~$ sudo docker run hello-world
...
Hello from Docker!
This message shows that your installation appears to be working correctly.On Debian and Ubuntu, Docker starts automatically after installation and after every reboot (systemctl status docker, lesson 6.1).
Note: by default, unattended-upgrades (lesson 4.2) does not update packages from this repository automatically. Include sudo apt update && sudo apt upgrade for Docker in your maintenance routine (lesson 10.4), or deliberately add the repository to your automatic updates.
The docker group is root
All over the internet you'll read that you should add yourself to the docker group so you no longer have to type sudo:
anna@web1:~$ sudo usermod -aG docker annaOnly do that if you understand what it means. The Docker documentation is clear about it: the docker group grants root-level privileges. Anyone who can control the Docker service can start a container with the host's entire disk mounted inside it:
docker run -it -v /:/host debian chroot /hostand is then root on your server, without a password and without a sudo log entry. As the Docker documentation puts it: only trusted users should be allowed to control the Docker service.
For a server you manage on your own, it's a trade-off: convenience versus an extra route to root. Safer is to simply keep typing sudo docker ...; then every command ends up in the sudo log (lesson 3.2). In any case, never add a user to the group who shouldn't be root, such as a service user for a website.
Rootless mode
Docker can also run without root: the service and the containers then run under an ordinary user, with their own user namespace. You install it as that user with dockerd-rootless-setuptool.sh install (from the docker-ce-rootless-extras package; the uidmap package is needed beforehand). A breach in a container then doesn't give anyone root on the host. There are limitations and it's harder to set up; in this course we use the regular installation and keep it safe with the rules from this module.
A first real container
We'll start an nginx web server in a container, reachable only from the server itself:
anna@web1:~$ sudo docker run -d --name proef -p 127.0.0.1:8080:80 nginx:stable
Unable to find image 'nginx:stable' locally
stable: Pulling from library/nginx
...
3f1c9e0d7a2b...The parts:
| Part | Meaning |
|---|---|
-d | Run in the background (detached) |
--name proef | A name, so you don't have to work with long IDs |
-p 127.0.0.1:8080:80 | Port 80 in the container becomes port 8080 on the host, on loopback only |
nginx:stable | The official nginx image, tag stable |
Check it:
anna@web1:~$ sudo docker ps
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
3f1c9e0d7a2b nginx:stable "/docker-entrypoint.…" Up 5 seconds 127.0.0.1:8080->80/tcp proef
anna@web1:~$ curl -I http://127.0.0.1:8080
HTTP/1.1 200 OK
Server: nginx/1.28.0
anna@web1:~$ sudo ss -tlnp | grep 8080
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("docker-proxy",pid=2211,fd=7))ss (lesson 5.1) confirms it: only on 127.0.0.1. Had you typed -p 8080:80, you would have seen 0.0.0.0:8080 and [::]:8080, and your container would be reachable from the internet, even if ufw doesn't allow port 8080 (lesson 9.3). The version in the Server header may differ on your server.
Looking around and stepping in
anna@web1:~$ sudo docker logs proef # what the program wrote (stdout and stderr)
anna@web1:~$ sudo docker logs -f --tail 20 proef # last 20 lines and keep following
anna@web1:~$ sudo docker exec -it proef sh # a shell in the running container
# cat /etc/os-release
# exit
anna@web1:~$ sudo docker stop proef
anna@web1:~$ sudo docker start proef
anna@web1:~$ sudo docker rm -f proef # stop and removedocker logs is the first place to look when a container doesn't do what you expect, just like journalctl for ordinary services (lesson 6.2). With docker exec you look inside, but it's best not to change anything there: changes inside the container disappear with the next update (lesson 9.1).
Cleaning up
Images and stopped containers stay around until you clean them up. On a VPS with a small disk, that quietly adds up.
anna@web1:~$ sudo docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 7 3 1.9GB 1.2GB (63%)
Containers 3 3 12MB 0B (0%)
Local Volumes 2 2 180MB 0B (0%)
Build Cache 0 0 0B 0B
anna@web1:~$ sudo docker image prunedocker image prune removes unnamed images that no longer belong to anything; with -a it also removes all images not used by any container. Be careful with docker system prune --volumes: it also removes anonymous volumes (without a name) that aren't attached to a container at that moment, and docker volume prune -a even removes named volumes. Those may hold your data. So always give volumes a name or use a bind mount (lesson 9.1), and check first with docker volume ls.
Container logs can grow too. By default Docker stores them in JSON files with no size limit. You set a limit in /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}Then run sudo systemctl restart docker; the setting only applies to newly created containers, so recreate existing ones.
In practice
Docker works on a WebOké VPS the same way as with any other provider: it's software inside your own server that you install and update yourself. On a small VPS, check your disk space regularly with docker system df and df -h (lesson 1.4).
Summary
- Install Docker Engine from Docker's repository (with its own key and
Signed-By) ordocker.iofrom the distribution, not both. - The docker group is equivalent to root;
sudo dockerleaves a trail in the sudo log. Rootless mode is a safer but more limited alternative. docker run -d --name ... -p 127.0.0.1:host:container image:tagstarts a container that is only reachable locally; check withss.docker ps,logs,exec,stop,rmare your everyday tools.- Clean up images with
docker image prune, limit container logs (unlimited by default), and be sparing with--volumes.
Quiz
Exercise
Exercise O9.2: Your first container. Install Docker Engine on your server as described in this lesson (or on a test machine). Start an nginx container that listens only on 127.0.0.1:8080, check it with docker ps, curl -I and ss -tlnp, look at the logs, and then clean it up. Answer these questions: which source did you install Docker from, what did ss show for port 8080, and did you add yourself to the docker group (why or why not)?
Exercise O9.2: Your first container
You do this exercise yourself; no one checks it. Tick off what you've done and mark the exercise as done.
If it comes from Docker's own repository, unattended-upgrades doesn't update it by default; docker.io from the distribution it does. ss should show 127.0.0.1:8080, not 0.0.0.0:8080. The docker group gives root rights; with sudo docker every command stays in the sudo log.
Your ticks and notes only stay in this browser; we don't send them anywhere. If you're signed in, we only save in My courses that you've done the exercise.
Further reading
- Docker documentation, Install Docker Engine on Debian: https://docs.docker.com/engine/install/debian/
- Docker documentation, Install Docker Engine on Ubuntu: https://docs.docker.com/engine/install/ubuntu/
- Docker documentation, Linux post-installation steps: https://docs.docker.com/engine/install/linux-postinstall/
- Docker documentation, Docker security (Docker daemon attack surface): https://docs.docker.com/engine/security/
- Docker documentation, Rootless mode: https://docs.docker.com/engine/security/rootless/
- Docker documentation, JSON File logging driver: https://docs.docker.com/engine/logging/drivers/json-file/
