The issue opens pre-filled with this page title and URL.
With rootless mode, the Docker daemon and the containers run under an ordinary
account, with no root rights on the host. This lesson teaches you to
install a rootless engine next to the usual engine, to check that it really
belongs to the account, then to qualify what it changes: UID mapping, storage,
network, resource limits and persistence. It also says what this mode cannot
do, so you can decide whether it meets your need. The measurements come from a
throwaway Debian 13 VM with Docker Engine 29.9.2 and RootlessKit 3.3.0; the test
that tells modifying from deleting comes from a second pass on the same VM and
the same versions. Do not replay the installation on a shared host before you
have understood it.
What you will learn
Each check is read on the real state of the system, not on the announced
configuration.
You explain what rootless mode changes compared with the docker group.
You check the prerequisites: a range of subordinate UIDs (and how to give one
to an account that has none), uidmap, cgroup v2 and the
docker-ce-rootless-extras package.
You install a rootless engine with dockerd-rootless-setuptool.sh, without
touching the rootful engine.
You prove that the processes and the storage belong to the account.
You read the UID mapping and you deduce its consequences on the files of a
volume.
You qualify the network (ports, source address, --network host), the
resource limits and the startup with no open session.
You uninstall rootless mode without leaving anything behind.
The demonstration uses two roles that the text always names. The
rootful engine is the usual Docker engine, started by root. The account
c37-dev, created for the occasion, plays the developer who wants to run
containers without root rights. The administration commands, which need sudo,
are preceded by the comment # administrateur ("administrator" in French,
kept as is in the listings). The account's commands are preceded by
# c37-dev, en session SSH ("in an SSH session"): open a real SSH connection
under this account, because a sudo -u does not create the session that
rootless mode needs. All the containers start from the same image, pinned to
its digest, which you define in every session that runs containers:
Rootless mode runs the daemon, its containers and its socket under an ordinary
account, inside a user namespace. A user namespace is a kernel mechanism
that gives a group of processes its own view of UIDs: the root inside
corresponds, seen from the host, to an unprivileged UID (the page
Understanding Linux namespaces details
the mechanism).
The lesson Remove direct access to the Docker
daemon (C36) shows
the problem from the other side: an account in the docker group talks to a
root daemon, so it is root. Rootless mode is the alternative for a user who
really must run containers: the user is not given access to a powerful daemon,
the user starts one of their own, whose power is only that of the account.
Podman works this way by default, as the page Les concepts de
Podman (in French)
describes.
The diagram puts the two engines face to face on the same host. Look at the
owners, the socket and storage paths, the detached network namespace where only
the bridge network containers live, and the UID mapping at the bottom, which
the section on UIDs then measures.
This engine is entirely separate from the first one: socket, processes,
images, volumes and networks are not shared. What runs in it does not see the
containers of the rootful engine, and the other way round.
Which prerequisites must the machine and the account meet?
Three things must exist before the installation: a range of subordinate UIDs
for the account, the two programs newuidmap and newgidmap from the uidmap
package, and cgroups v2. A subordinate UID is a UID that the administrator
reserves for an account, in /etc/subuid (and /etc/subgid for groups), for it
to use in its user namespace: without a range, the account has only one UID to
put there, its own. Rootless Docker requires this range: the troubleshooting
section of its documentation quotes the error No subuid ranges found for an
account that has none. In the demonstration below, useradd gives its range to
the account of the lesson, and a system account gets its own with usermod.
Start by reading the state of the machine: kernel, cgroups and packages already
installed.
cgroup2fs indicates cgroups v2. docker-ce-rootless-extras brings
dockerd-rootless.sh, dockerd-rootless-setuptool.sh and rootlesskit
(RootlessKit, the program that creates the namespace and its network); the
installation lesson already pinned it together with docker-ce, at the same
version 29.9.2. dbus-user-session gives the account's service manager its
session bus. The Docker documentation quotes it in its troubleshooting: without
this bus, the containers fail to start.
On Debian 13, useradd gives the range by itself to a normal account (not to a
system account created with -r), thanks to the SUB_UID_COUNT and
SUB_GID_COUNT settings of /etc/login.defs, as long as /etc/subuid exists:
Each new account therefore gets 65,536 subordinate UIDs, taken between
SUB_UID_MIN and SUB_UID_MAX. The official documentation asks for a range of
at least 65,536: it is a recommendation, but a shorter range makes the download
of an image fail when its files belong to higher UIDs, with an
lchown … invalid argument error. Create the working account, without putting
it in the docker group, then read its UID and its ranges:
The account has UID 1002 and the UIDs 231072 to 296607 (65,536 values) are
reserved for it. The starting point of the range, 231072, is specific to this
VM: it depends on the accounts already created, it is not a default value.
These figures come back in the UID mapping, further down.
A system account, on the other hand, has no range. To give it one, first read
the ranges already taken in /etc/subuid: two accounts whose ranges overlap
would share UIDs. Here, the three existing lines stop at 296607, and the
range 300000-365535 overlaps none of them. The value 300000 is specific to
this VM: compute yours from your own file.
userdel: c37-svc mail spool (/var/mail/c37-svc) not found
userdel_svc_rc=0
0
The first 0 confirms that the system account has no line in /etc/subuid.
usermod --add-subuids … --add-subgids … gives it one, in both files, and
userdel -r removes it with the account (the last 0). The warning about the
mailbox is harmless. The account of the lesson, c37-dev, does not need this
step: useradd already gave it its range.
To open the SSH session of the account, the administrator copies their own
authorized key to it here; adapt this step to the way you reach the machine:
In the account's session, three readings give the starting state: who it is, the
runtime directory of its session, and what the rootful engine answers it. The
installation tool then checks its prerequisites with the check subcommand:
permission denied while trying to connect to the docker API at unix:///var/run/docker.sock
[ERROR] Missing system requirements. Run the following commands to
[ERROR] install the requirements and run this tool again.
########## BEGIN ##########
sudo sh -eux <<EOF
# Install newuidmap & newgidmap binaries
apt-get install -y uidmap
EOF
########## END ##########
XDG_RUNTIME_DIR=/run/user/1002 is the directory that a real session creates,
and the socket of the rootless engine will be placed in it. The account, which
is outside the docker group, is refused the socket of the rootful engine: this
is intended. As for check, it refuses because uidmap is missing, and it
copies the command to run, which only an administrator can type: the account
cannot install uidmap itself, whose two programs are setuid root so that
they can write the UID mapping. The sed removes the color codes from the
output.
How do you install a rootless engine next to the rootful engine?
The installation takes two steps: the administrator prepares the machine, then
the account runs dockerd-rootless-setuptool.sh install. The administrator
installs uidmap, then sets the account's lingering, before the
installation. Lingering is a systemd-logind setting that keeps an account's
service manager alive even when nobody is logged in. Without it, this manager
stops with the last session of the account and takes the rootless daemon with
it: setting it first gives a stable daemon throughout the lesson, with the same
PIDs. The section on persistence measures what happens without it.
The output of apt-get is redirected to /dev/null to stay short, and
DEBIAN_FRONTEND=noninteractive avoids any question. The s in the mode
-rwsr-xr-x marks the setuid bit: these two programs run with the rights of
root, which lets them write into a user namespace UIDs that the account does not
own. Linger=yes confirms the lingering.
The account then reruns check, then install. The tool does not ask for
--force here: it stops when the socket of the rootful engine is writable by
the account, and the account, outside the docker group, has no access to it.
The --force option also exists to override a RootlessKit failure: do not add
it by reflex, it hides errors. The return codes are read with PIPESTATUS[0],
which gives the one of the tool and not the one of sed. The excerpts below
keep only the useful lines of the installation:
Five facts can be read here. The tool created a systemd user unit
(~/.config/systemd/user/docker.service), that is, a service managed by the
account's systemd and not by the system's. This unit starts rootlesskit (PID
5090), which in turn starts the daemon. The network driver is
gvisor-tap-vsock, with the builtin port driver. The API listens on
/run/user/1002/docker.sock, a path under /run/user/<uid>. Finally, the tool
created a Docker context named rootless and activated it, which saves
exporting DOCKER_HOST by hand for the docker command line (the tool points
out that some applications need this variable). The last [INFO] line recalls
the lingering: it is already set.
The state after installation is read with one command per object:
Terminal window
# c37-dev, en session SSH
ls~/.config/systemd/user/
systemctl--useris-activedocker.service
systemctl--useris-enableddocker.service
dockercontextls
default.target.wants
docker.service
active
enabled
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default Current DOCKER_HOST based configuration unix:///var/run/docker.sock
The two engines each have their own context: default still targets the
rootful socket, rootless (marked by the star) targets the account's socket.
The rootful engine was neither stopped nor modified.
How do you check that the daemon and its processes belong to the account?
The proof of rootless mode is threefold: the daemon declares itself rootless,
its processes belong to the account, and its storage is separate. The daemon
declares its mode in SecurityOptions, with the name=rootless entry, and it
gives its data directory and its storage driver:
Look at three elements. name=rootless in SecurityOptions is the daemon's
announcement. DockerRootDir points into the account's home directory:
~/.local/share/docker, not /var/lib/docker. And ps lists two sets of
processes: containerd and dockerd of root (the rootful engine, still there),
and rootlesskit, dockerd and containerd of the c37-dev account. The
storage driver is overlayfs, the containerd snapshotter, which the daemon
chooses without configuration.
The rootlesskit shown by ps has PID 5090, the same as in the installation
output, and so do the daemon (5132) and containerd (5154): the lingering set
before the installation keeps the account's manager, hence the daemon, alive
between sessions.
The image store is separate too. The account starts from zero, downloads the
image, then measures what it takes up on its side:
The account's engine contained no image at the start (0). After the download,
13 MB occupy ~/.local/share/docker. The administrator now looks at the store
of the rootful engine:
If your system engine already has this image, as here, its store lists it with
the same identifier, because it is the same digest. The account, for its part,
started from 0 images and had to download it: each engine keeps its own copy.
Practical corollary: each rootless account downloads its images again, and its
data count in its home directory.
How do container UIDs map to the host?
In a rootless container, root is not the host's root: it is the account
itself. The mapping table/proc/self/uid_map writes it in three columns:
the first UID in the container, the first matching UID on the host, and the
length of the range. Run two containers, one as root by default, the other with
--user 1000:1000, and look at them from the host:
The output reads in the order of the commands. 0 is the UID of the first
container, followed by its table. The first line of the table, 0 1002 1, says
that UID 0 of the container is UID 1002 of the host: the container's root is
the c37-dev account, and ps confirms it (c37-dev 1002). The second,
1 231072 65536, places UIDs 1 to 65536 of the container on the subordinate
UIDs 231072 to 296607. The second container, 1000 inside, therefore has,
seen from the host, UID 232071, that is 231072 + (1000 - 1). This is the diagram
of the previous section, measured.
The concrete consequence touches the files of a volume. A file created by
root in the container belongs to the account on the host. A file created for
UID 1000 belongs to UID 232071, which no real account bears:
The first two listings show the same volume seen from two sides: 0 and 1000
in the container, 1002 and 232071 on the host. The account easily modifies
the file of the container's root (ecriture_hote_rc=0), but the touch
command on the file of UID 1000 is refused (touch_1000_rc=1), because the
host sees another owner. A bind mount of a directory of your repository calls
for the same vigilance: a container that writes as a UID other than root
drops files that the account will no longer be able to modify from the host.
Modifying and deleting do not follow the same rule. Deleting an entry depends on
the rights on the directory that contains it, not on the file itself. The
following test creates, from a container, a file and a directory of UID 1000 in
a new volume, then modifies and deletes them from the host:
drwxr-xr-x 2 232071 232071 4096 Nov 4 20:47 rep-1000
=== nettoyage
c37-m-droits
In this capture, the touch message appears below the === supprimer … header
("delete" in French): output and errors travelled through two distinct
channels. In your terminal, the error will appear right after the command that
produced it. Follow the return codes. The account does not modify the file of
UID 1000 (touch_rc=1), yet it deletes it (rm_fichier_rc=0): the parent
directory _data belongs to it, and this is the right that counts to remove an
entry. It cannot empty rep-1000 (rm_rep_rc=1, Permission denied): this
directory belongs to UID 232071, the account has no write right on it, so it
cannot remove dedans ("inside") from it, and rep-1000 stays visible in the
last ls. To erase this kind of data, go through the daemon while it runs
(docker volume rm), or through rootlesskit after the uninstallation, as
uninstall suggests.
What does rootless mode change for networking?
Rootless mode changes three things for the network: the service no longer
receives the source address of the clients by default, a port below 1024 cannot
be published, and --network host exposes the service on the real network of
the machine. A high port, for its part, is published normally. The service of
the example is a small nc server that answers one line and prints the address
of its clients; HOTE ("host" in French) receives the first global IPv4
address of the machine:
connect to [::ffff:172.17.0.2]:8080 from [::ffff:172.17.0.1]:57558 ([::ffff:172.17.0.1]:57558)
The publication on 127.0.0.1:8088 answers (curl_boucle_rc=0), and the
address of the VM, which is not in the publication, does not answer
(curl_ip_hote_rc=7). The log line shows a limit: the service
(172.17.0.2) receives the connection from 172.17.0.1, an address internal
to the container network, and not from 127.0.0.1, the real address of the
client. With the default configuration (the builtin port driver), the real
source address is not passed on, which hinders a service that logs or filters
by address.
The Docker documentation states that RootlessKit 3 passes on the source address
over TCP, provided the userland-proxy is disabled. The workaround played in
this lesson is the slirp4netns port driver, presented further down.
The rootless bridge takes 172.17.0.0/16, like the rootful engine, without any
address conflict. The likely explanation: the account's bridge lives in the
RootlessKit network namespace, distinct from the host one where the bridge of
the rootful engine is.
Now port 80. An ordinary account cannot listen on a port below 1024, and the
same goes for RootlessKit. Publishing port 80 therefore fails:
docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint c37-m-web80 (398ba7ab05c922a3581a9647ba99db1e738e3d8473793fe17fedfa83f466b200): error while calling RootlessKit PortManager.AddPort(): cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or set CAP_NET_BIND_SERVICE on rootlesskit binary, or choose a larger port number (>= 1024): listen tcp4 0.0.0.0:80: bind: permission denied
Run 'docker run --help' for more information
run80_rc=126
The container was created (its identifier is displayed) but did not start,
hence the docker rm -f. The return code is 126, and the message quotes its
cause, cannot expose privileged port 80, then three ways out that it offers:
lowering net.ipv4.ip_unprivileged_port_start in /etc/sysctl.conf (current
value 1024), giving the CAP_NET_BIND_SERVICE capability to the rootlesskit
binary, or choosing a port greater than or equal to 1024. The first is a kernel
setting that applies to the whole machine. The second is set on the file: it
holds for every user who runs this binary, not only for your account. The
documentation of this mode words them like this:
sudo setcap cap_net_bind_service=ep $(which rootlesskit) then a restart of
the daemon, or net.ipv4.ip_unprivileged_port_start in /etc/sysctl.d then
sudo sysctl --system. These two settings modify the whole machine or a shared
binary: they are outside the scope of the demonstration, which stays on an
isolated account. The simplest is still to publish a high port, behind a
reverse proxy or a redirection set up by the administrator if the service must
be reached on port 80.
On this VM, outbound traffic works, ping included:
The ping answered on this Debian 13. The Docker documentation specifies that
its operation depends on the kernel parameter net.ipv4.ping_group_range, whose
value varies by distribution: on another one, check it.
The --network host option calls for particular attention. The RootlessKit
command line seen at installation carries --detach-netns: the daemon stays in
the network namespace of the host, and only the containers on the bridge
network live in the detached network namespace of RootlessKit (their IP address
is therefore not reachable from the host). A container started with
--network host thus sees the real interfaces of the host, and a service
listening there is reachable from the host:
Terminal window
# c37-dev, en session SSH
dockerrun--rm--networkhost"$ALP"sh-c'ip -4 -o addr show | awk "{print \$2, \$4}"'
The container sees lo, enp1s0 (the interface that carries the address
10.10.130.11 that HOTE read) and docker0, the bridge of the rootful
engine: these are the interfaces of the host. The service answers from the host
(curl_hote_8080_rc=0), and port 8080 listens on *, every address of the
machine. The last 0 shows that the port is freed once the container is
removed. This is a security limit: rootless mode does not partition the
network of a container started with --network host, which exposes its service
to the network of the machine. Keep it for an identified need, and prefer
publishing specific ports.
The network driver seen at installation, gvisor-tap-vsock, is not imposed:
according to the dockerd-rootless.sh script, it picks slirp4netns if it is
installed, otherwise pasta, otherwise vpnkit, and falls back on
gvisor-tap-vsock. This driver is embedded in RootlessKit, so there is no
extra binary to install, but RootlessKit describes it as experimental: do
not present it as a proven driver for production. The workaround below changes
driver precisely.
The Docker documentation offers, in its troubleshooting section "docker run -p
does not propagate source IP addresses", a workaround to keep the source
address: replace the port driver with slirp4netns. It needs the administrator,
who installs the Debian package (the engine does not embed slirp4netns), then
the account:
The account restarts its daemon, then compares the drivers before and after with
the pilotes function ("drivers" in French). The package alone is enough to
change the network, but not the port driver:
The first call shows the default network, gvisor-tap-vsock builtin. After the
restart, the network is slirp4netns, but the port driver stays builtin. To
change the latter too, the account writes an override of the user unit, reloads
systemd and restarts the daemon, then calls the service again with the same
nc program. ALP, HOTE and SRV are the ones defined above: if you opened
a new session, define them again.
connect to [::ffff:172.17.0.2]:8080 from [::ffff:10.0.2.2]:58892 ([::ffff:10.0.2.2]:58892)
page-c37
curl_ip_hote_rc=0
connect to [::ffff:172.17.0.2]:8080 from [::ffff:10.10.130.11]:35134 ([::ffff:10.10.130.11]:35134)
After the override, pilotes answers slirp4netns slirp4netns. The two logs
read as follows. A call on 127.0.0.1 is seen from 10.0.2.2, the gateway of
slirp4netns. A call on the address of the machine is seen from 10.10.130.11:
the address of the client is preserved. Two reservations: the client was the
host itself, for lack of another machine, and the documentation classes this
port driver as slow ("Slow"). So choose it when the service needs the source
address, not by default.
The way back takes three steps: the account removes the override, the
administrator purges the package, then the account restarts its daemon.
The daemon is back to gvisor-tap-vsock builtin: the rest of the lesson runs on
the original setting.
Which resource limits can the account enforce?
The account can only limit the resources whose cgroup controller systemd
delegates to it. On Debian 13 (systemd 257), the account's manager receives
cpu, memory and pids by default, not io nor cpuset. The official
documentation of rootless mode says that only memory and pids are typically
delegated: on this Debian, cpu is too, so the list depends on the machine. To
delegate more (io, cpuset), the documentation describes an override of
user@.service with Delegate=. To check it here, run containers with these
three limits and read the cgroup again from the inside, then read the list of
delegated controllers:
--memory 64m sets memory.max to 67,108,864 bytes, --pids-limit 20 sets
pids.max to 20, and --cpus 0.5 sets cpu.max to 50000 100000, that is
half a core. The last line explains why: only cpu, memory and pids are
delegated to the account. The absence of io also shows at the start of the
daemon, which warns No io.max (rbps) support (and the same line for wbps,
riops and wiops), in the daemon log at installation. As no io controller
is delegated, the I/O limits such as --device-read-bps cannot apply; this
case is outside the scope of the demonstration. If your need depends on it,
test it on your machine or ask the administrator to delegate this controller.
Does AppArmor protect rootless containers?
No: in rootless mode, no AppArmor profile is applied, and the option that asks
for one is ignored without error. The Docker documentation, besides, lists
AppArmor among the features not supported in this mode. The daemon says so in
its security options, and the container confirms it in /proc/1/attr/current:
SecurityOptions does not contain apparmor, AppArmorProfile is empty and
process 1 of the container is runc (unconfined). Explicitly asking for
--security-opt apparmor=docker-default succeeds (code 0, measured without a
pipe) and changes nothing: a security option is silently ignored, which is
more misleading than an error. The seccomp filter stays active
(name=seccomp,profile=builtin). The lesson seccomp and AppArmor profiles for
Docker
containers
details these two layers.
What can a rootless container that escapes reach?
A rootless container that escapes through the daemon or the runtime obtains the
powers of the account, not those of root. The UID table showed it: the
container's root is UID 1002, the account's, and the other UIDs of the
container fall into the account's subordinate range. An escape would therefore
reach what this account can read or write: its SSH keys, its code, its tokens.
The rootless daemon is not a daemon without power, it is a daemon that has only
those of its account.
Hence the choice of the demonstration: a dedicated account, which contains only
its rootless engine and its data, and which has neither the keys nor the code of
another project. Running rootless mode under a developer's personal account
gives the daemon access to everything that account owns. Add to this two limits
measured above: no AppArmor profile, and an open host network as soon as a
container is started with --network host.
How do you keep the rootless daemon running with no open session?
Without lingering, the daemon stops when the last session of the account
closes: the account's manager stops, rootlesskit disappears and the commands
run afterwards fail for lack of a socket. Lingering, set by the administrator
with sudo loginctl enable-linger, prevents this: it is what lets a user unit
start at boot and survive the logout. The measurement below shows rootlesskit
started again (PID 8886) as soon as enable-linger is restored.
The checks that follow are made without any open session of the account:
close your SSH session of c37-dev before this test. They are played from the
administrator. The D() function runs a Docker command under the account, with
the socket of the rootless daemon. A container is declared there with
--restart unless-stopped, then the lingering is removed and restored. The
first check lists the sessions of the machine:
failed to connect to the docker API at unix:///run/user/1002/docker.sock; check if the path is correct and if the daemon is running: dial unix /run/user/1002/docker.sock: connect: no such file or directory
The first line of loginctl list-sessions belongs to c37-dev (UID 1002) and
its class is manager: it is not a connection, since you closed yours, but the
session that systemd-logind opens for the account's service manager, which the
lingering maintains. The two ansible lines, the administrator's account,
carry a manager class and a user class, its own connection.
The for loop waits for rootlesskit to end instead of a fixed duration:
attente=13s ("wait" in French) says that the stop took 13 seconds (the loop
gives up at 90 s). Without lingering, the account is in the closing state, no
rootlesskit runs any more and the socket has disappeared: the API answers
no such file or directory, and the container is no longer served. With
lingering, the state goes to lingering, rootlesskit is back (PID 8886) and
the container c37-m-veille, Up 19 seconds, restarted thanks to its restart
policy. For a rootless service to survive the logout of its owner, enable
lingering, as the last [INFO] line of the installation advises. A related
point: the documentation specifies that running rootless Docker as a system
service (/etc/systemd/system/docker.service), even with User=, is not
supported. Keep the user unit created by the tool.
The D() function only gives access to the daemon, through DOCKER_HOST. For
the administrator to drive the account's service manager with
systemctl --user, a sudo -u is not enough: XDG_RUNTIME_DIR must also point
to the account's runtime directory, and DBUS_SESSION_BUS_ADDRESS to its bus
when the default does not fit. The demonstration does not need it: it goes
through DOCKER_HOST.
What remains is the reboot of the machine. After a reboot, still without the
account logging in, the administrator reads:
docker0 DOWN ee:06:6b:a4:eb:6c <NO-CARRIER,BROADCAST,MULTICAST,UP>
The machine rebooted at 20:25:19, and the rootless daemon and its container came
back (the container shows Up 14 seconds) without the account logging in:
lingering maintains the account's service manager. The docker0 bridge of the
rootful engine is also present on the host, in the DOWN state.
How do you uninstall rootless mode cleanly?
The uninstallation takes three steps: the account stops and removes its engine,
it deletes its data, then the administrator removes the lingering, the account
and the uidmap package. Reopen an SSH session under the account, which deletes
its objects and runs uninstall:
[INFO] Configured CLI to use the "default" context.
[INFO]
[INFO] Make sure to unset or update the environment PATH, DOCKER_HOST, and DOCKER_CONTEXT environment variables if you have added them to `~/.bashrc`.
[INFO] This uninstallation tool does NOT remove Docker binaries and data.
[INFO] To remove data, run: `/usr/bin/rootlesskit rm -rf /home/c37-dev/.local/share/docker`
uninstall_rc=0
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
The tool stops and disables the unit, deletes the rootless context and
reactivates default. It also says what it does not do: it removes neither the
binaries nor the data, and it suggests rootlesskit rm -rf for the data. You
must go through RootlessKit, because this data partly belongs to subordinate
UIDs: the account cannot modify these files from the host, and it cannot empty
a directory of UID 1000, as the rm refused above shows. rootlesskit creates
a new namespace with the same mapping as the daemon, where the account is root:
it does not enter an existing namespace. The deletion therefore runs there with
the necessary rights:
ls: cannot access '/home/c37-dev/.local/share/docker': No such file or directory
The 13 MB folder has disappeared. Here, the command played is
rootlesskit find … -delete, which does the same job as the
rootlesskit rm -rf proposed by the tool. What remains is the administrator's
side. First close your SSH session of c37-dev: the administrator then removes
the lingering, checks that no process remains, deletes the account and its UID
ranges. The first command counts the account's sessions, to show that only the
lingering one remains:
pgrep-uc37-dev-a.||echo"aucun processus de c37-dev"
sudouserdel-rc37-dev; echo"userdel_rc=$?"
grep-c'^c37-dev:'/etc/subuid/etc/subgid
ls-d/home/c37-dev2>&1
1
disable_rc=0
aucun processus de c37-dev
userdel: c37-dev mail spool (/var/mail/c37-dev) not found
userdel_rc=0
/etc/subuid:0
/etc/subgid:0
ls: cannot access '/home/c37-dev': No such file or directory
The 1 at the start is the manager session of the lingering, not a
connection. After disable-linger, no process remains for the account, and
userdel -r removed the account, its range in /etc/subuid and /etc/subgid
(:0 everywhere) and its home directory. The warning about the mailbox is
harmless. What remains is the uidmap package, installed for the lesson:
uidmap is purged, and no file of UID 1002 is left in /tmp (the last 0):
the machine is back to its starting state, leaving nothing behind, apart from
the Docker packages installed before the lesson.
Put it into practice
The lab prepares a rootless engine for a dedicated account, through a user unit
of its own rather than through the installation tool seen in this lesson, and
leaves the system's rootful engine intact. It asks you to qualify this engine.
The tests measure the real state: the user unit is active, the daemon process
belongs to the unprivileged account, the daemon announces rootless mode, a real
HTTP service answers on the loopback, a peer on the network is allowed and a
peer with no network is refused, and a piece of data written in a volume
survives the recreation of the container.
Key points
Rootless mode replaces access to a root daemon with a daemon that has only the
powers of its ordinary account, and those powers are the account's: its keys,
its code.
You tell the docker group (an access to the root daemon) from rootless mode
(a daemon and containers that belong to the account).
You check the prerequisites: 65,536 subordinate UIDs in /etc/subuid and
/etc/subgid (usermod --add-subuids … --add-subgids … for an account that
has none), the uidmap package, cgroups v2, and docker-ce-rootless-extras
at the engine's version.
You set lingering before the installation, then you install with
dockerd-rootless-setuptool.sh install under the account, in an SSH session;
check says what is missing, and the tool creates a user systemd unit, a
rootless context and the socket /run/user/<uid>/docker.sock.
You prove the mode by name=rootless in SecurityOptions, the account's ps
processes and DockerRootDir in its home directory; the image store is
separate from the rootful engine's.
You read /proc/self/uid_map: the container's root is the account, the
other UIDs fall into the subordinate range, and the files of a volume bear
these UIDs on the host: the account does not modify them, can delete a file
whose directory it owns, but cannot empty a directory of UID 1000, hence
rootlesskit to erase the data.
You know that ports below 1024 cannot be published, that the clients' source
address is not passed on by default (the slirp4netns port driver keeps it,
more slowly), and that --network host exposes the service on the real
network of the machine.
You know that cpu, memory and pids are delegated by default on
Debian 13, not io, and that AppArmor is not applied in rootless mode:
seccomp stays active.
You enable lingering so that the daemon survives logout and reboot, and
you delete the data with rootlesskit find … -delete.
Knowledge check
Check that the essentials are mastered: what rootless mode changes, how to read
the UID mapping and which constraints it imposes on the network, the resources
and persistence.
Knowledge check
Check what you have learned with this interactive quiz
6 questions
6 min
70% to pass
How it works
The timer starts when you click Start
Multiple choice, true/false and short answer questions
You can move between questions
Detailed results are shown at the end
Starts the quiz and the timer
⏱️--:--
0 / 6
Review
(0/0)
--:--
0%
0Correct
0To review
0:00Time
-Level
Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.