docker: run as non-root, add health check, document upaas #156

Merged
clawbot merged 1 commits from issue-147-upaas-deploy-readiness into next 2026-09-28 20:13:38 +02:00
Collaborator

Deploy readiness for upaas, #147.

  • The runtime image runs as dnswatcher (uid and gid 10001), which owns /var/lib/dnswatcher. The binary is at /usr/local/bin/dnswatcher and the working directory is /: config loading also reads a .env file and a dnswatcher config file from the working directory, so every setting now comes from the environment.
  • Startup fails with "data directory ... is not writable" when the data directory cannot be written, so the container exits and upaas marks the deploy failed. Before, it ran, reported healthy, and lost every save. The check is in the state startup hook: it creates the directory if needed, then writes and removes the temp file that saving uses.
  • Docker HEALTHCHECK: busybox wget against /.well-known/healthcheck every 10 seconds.
  • README "Running under upaas": prod branch, host directory commands, network and port, environment, health check.

Trial run on fbc021e, which differs from this head only in a test: host directory prepared with the README commands and bind-mounted, reached over a Docker network with no port mapping, five real targets, 30-second DNS interval, one restart, then a root-owned host directory for the failure case.

Defects found, not fixed here:

  • #157: record values compared with letter case, so eeqj.de is reported as inconsistent.
  • #158: the inconsistency alert repeats every DNS cycle.

Disclosures:

  • Behavior change: a run outside Docker as a non-root user with the default data directory now exits at startup instead of running without saving.
  • Judgement call: the health probe runs every 10 seconds, where webhooker and pixa use 30.

Model: opus-5-5

Deploy readiness for upaas, https://git.eeqj.de/sneak/dnswatcher/issues/147. - The runtime image runs as `dnswatcher` (uid and gid 10001), which owns `/var/lib/dnswatcher`. The binary is at `/usr/local/bin/dnswatcher` and the working directory is `/`: config loading also reads a `.env` file and a `dnswatcher` config file from the working directory, so every setting now comes from the environment. - Startup fails with "data directory ... is not writable" when the data directory cannot be written, so the container exits and upaas marks the deploy failed. Before, it ran, reported healthy, and lost every save. The check is in the state startup hook: it creates the directory if needed, then writes and removes the temp file that saving uses. - Docker `HEALTHCHECK`: busybox `wget` against `/.well-known/healthcheck` every 10 seconds. - README "Running under upaas": `prod` branch, host directory commands, network and port, environment, health check. Trial run on `fbc021e`, which differs from this head only in a test: host directory prepared with the README commands and bind-mounted, reached over a Docker network with no port mapping, five real targets, 30-second DNS interval, one restart, then a root-owned host directory for the failure case. Defects found, not fixed here: - https://git.eeqj.de/sneak/dnswatcher/issues/157: record values compared with letter case, so `eeqj.de` is reported as inconsistent. - https://git.eeqj.de/sneak/dnswatcher/issues/158: the inconsistency alert repeats every DNS cycle. Disclosures: - Behavior change: a run outside Docker as a non-root user with the default data directory now exits at startup instead of running without saving. - Judgement call: the health probe runs every 10 seconds, where webhooker and pixa use 30. Model: opus-5-5
clawbot added the needs-review label 2026-09-21 10:01:02 +02:00
clawbot self-assigned this 2026-09-21 10:01:02 +02:00
clawbot added needs-rebase and removed needs-review labels 2026-09-21 14:54:27 +02:00
clawbot added needs-rework and removed needs-rebase labels 2026-09-28 18:58:48 +02:00
clawbot force-pushed issue-147-upaas-deploy-readiness from df80bc1fb6 to cc55720167 2026-09-28 19:06:10 +02:00 Compare
clawbot force-pushed issue-147-upaas-deploy-readiness from cc55720167 to fbc021e21b 2026-09-28 19:12:58 +02:00 Compare
clawbot changed title from docker: run as non-root, add HEALTHCHECK, document upaas deploy to docker: run as non-root, add health check, document upaas 2026-09-28 19:18:37 +02:00
Author
Collaborator

Rework against the plan in #147 (comment):

  1. Rebased onto current next; the TODO.md conflict keeps both entries.
  2. Branch is prod, cut from main; the "not confirmed" wording is gone.
  3. README gives mkdir -p, chown 10001:10001 and chmod 700 for the host directory; the image's group is now pinned to gid 10001 so that chown matches.
  4. The state startup hook writes and removes the temp file that saving uses, before loading state, and fails with "data directory <path> is not writable: ..." otherwise. Tests run the real startup hook: a data directory under a regular file (holds as root), and a missing directory that is created and left empty. README Monitoring Lifecycle notes the check.
  5. Working directory is /; the Dockerfile comment names the .env and dnswatcher config files that make the data directory, or the binary's directory, unsafe as the working directory.
  6. README: map port 8080 only for a public dashboard; otherwise join the reverse proxy's Docker network and reach upaas- plus the app name, port 8080.
  7. Health probe every 10 seconds (was 30); README states the 60-second health read.
  8. Heading is "Running under upaas" and covers each listed item, with DNSWATCHER_DATA_DIR and PORT under "Leave unset".
  9. Trial run on the final head, the upaas way, including the root-owned directory case. Two defects filed: #157 and #158.

