From fb47979787f5d06e0904d69db87c3924a1ba3952 Mon Sep 17 00:00:00 2001 From: Antoine Pelletier Date: Thu, 30 Jul 2026 00:14:55 +0200 Subject: [PATCH] fix: image uid --- Dockerfile | 17 ++++++++++------- README.md | 22 ++++++++++------------ docker-compose.yml | 8 ++------ 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9374348..15503dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,22 +55,25 @@ COPY --from=ghcr.io/amacneil/dbmate:2 /usr/local/bin/dbmate /usr/local/bin/dbmat COPY db/migrations/ /app/db/migrations/ # debian:*-slim ships without /etc/nsswitch.conf, and glibc then cannot turn a -# uid back into a user record. libvirt does exactly that lookup before connecting -# and gives up with "Failed to find user record for uid", whatever the socket -# permissions are. Restore the plain file-based lookup. +# uid back into a user record. Restore the plain file-based lookup. RUN printf 'passwd: files\ngroup: files\nshadow: files\nhosts: files dns\n' \ > /etc/nsswitch.conf -# Unprivileged, but it must land in the host's libvirt group to reach the -# socket: `group_add` in the compose file does that at run time. -RUN useradd --system --create-home --uid 10001 app +# Runs as root, on purpose. libvirt resolves the calling uid to a user record +# before it even opens the socket, and refused to do so for a custom uid here +# ("Failed to find user record for uid") even with /etc/passwd and the libvirt +# group both in place. Root always resolves. +# +# The privilege actually gained is small: any process that can reach the libvirt +# socket already starts and destroys the VMs of this host, which is the whole +# point of this container. It holds no other mount and no secret beyond the +# database password. WORKDIR /app COPY --from=backend /build/target/release/app-template /usr/local/bin/vm-selector COPY --from=frontend /build/dist/ /app/frontend/ COPY docker/config.yml /etc/app-template/config.yml -USER app EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ diff --git a/README.md b/README.md index 365ed23..06a1d28 100644 --- a/README.md +++ b/README.md @@ -196,24 +196,22 @@ cargo sqlx prepare # dev database must be up and migrated ### Portainer stack -Deploy `docker-compose.yml` and set the four stack variables it documents at the top -(`IMAGE`, `POSTGRES_PASSWORD`, `LIBVIRT_GID`, `APP_PORT`). Get the group id from the -host: - -```bash -getent group libvirt | cut -d: -f3 -``` +Deploy `docker-compose.yml` and set the stack variables it documents at the top +(`IMAGE`, `POSTGRES_PASSWORD`, `APP_PORT`). Then create a webhook on the stack, **tick "Re-pull image"**, and put its url in the `PORTAINER_WEBHOOK` secret of the repository. Without the re-pull, the webhook recreates the containers from the layer already on disk and nothing changes. -The two things that make libvirt reachable from inside the container: +What makes libvirt reachable from inside the container: `/var/run/libvirt` is +bind-mounted from the host — the whole directory, since the socket is `libvirt-sock` +or `virtqemud-sock` depending on the host's setup — and the container runs as root. -- `/var/run/libvirt` bind-mounted from the host — the whole directory, since the - socket is `libvirt-sock` or `virtqemud-sock` depending on the host's setup -- the container in the host's `libvirt` group (`group_add`) — without it the socket - is visible and every call is denied +Running as root is not laziness about the group: with a custom uid, libvirt resolves +the caller to a user record before opening the socket and refused to do so here even +with `/etc/passwd` correct and the `libvirt` group added through `group_add` +(`Failed to find user record for uid '10001'`). What root actually adds is small — +anything that reaches this socket already controls every VM on the host. ### Domain name diff --git a/docker-compose.yml b/docker-compose.yml index 2420d81..2e69216 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,6 @@ # Environment variables to set in the Portainer stack: # IMAGE registry.tibiscuit.ch/vm-selector:latest # POSTGRES_PASSWORD anything, it never leaves the internal network -# LIBVIRT_GID the host's libvirt group id, from: -# getent group libvirt | cut -d: -f3 # APP_PORT host port for the reverse proxy to point at (default 3010) services: @@ -59,10 +57,8 @@ services: # runs the monolithic libvirtd or the split daemons, the app needs # libvirt-sock or virtqemud-sock. - /var/run/libvirt:/var/run/libvirt - # Reaching that socket requires being in the host's libvirt group. Without - # this the socket is visible and every call is denied. - group_add: - - '${LIBVIRT_GID:?see the header of this file}' + # No `group_add`: the container runs as root (see the Dockerfile for why), so + # it needs no group membership to reach the socket. depends_on: db: condition: service_healthy