Table des matières

docker rootless sous ubuntu

Date août 2023
2024/07/31
Temps 10 minutes
Difficulté ★★☆☆☆
Type how-to
OS ✓ Ubuntu GNU/Linux 24.04 LTS «noble numbat»
✓ Ubuntu GNU/Linux 22.04 LTS «jammy jellyfish»
cible sysadmins tout venant
Références docker : rootless (anglais)
docker : installation sur ubuntu (anglais)
docker : installation (anglais)
github : Générer une clé ssh et l'ajouter à l'agent
digital ocean: configurer une authentication par clé ssh

Le plus simple est de suivre les documentations citées en référence.

Mais j'ai noté un ou deux trucs. Ici, un résumé.

Commandes à passer sous le compte root

Pour économiser mes petits doigts, je ne tape sudo qu'une seule fois, pour devenir root. Si vous ne faites pas cela, vous préfixerez les commandes par sudo quand il le faudra.

sudo -i

Créer un utilisateur non privilégié

Utilisateur système, volontairement sans sudo et à mot de passe bloqué. Connexion uniquement via ssh et des clés.

  1. Création du compte système, ajout dans /etc/subuid et /etc/subgid.
    nouveauCompte=dockeruser
     
    adduser --system --gecos "Docker rootless account" --group \
                 --home /home/$nouveauCompte --shell /bin/bash $nouveauCompte
    chpasswd -e <<<"${nouveauCompte}:*" &&
    IFS=: read _ base taille < <( sort -b -t: -k 2,2n /etc/subuid | tail -1 ) &&
    echo ${nouveauCompte}:$(( base + taille )):65536 >> /etc/subuid &&
    IFS=: read _ base taille < <( sort -b -t: -k 2,2n /etc/subgid | tail -1 ) &&
    echo ${nouveauCompte}:$(( base + taille )):65536 >> /etc/subgid 
  2. Copie des clés ssh autorisées de root (très personnel et donc très optionnel). M'enfin, comme mot de passe est verrouillé, il faudra bien des clés ssh autorisées… Copiez les votres, quoi1).
    install -d -m 0700 /home/${nouveauCompte}/.ssh
    cp -a /root/.ssh/authorized_keys /home/${nouveauCompte}/.ssh
  3. Préconfigurer le démon docker rootless avec un fichier daemon.json. Là encore c'est très personnel – et donc très optionnel – mais ça donne une idée de ce qu'on peut faire facilement.
    install -d /home/${nouveauCompte}/.config/docker
    cat > /home/${nouveauCompte}/.config/docker/daemon.json <<'EOH'
    {
      "default-address-pools": [
        {
          "base": "10.14.0.0/16",
          "size": 24
        }
      ],
      "log-driver": "json-file",
      "log-opts": {
        "max-size": "10m",
        "max-file": "3" 
      }
    }
    EOH
  4. Et finalement, corriger les propriétaires et groupes des fichiers.
    chown -hR ${nouveauCompte}: /home/${nouveauCompte}

Installation des commandes docker

Supprimer toute trace de docker qui n'est pas celui livré par docker soi même.

for pkg in \
    docker.io docker-doc docker-compose docker-compose-v2 \
    podman-docker containerd runc \
; do
  apt-get purge $pkg
done

Et puis installation

dpkg -l dbus-user-session | grep -q ^.i || sudo apt-get install dbus-user-session
dpkg -l uidmap | grep -q ^.i || sudo apt-get install uidmap
apt-get install ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
 
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
     -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
  "deb [arch=$(dpkg --print-architecture) \
  signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" |
  tee /etc/apt/sources.list.d/docker.list > /dev/null
 
apt-get update
apt-get install docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

Docker est maintenant installé. Pour l'instant, en mode normal.

Préparation du mode rootless

Pour qu'on puisse utiliser les port réservés, il faut utiliser une et une seule des deux méthodes présentées ci-dessous.

  1. 1ère Alternative, ma préférée.
    Ajouter la capacité CAP_NET_BIND_SERVICE à l'exécutable rootlesskit.
    setcap cap_net_bind_service=ep $(which rootlesskit)
    • 2023 :
      A priori, ce réglage est permanent et devrait résister aux mises à jour. mais c'est pas certain. CF réponse 92703 d'ask ubuntu et cette réponse sur unix & linux.
    • 2024 :
      Ça résiste, sans souci.
  2. 2ème Alternative
    Abaisser le numéro du premier port non privilégié.
    On indique que les ports non privilégiés démarrent à 0, tout simplement. Mais dans ce cas, tout process peut utiliser les ports non privilégiés, pas seulement les conteneurs docker rootless.
    echo net.ipv4.ip_unprivileged_port_start=0 > /etc/sysctl.d/99-z-unprivileded-ports-for-all.conf
    sysctl --system

