Part of #17 (definition of done: #17 (comment)). Start only after #111 (health check and smoke test) and #128 (every setting as an environment variable) are on next: the README section below lists what they add.
An app volume is a Docker bind mount of an absolute host path, typed in by the operator, onto an absolute container path (buildMounts in internal/docker/client.go, mount.TypeBind). upaas neither creates the host directory nor changes its owner, and Docker refuses a bind mount whose host directory does not exist, so the directory must exist before the first deploy.
upaas sets no container user; the image's USER applies.
upaas passes the app's environment variables and maps a host port to a container port.
60 seconds after a deploy upaas inspects the container: with a Docker HEALTHCHECK it requires healthy, without one only running (IsContainerHealthy).
The obstacle
The image runs as the unprivileged pixad user. A host directory made with mkdir as root is owned by root with mode 755, and pixa checks at startup that state_dir is writable, so on a fresh upaas volume the container exits at startup.
Definition of done
The container starts on a fresh, root-owned host directory bind-mounted at /var/lib/pixa. Recommended: the image starts as root only long enough to give /var/lib/pixa to pixad when pixad does not already own it, then runs the server as pixad through su-exec (alpine's package for this). The server process never runs as root. The alternative, a fixed user id plus a documented chown step, documents the obstacle rather than removing it; name it in the PR if you take it.
Checked once by hand and stated in one line in the PR body, no transcripts: build the image; create a root-owned host directory (created from inside a container running as root, so it is not owned by your own user); run the container twice with --mount type=bind,source=<that directory>,target=/var/lib/pixa and the same PIXA_SIGNING_KEY. The first run fetches and caches one image from an allowed host (set through PIXA_ALLOWLIST_HOSTS); the second run serves that image from the cache without fetching it again. Afterwards remove every container, image tag and directory the check created.
README.md gains a short "Running under upaas" section that lists exactly:
the container port: 8080;
the volume: container path /var/lib/pixa, and that the host directory must exist before the first deploy;
each environment variable upaas should set, with its meaning: PIXA_SIGNING_KEY (required), plus those a deployment usually sets, taken from the table #128 adds, with a pointer to that table for the rest;
the health check: the image's HEALTHCHECK on /.well-known/healthcheck.json, which upaas reads 60 seconds after a deploy; it probes the port from PORT (default 8080), so a port changed only in a mounted config file is not seen by it: change the port with PORT;
any first-run step (creating the host directory).
Only settings upaas actually has; invent nothing.
Model: opus-5-5
Part of https://git.eeqj.de/sneak/pixa/issues/17 (definition of done: https://git.eeqj.de/sneak/pixa/issues/17#issuecomment-102965). Start only after https://git.eeqj.de/sneak/pixa/issues/111 (health check and smoke test) and https://git.eeqj.de/sneak/pixa/issues/128 (every setting as an environment variable) are on `next`: the README section below lists what they add.
## What upaas does
Read from https://git.eeqj.de/sneak/upaas at `6026e39`; re-check against its current `main` before writing the README.
- An app volume is a Docker bind mount of an absolute host path, typed in by the operator, onto an absolute container path (`buildMounts` in `internal/docker/client.go`, `mount.TypeBind`). upaas neither creates the host directory nor changes its owner, and Docker refuses a bind mount whose host directory does not exist, so the directory must exist before the first deploy.
- upaas sets no container user; the image's `USER` applies.
- upaas passes the app's environment variables and maps a host port to a container port.
- 60 seconds after a deploy upaas inspects the container: with a Docker `HEALTHCHECK` it requires `healthy`, without one only running (`IsContainerHealthy`).
## The obstacle
The image runs as the unprivileged `pixad` user. A host directory made with `mkdir` as root is owned by root with mode 755, and pixa checks at startup that `state_dir` is writable, so on a fresh upaas volume the container exits at startup.
## Definition of done
1. The container starts on a fresh, root-owned host directory bind-mounted at `/var/lib/pixa`. Recommended: the image starts as root only long enough to give `/var/lib/pixa` to `pixad` when `pixad` does not already own it, then runs the server as `pixad` through `su-exec` (alpine's package for this). The server process never runs as root. The alternative, a fixed user id plus a documented `chown` step, documents the obstacle rather than removing it; name it in the PR if you take it.
2. Checked once by hand and stated in one line in the PR body, no transcripts: build the image; create a root-owned host directory (created from inside a container running as root, so it is not owned by your own user); run the container twice with `--mount type=bind,source=<that directory>,target=/var/lib/pixa` and the same `PIXA_SIGNING_KEY`. The first run fetches and caches one image from an allowed host (set through `PIXA_ALLOWLIST_HOSTS`); the second run serves that image from the cache without fetching it again. Afterwards remove every container, image tag and directory the check created.
3. `README.md` gains a short "Running under upaas" section that lists exactly:
- the container port: 8080;
- the volume: container path `/var/lib/pixa`, and that the host directory must exist before the first deploy;
- each environment variable upaas should set, with its meaning: `PIXA_SIGNING_KEY` (required), plus those a deployment usually sets, taken from the table https://git.eeqj.de/sneak/pixa/issues/128 adds, with a pointer to that table for the rest;
- the health check: the image's `HEALTHCHECK` on `/.well-known/healthcheck.json`, which upaas reads 60 seconds after a deploy; it probes the port from `PORT` (default 8080), so a port changed only in a mounted config file is not seen by it: change the port with `PORT`;
- any first-run step (creating the host directory).
Only settings upaas actually has; invent nothing.
Model: opus-5-5
clawbot
added this to the 1.0.0 milestone 2026-09-28 11:09:58 +02:00
clawbot
self-assigned this 2026-09-28 11:09:58 +02:00
Dockerfile runtime stage: add alpine's su-exec package and drop USER pixad, so the container starts as root. A new start-up script, deploy/docker-entrypoint.sh, becomes the ENTRYPOINT: when pixad does not own /var/lib/pixa, it gives the directory (not its contents) to pixad, then runs the server as pixad through su-exec. The server never runs as root; make docker-smoke and the HEALTHCHECK stay as they are.
The two-run check from item 2, done once by hand and stated in one line in the PR.
README.md: a "Running under upaas" section with the container port, the volume, PIXA_SIGNING_KEY plus PIXA_ALLOWLIST_HOSTS and PIXA_CACHE_MAX_BYTES from the Configuration table (with a pointer to it), the health check, and the first-run step. The upaas summary above still holds at upaas main97a17e5.
A TODO.md entry.
Judgement call: the image's user becomes root, so the health probe and docker exec also run as root; only the server drops to pixad.
Model: opus-5-5
Plan:
- `Dockerfile` runtime stage: add alpine's `su-exec` package and drop `USER pixad`, so the container starts as root. A new start-up script, `deploy/docker-entrypoint.sh`, becomes the `ENTRYPOINT`: when `pixad` does not own `/var/lib/pixa`, it gives the directory (not its contents) to `pixad`, then runs the server as `pixad` through `su-exec`. The server never runs as root; `make docker-smoke` and the `HEALTHCHECK` stay as they are.
- The two-run check from item 2, done once by hand and stated in one line in the PR.
- `README.md`: a "Running under upaas" section with the container port, the volume, `PIXA_SIGNING_KEY` plus `PIXA_ALLOWLIST_HOSTS` and `PIXA_CACHE_MAX_BYTES` from the Configuration table (with a pointer to it), the health check, and the first-run step. The upaas summary above still holds at upaas `main` `97a17e5`.
- A `TODO.md` entry.
Judgement call: the image's user becomes root, so the health probe and `docker exec` also run as root; only the server drops to `pixad`.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #17 (definition of done: #17 (comment)). Start only after #111 (health check and smoke test) and #128 (every setting as an environment variable) are on
next: the README section below lists what they add.What upaas does
Read from https://git.eeqj.de/sneak/upaas at
6026e39; re-check against its currentmainbefore writing the README.buildMountsininternal/docker/client.go,mount.TypeBind). upaas neither creates the host directory nor changes its owner, and Docker refuses a bind mount whose host directory does not exist, so the directory must exist before the first deploy.USERapplies.HEALTHCHECKit requireshealthy, without one only running (IsContainerHealthy).The obstacle
The image runs as the unprivileged
pixaduser. A host directory made withmkdiras root is owned by root with mode 755, and pixa checks at startup thatstate_diris writable, so on a fresh upaas volume the container exits at startup.Definition of done
/var/lib/pixa. Recommended: the image starts as root only long enough to give/var/lib/pixatopixadwhenpixaddoes not already own it, then runs the server aspixadthroughsu-exec(alpine's package for this). The server process never runs as root. The alternative, a fixed user id plus a documentedchownstep, documents the obstacle rather than removing it; name it in the PR if you take it.--mount type=bind,source=<that directory>,target=/var/lib/pixaand the samePIXA_SIGNING_KEY. The first run fetches and caches one image from an allowed host (set throughPIXA_ALLOWLIST_HOSTS); the second run serves that image from the cache without fetching it again. Afterwards remove every container, image tag and directory the check created.README.mdgains a short "Running under upaas" section that lists exactly:/var/lib/pixa, and that the host directory must exist before the first deploy;PIXA_SIGNING_KEY(required), plus those a deployment usually sets, taken from the table #128 adds, with a pointer to that table for the rest;HEALTHCHECKon/.well-known/healthcheck.json, which upaas reads 60 seconds after a deploy; it probes the port fromPORT(default 8080), so a port changed only in a mounted config file is not seen by it: change the port withPORT;Only settings upaas actually has; invent nothing.
Model: opus-5-5
Plan:
Dockerfileruntime stage: add alpine'ssu-execpackage and dropUSER pixad, so the container starts as root. A new start-up script,deploy/docker-entrypoint.sh, becomes theENTRYPOINT: whenpixaddoes not own/var/lib/pixa, it gives the directory (not its contents) topixad, then runs the server aspixadthroughsu-exec. The server never runs as root;make docker-smokeand theHEALTHCHECKstay as they are.README.md: a "Running under upaas" section with the container port, the volume,PIXA_SIGNING_KEYplusPIXA_ALLOWLIST_HOSTSandPIXA_CACHE_MAX_BYTESfrom the Configuration table (with a pointer to it), the health check, and the first-run step. The upaas summary above still holds at upaasmain97a17e5.TODO.mdentry.Judgement call: the image's user becomes root, so the health probe and
docker execalso run as root; only the server drops topixad.Model: opus-5-5
Built as planned in the comment above: #135
Model: opus-5-5
clawbot referenced this issue2026-09-28 15:13:30 +02:00