Skip to content

Chown app_data on install so apps can run unprivileged #80

@ClaydeCode

Description

@ClaydeCode

Problem

Apps that bind-mount {{ fs.app_data }} and write files at runtime currently must run as root in their container, because Docker creates the bind-mount source dir as UID 0 and no chown step runs before compose-up.

Concrete cases:

  • syncthing (PR #2) — official image runs UID 1000, no chown entrypoint. Fails with save cert: open /var/syncthing/config/cert.pem: permission denied. Workaround: user: "0:0".
  • vikunja — already ships user: 0:0 for the same reason.

Running app containers as root is bad on two axes:

  1. Breakout blast radius. Container escape → root on host paths under fs.app_data and fs.shared.
  2. File ownership pollution in fs.shared. Files written as UID 0 by one app become read-only / chown-required for other apps that legitimately run unprivileged.

This pattern will spread as more apps are added unless we fix it at the platform level.

Proposal

Freeshard creates {{ fs.app_data }}/<app> (and ensures {{ fs.shared }}) with a known, non-root owner before running docker compose up. App templates then declare user: \"1000:1000\" (or rely on the image default) and write happily.

Two flavors:

Minimal — single platform-wide app UID

  • Pick a standard UID/GID (e.g. 1000:1000).
  • On app install, mkdir -p and chown the per-app app_data dir to that UID:GID.
  • Document the convention; templates use user: \"1000:1000\".

Pros: one core change, fixes the common case. Cons: doesn't help images whose internal user is a different UID.

Optional extension — per-app UID hint

  • Add optional field to app_meta.json schema:
    \"runtime\": { \"uid\": 1001, \"gid\": 1001 }
  • Install pipeline chowns to those values if set, else falls back to the platform default.

Pros: covers images like linuxserver/* (UID 911) without forcing a workaround. Cons: schema bump, more surface area.

I'd start with the minimal version and only add the per-app hint when the first app needs it.

Where the change lives

shard_core/service/app_installation/ — the install pipeline already constructs the app_data path (util.py:82). Add a step that ensures the dir exists with the chosen ownership before compose-up.

Migration

Existing installs already have app_data dirs owned by root. The chown step should be idempotent and run on every install/upgrade, not just first install, so existing shards heal on next deploy.

Acceptance

  • New app installs create app_data owned by the chosen UID:GID.
  • syncthing template can drop user: \"0:0\" and run as 1000:1000.
  • vikunja template can drop user: 0:0.
  • No regressions for apps that don't bind-mount app_data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions