services: klappstuhl_me: container_name: klappstuhl_me build: context: . image: klappstuhl_me:latest restart: unless-stopped volumes: # Persistent data: config, database, logs, ACME cert cache. # On first run the app auto-creates the config file at: # ./data/config/klappstuhl_me/config.json # Edit that file (see notes below) then restart the container. - ./data:/data # Docker socket – required so the /admin/services admin page can call # `docker ps` and `docker inspect` on the host. # Remove this mount if you are not using the services feature. - /var/run/docker.sock:/var/run/docker.sock # NOTE: chromium and ffmpeg (used by the render-screenshot, # render-markdown-to-pdf and transcode API endpoints) are installed # inside the image itself — see the Dockerfile runtime stage. There is # no need to bind-mount the host binaries; doing so would break, because # the executables need their shared libraries and fonts to be present in # the container too. # Host ufw configuration and rule database. network_mode: host shares # the kernel's iptables tables, but ufw reads its rule list from # /etc/ufw/ (user.rules, user6.rules, …) and its runtime state from # /var/lib/ufw/. Without these mounts `ufw status` inside the container # sees the container image's empty ufw install instead of the host's # configured rules. Mounting them lets the firewall dashboard sync and # display the real host ruleset, and lets apply/delete commands persist # changes back to the host's ufw database. - /etc/ufw:/etc/ufw - /var/lib/ufw:/var/lib/ufw # Host /proc and /sys (read-only) so the live metrics dashboard # (/admin/metrics) reports the real machine instead of the # container's cgroup-limited view. Required for accurate CPU, # RAM, network, and temperature sensor readings. # Remove these if you don't use the metrics dashboard. - /proc:/host/proc:ro - /sys:/host/sys:ro # Host /home and /root so the SSH admin page can sync each user's # authorized_keys file directly. The target user for a given key is # derived from the comment ('user@host' → 'user'); the container # writes to /host-home//.ssh/authorized_keys (or # /host-root/.ssh/authorized_keys for root). ~/.ssh is auto-created # if missing, chmod 0700, and chown'd to match the home dir's owner # so sshd's StrictModes check accepts the file. # # No config needed beyond these mounts — adding a key for a user # that doesn't exist on the host returns HTTP 422. Remove these # mounts to disable filesystem sync (the admin page still stores # keys in the DB and offers a download). - /home:/host-home - /root:/host-root # Host sshd auth log (read-only) so the SSH admin page can populate # the "Last used" column. The container tails this file and updates # ssh_key.last_used_at whenever sshd logs a successful publickey auth # whose SHA256 fingerprint matches a stored key. # # Path varies by distro: # • Debian / Ubuntu → /var/log/auth.log # • RHEL / Fedora / Rocky → /var/log/secure # • Systemd-only (no syslog) → use `journalctl -u ssh > /var/log/auth.log` # in a cron, or run rsyslog/syslog-ng. The watcher needs a real file. # # sshd must log fingerprints — true by default on modern OpenSSH; if # your log lines lack `ssh2: SHA256:`, set # `LogLevel VERBOSE` in /etc/ssh/sshd_config. # # Required companion setting in config.json: # "sshd_auth_log_path": "/host-log/auth.log" - /var/log/auth.log:/host-log/auth.log:ro # NET_ADMIN is required so ufw/iptables/nftables commands issued from # inside the container can actually modify the host's kernel packet # filter tables (network namespace is shared via host networking above). cap_add: - NET_ADMIN - NET_BIND_SERVICE # HOST_PROC / HOST_SYS tell the metrics collector where the host # filesystem is mounted (see the read-only bind mounts above). environment: HOST_PROC: "/host/proc" HOST_SYS: "/host/sys" # Optional: pass RUST_LOG to control log verbosity. # RUST_LOG: "info" # ───────────────────────────────────────────────────────────────────────────── # First-run setup # ───────────────────────────────────────────────────────────────────────────── # 1. Start once to let the app write a default config: # docker compose up -d # Then stop it: # docker compose down # # 2. Edit ./data/config/klappstuhl_me/config.json # # 3. Create the admin account (interactive prompt): # docker compose run --rm klappstuhl_me ./klappstuhl_me admin # # 4. Start permanently: # docker compose up -d healthcheck: test: ["CMD", "curl", "-s", "-o", "/dev/null", "http://localhost:9510/"] interval: 30s timeout: 5s retries: 3 start_period: 15s deploy: resources: limits: cpus: '0.5' memory: 256M networks: - homelab networks: homelab: external: true