Passage en mode rootless

En préliminaire, arrêter tout service docker qui traînerait par là, et installer le paquet docker-ce-rootless-extras.

systemctl disable --now docker.service docker.socket
apt-get install docker-ce-rootless-extras

S'assurer que docker sera démarré au boot

loginctl enable-linger $nouveauCompte

Devenir l'utilisateur non privilégié dockeruser et continuer

ssh  $nouveauCompte@localhost
dockerd-rootless-setuptool.sh install

Cette commande crée et active le contexte docker rootless. C'est parfait. Avec cet utilisateur, on peut maintenant utiliser la commande docker, comme si docker était installé de manière standard.

détails si ça vous intéresse

Vérification que ça fonctionne bien

dockeruser@pc:~$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
dockeruser@pc:~$ 

On va malgré tout ajouter la variable d'environnement DOCKER_HOST à .bashrc. En effet certaines commandes tierces, tel docker-compose2), n'utilisent pas le contexte docker mais cette variable.

echo . $HOME/.bashrc > $HOME/.profile
echo export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock >> ~/.bashrc

S'assurer que le service utilisateur est bien démarré automatiquement.

systemctl --user enable docker

Et voilà, c'est tout pour le compte dockeruser.

Utiliser

reboot

Et après un redémarrage, on doit avoir le démon dockerd qui fonctionne.

Sur une machine distante, ou sur la même machine avec un compte d'utilisateur ordinaire différent du compte dédié qu'on vient de créer, on peut créer un contexte docker et l'activer. L'activation est en fait un réglage de la commande docker. À un moment donné, pour un utilisateur donné, il ne peut y avoir qu'un seul contexte docker actif. On peut créer des centaines de contextes différents si besoin.

docker context create \
       --docker host=ssh://dockeruser@docker-pc \
       --description "rootless docker on docker-pc" \
       docker-pc
docker context use docker-pc

Ensuite, tout est possible. Voilà une session d'exemple.

moi@monpc:~ (0) $ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
moi@monpc:~ (0) $ docker run --rm busybox echo hello world!
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
ec562eabd705: Pull complete
Digest: sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7
Status: Downloaded newer image for busybox:latest
hello world!
moi@monpc:~ (0) $ docker run --rm busybox echo hello world!
hello world!
moi@monpc:~ (0) $ docker run --name patience --rm busybox sleep infinity
^C
context canceled moi@monpc:~ (1) $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7c15a6d3b253 busybox "sleep infinity" 48 seconds ago Up 22 seconds patience moi@monpc:~ (0) $ : "Ah ben ça alors, le ^C n'a tué que la commande locale" moi@monpc:~ (0) $ : "Et la commande docker continue à fonctionner" moi@monpc:~ (0) $ docker stop patience patience moi@monpc:~ (0) $ : Voyons le réseau, maintenant. Détachons un conteneur moi@monpc:~ (0) $ : qui ne fait rien et allons voir. moi@monpc:~ (0) $ docker run --name patience --rm -d busybox sleep infinity af3adee0ae4485eaa50f1c5bcb486d58effa6df92528eb0e23aa85569bc3dda9 moi@monpc:~ (0) $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES af3adee0ae44 busybox "sleep infinity" 31 seconds ago Up 5 seconds patience moi@monpc:~ (0) $ docker exec -it patience sh
/ # ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 17: eth0@if18: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 02:42:0a:0e:00:02 brd ff:ff:ff:ff:ff:ff inet 10.14.0.2/24 brd 10.14.0.255 scope global eth0 valid_lft forever preferred_lft forever / # exit
moi@monpc:~ (0) $ docker stop patience patience moi@monpc:~ (0) $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES moi@monpc:~ (0) $
1)
En cas de manque de connaissances sur le sujet, mettez un mot de passe à cet utilisateur, ou vous pouvez également lire les deux dernières références citées dans le cartouche ce cet article.
2)
Maintenant que la commande compose est un plugin de docker, est-ce que docker-compose est toujours vraiment nécessaire ? En effet, on peut utiliser
docker compose ...