Model: opus-5-5

Rework against the plan in https://git.eeqj.de/sneak/dnswatcher/issues/147#issuecomment-103914: 1. Rebased onto current `next`; the `TODO.md` conflict keeps both entries. 2. Branch is `prod`, cut from `main`; the "not confirmed" wording is gone. 3. README gives `mkdir -p`, `chown 10001:10001` and `chmod 700` for the host directory; the image's group is now pinned to gid 10001 so that `chown` matches. 4. The state startup hook writes and removes the temp file that saving uses, before loading state, and fails with "data directory &lt;path&gt; is not writable: ..." otherwise. Tests run the real startup hook: a data directory under a regular file (holds as root), and a missing directory that is created and left empty. README Monitoring Lifecycle notes the check. 5. Working directory is `/`; the Dockerfile comment names the `.env` and `dnswatcher` config files that make the data directory, or the binary's directory, unsafe as the working directory. 6. README: map port `8080` only for a public dashboard; otherwise join the reverse proxy's Docker network and reach `upaas-` plus the app name, port `8080`. 7. Health probe every 10 seconds (was 30); README states the 60-second health read. 8. Heading is "Running under upaas" and covers each listed item, with `DNSWATCHER_DATA_DIR` and `PORT` under "Leave unset". 9. Trial run on the final head, the upaas way, including the root-owned directory case. Two defects filed: https://git.eeqj.de/sneak/dnswatcher/issues/157 and https://git.eeqj.de/sneak/dnswatcher/issues/158. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-09-28 19:18:48 +02:00
Author
Collaborator
  • internal/state/state_test.go, TestStartupFailsWhenDataDirNotWritable: the test only covers a data directory that cannot be created, which the os.MkdirAll call in checkDataDirWritable already catches. No test reaches the temp-file write in internal/state/state.go. That write is the step that catches the upaas case this change is for: a host directory that already exists but uid 10001 cannot write to. Acceptable: a test that starts against an existing data directory where writing the temp file fails even when the tests run as root (for example, a directory already at the state.json.tmp path), and checks for the "not writable" error.

Model: opus-5-5

- `internal/state/state_test.go`, `TestStartupFailsWhenDataDirNotWritable`: the test only covers a data directory that cannot be created, which the `os.MkdirAll` call in `checkDataDirWritable` already catches. No test reaches the temp-file write in `internal/state/state.go`. That write is the step that catches the upaas case this change is for: a host directory that already exists but uid 10001 cannot write to. Acceptable: a test that starts against an existing data directory where writing the temp file fails even when the tests run as root (for example, a directory already at the `state.json.tmp` path), and checks for the "not writable" error. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-09-28 19:43:49 +02:00
clawbot added 1 commit 2026-09-28 19:57:54 +02:00
The runtime image runs as uid 10001, which owns /var/lib/dnswatcher. The
working directory is /, so config loading finds no .env or dnswatcher
config file there; the binary lives in /usr/local/bin. A Docker
HEALTHCHECK probes /.well-known/healthcheck every 10 seconds with busybox
wget, well inside the 60 seconds upaas waits.

Startup now fails with an error naming the data directory when it cannot
be written, instead of running with every save failing. The check creates
the directory if needed and writes and removes the temp file Save uses;
tests cover the create and the write failing.

README gains "Running under upaas": the prod branch, host directory
setup, network and port, environment and health check.

Model: opus-5-5
clawbot force-pushed issue-147-upaas-deploy-readiness from fbc021e21b to daef29e432 2026-09-28 19:57:54 +02:00 Compare
Author
Collaborator

New TestStartupFailsWhenExistingDataDirNotWritable starts against an existing data directory with a directory already at state.json.tmp, so the temp-file write fails, also as root, and checks for the "not writable" error. TestStartupFailsWhenDataDirNotWritable stays: it covers a data directory that cannot be created, which the new test does not reach.

PR body: the trial-run line now names fbc021e, since this head changes only the test file.

Model: opus-5-5

New `TestStartupFailsWhenExistingDataDirNotWritable` starts against an existing data directory with a directory already at `state.json.tmp`, so the temp-file write fails, also as root, and checks for the "not writable" error. `TestStartupFailsWhenDataDirNotWritable` stays: it covers a data directory that cannot be created, which the new test does not reach. PR body: the trial-run line now names `fbc021e`, since this head changes only the test file. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-09-28 19:58:24 +02:00
Author
Collaborator

Review passed on daef29e.

Model: opus-5-5

Review passed on `daef29e`. Model: opus-5-5
clawbot merged commit 148e47d9c0 into next 2026-09-28 20:13:38 +02:00
clawbot deleted branch issue-147-upaas-deploy-readiness 2026-09-28 20:13:38 +02:00
Sign in to join this conversation.