LibrePortal/containers/vikunja/docker-compose.yml
librelad b915e0731a fix(vikunja): run as the mount owner, and drop the docker socket
Found by installing it. Two problems, one fatal and one worse.

The container crash-looped: the image runs as uid 1000, which under
rootless Docker maps to host sub-UID 232071 while the bind mounts are
owned by the install user, so Vikunja died on its first write to
/app/vikunja/files and restarted forever. Its own error message
diagnosed it exactly. Fixed with the existing USER_TAG mechanism the
portal container already uses — 0:0 under rootless (container root IS
the install user on the host), the real uid:gid under rooted — rather
than hardcoding either.

More seriously, the compose mounted the docker socket, copied in from a
template that needed it. A task manager has no business talking to the
daemon, and the socket is root-equivalent access on the host. Removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 04:08:56 +01:00

70 lines
4.0 KiB
YAML

networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
external: true
services:
# Vikunja — tasks, lists, kanban, gantt. One container: the API and the
# frontend were merged upstream, and SQLite is a first-class backend, so
# this needs no database sidecar.
vikunja-service: #LIBREPORTAL|SERVICE_TAG_1|vikunja-service
container_name: vikunja-service
image: vikunja/vikunja:latest #LIBREPORTAL|VIKUNJA_VERSION_TAG|latest
# The image runs as uid 1000, which under rootless Docker maps to a host
# sub-UID that owns nothing — Vikunja then dies on its first write to
# /app/vikunja/files and restarts forever. USER_TAG resolves to the
# identity that actually owns the bind mounts: 0:0 under rootless
# (container root == the install user on the host), the real uid:gid
# under rooted.
user: "USER_DATA" #LIBREPORTAL|USER_TAG|USER_DATA
restart: unless-stopped
hostname: vikunja
# GLUETUN_OFF_BEGIN
ports:
- "PORTS_DATA_1" #LIBREPORTAL|PORTS_TAG_1|PORTS_DATA_1
# GLUETUN_OFF_END
# Deliberately NO docker socket mount: Vikunja is a task app and has no
# reason to talk to the daemon. Handing it the socket would be
# root-equivalent access on the host for no benefit.
volumes:
# The image presets VIKUNJA_DATABASE_PATH=/db/vikunja.db, so the
# database lives in /db and uploaded attachments in files/.
- ./db:/db
- ./files:/app/vikunja/files
environment:
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
# Vikunja builds absolute URLs from this, and it is NOT optional:
# get it wrong and registering the first account fails with a bare
# "unauthorized". APP_URL_TAG is the existing mechanism for exactly
# this (bookstack uses it too) — it resolves to https://<domain>
# when the app is public and Traefik is installed, and to
# http://<host>:<assigned-port> otherwise, so the port never has to
# be guessed or hardcoded here.
- VIKUNJA_SERVICE_PUBLICURL=APP_URL_DATA #LIBREPORTAL|APP_URL_TAG|APP_URL_DATA
- VIKUNJA_DATABASE_TYPE=sqlite
- VIKUNJA_DATABASE_PATH=/db/vikunja.db
labels:
libreportal.category: "CATEGORY_DATA" #LIBREPORTAL|CATEGORY_TAG|CATEGORY_DATA
libreportal.title: "TITLE_DATA" #LIBREPORTAL|TITLE_TAG|TITLE_DATA
libreportal.backup.db: "sqlite:::db/vikunja.db"
libreportal.backup.files: "vikunja-service:/app/vikunja/files:files"
traefik.enable: TRAEFIK_ENABLE_DATA #LIBREPORTAL|TRAEFIK_ENABLE_TAG|TRAEFIK_ENABLE_DATA
# TRAEFIK_PORT_1_BEGIN
traefik.http.routers.vikunja-service.entrypoints: web,websecure
traefik.http.routers.vikunja-service.rule: Host(`DOMAINSUBNAME_DATA_1`) #LIBREPORTAL|DOMAINSUBNAME_TAG_1|DOMAINSUBNAME_DATA_1
traefik.http.routers.vikunja-service.tls: true
traefik.http.routers.vikunja-service.tls.certresolver: production
traefik.http.services.vikunja-service.loadbalancer.server.port: PORT_INTERNAL_DATA_1 #LIBREPORTAL|PORT_INTERNAL_TAG_1|PORT_INTERNAL_DATA_1
traefik.http.routers.vikunja-service.middlewares: MIDDLEWARE_DATA_1 #LIBREPORTAL|MIDDLEWARE_TAG_1|MIDDLEWARE_DATA_1
# TRAEFIK_PORT_1_END
traefik.docker.network: DOCKER_NETWORK_DATA #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
healthcheck:
disable: HEALTHCHECK_DATA #LIBREPORTAL|HEALTHCHECK_TAG|HEALTHCHECK_DATA
# GLUETUN_OFF_BEGIN
networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
ipv4_address: IP_DATA_1 #LIBREPORTAL|IP_TAG_1|IP_DATA_1
# GLUETUN_OFF_END
# GLUETUN_ON_BEGIN
# network_mode: "container:gluetun-service"
# GLUETUN_ON_END