Skip to content

Run container as non-root user (UID/GID 1000) for rootless compatibility#799

Open
dmfrey wants to merge 1 commit intomadeofpendletonwool:mainfrom
dmfrey:fix/rootless-container
Open

Run container as non-root user (UID/GID 1000) for rootless compatibility#799
dmfrey wants to merge 1 commit intomadeofpendletonwool:mainfrom
dmfrey:fix/rootless-container

Conversation

@dmfrey
Copy link
Copy Markdown

@dmfrey dmfrey commented Mar 11, 2026

Summary

Adds full rootless container support by running the PinePods container as a dedicated non-root user (pinepods, UID/GID 1000), following Docker, OWASP, and rootlesscontaine.rs best practices. This is a common requirement in homelab and enterprise environments.

Changes

dockerfile

  • Creates a pinepods system user/group with fixed UID/GID 1000
  • Pre-creates all runtime directories at build time and sets ownership to pinepods:pinepods (downloads, backups, certs, cache, logs, nginx dirs, horust services)
  • Copies Horust service configs into /etc/horust/services/ at build time, eliminating the runtime root write
  • Adds USER pinepods:pinepods before ENTRYPOINT
  • Corrects EXPOSE from 8080 8000 to 8040

startup/nginx.conf

  • Adds pid /tmp/nginx.pid so nginx can write its PID without root
  • Redirects all nginx temp/cache paths to /tmp/nginx

startup/startup.sh

  • Replaces writes to /etc/localtime//etc/timezone with reliance on the TZ environment variable (musl/glibc honour it without touching root-owned files)
  • Removes chown -R $PUID:$PGID of volume directories (non-root can't chown; host dirs must be pre-owned — see note below)
  • Removes exim root setup (not applicable on Alpine anyway)
  • Removes runtime copy of Horust .toml configs (now done at build time)

docker-compose (postgres / mysql / mariadb)

  • Adds user: "1000:1000"
  • Adds cap_drop: [ALL]
  • Adds security_opt: no-new-privileges:true
  • Removes PUID/PGID environment variables
  • Removes /etc/localtime and /etc/timezone volume mounts
  • Adds comment explaining that host volume directories must be pre-owned by UID 1000

Migration note for existing deployments

Before (re)starting the container, ensure your host volume directories are owned by UID 1000:

sudo chown -R 1000:1000 /home/user/pinepods/downloads /home/user/pinepods/backups

Test plan

  • Container starts successfully without root
  • docker exec ... id confirms the process runs as UID 1000
  • Downloads and backups write correctly to mounted volumes
  • Web UI loads and API responds on port 8040
  • CI passes

🤖 Generated with Claude Code

Adds a dedicated pinepods user (UID 1000, GID 1000) to the image and
switches to it before the entrypoint, following Docker and OWASP best
practices for non-root containers:

- dockerfile: create pinepods user/group, pre-create and chown all
  runtime directories at build time, copy Horust service configs at
  build time (avoids runtime root write), add USER directive, drop
  the old EXPOSE 8080/8000 in favour of the correct 8040
- startup/nginx.conf: add pid /tmp/nginx.pid and redirect all nginx
  temp paths to /tmp/nginx so nginx runs without root
- startup/startup.sh: replace /etc/localtime writes with TZ env var
  (musl/glibc respect it without touching root-owned files), remove
  runtime chown of volume dirs, remove exim root setup, remove
  runtime Horust services copy (now done at build time)
- docker-compose (postgres/mysql/mariadb): add user: "1000:1000",
  cap_drop: [ALL], security_opt: no-new-privileges, remove PUID/PGID
  env vars, remove /etc/localtime volume mounts; add note that host
  volume dirs must be pre-owned by UID 1000

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant