Ready to run under upaas on fsn1app1: prod branch, settings from env, health check, README section #31

Closed
opened 2026-09-25 11:02:24 +02:00 by clawbot · 2 comments
Collaborator

sneak's goal is six apps in beta under upaas (https://git.eeqj.de/sneak/upaas) on fsn1app1: webhooker and pixa first, then dnswatcher, netwatch and routewatch (his ruling of 24 September, sneak/project-management#1). This is routewatch's readiness issue; it starts after webhooker and pixa. Setting the app up in upaas and deploying it are sneak's.

It builds on #3, which keeps routewatch under 5 GiB of memory on the real feed and documents the container memory limit.

Definition of done

  • A prod branch exists, cut from main. It moves forward only through reviewed main to prod PRs (sneak's ruling of 30 August).
  • The image built from Dockerfile runs routewatch with its state under one volume path, which survives a restart.
  • Every setting comes from an environment variable; a value that is set but invalid stops the start.
  • The image has a HEALTHCHECK.
  • README.md has a short "Running under upaas" section listing exactly what the upaas app needs: the container port, the volume path, each environment variable with its value, the memory limit from issue 3, and the health check.
  • Code changes land on next through reviewed PRs, with make check green.

Model: opus-5-5

sneak's goal is six apps in beta under upaas (https://git.eeqj.de/sneak/upaas) on fsn1app1: webhooker and pixa first, then dnswatcher, netwatch and routewatch (his ruling of 24 September, https://git.eeqj.de/sneak/project-management/issues/1). This is routewatch's readiness issue; it starts after webhooker and pixa. Setting the app up in upaas and deploying it are sneak's. It builds on https://git.eeqj.de/sneak/routewatch/issues/3, which keeps routewatch under 5 GiB of memory on the real feed and documents the container memory limit. ## Definition of done - A `prod` branch exists, cut from `main`. It moves forward only through reviewed `main` to `prod` PRs (sneak's ruling of 30 August). - The image built from `Dockerfile` runs routewatch with its state under one volume path, which survives a restart. - Every setting comes from an environment variable; a value that is set but invalid stops the start. - The image has a `HEALTHCHECK`. - `README.md` has a short "Running under upaas" section listing exactly what the upaas app needs: the container port, the volume path, each environment variable with its value, the memory limit from issue 3, and the health check. - Code changes land on `next` through reviewed PRs, with `make check` green. Model: opus-5-5
clawbot self-assigned this 2026-09-25 11:02:24 +02:00
Author
Collaborator

Implementer's brief. prod is cut from main at ddf0b2f; that part of the definition of done is done, and nothing else touches prod.

What upaas does (its main, 97a17e5), from its code:

  • Volume: a bind mount of an absolute host path onto a container path (internal/docker/client.go, buildMounts). upaas never creates the host directory and has no setting for the container user.
  • Port: per-app mappings of a host port to a container port.
  • Health check: the image's own HEALTHCHECK. 60 seconds after a deploy, upaas fails the deploy unless the container is healthy (internal/service/deploy/deploy.go, checkHealthAfterDelay).
  • Memory: the app's "Memory Limit" field (256m, 1g or plain bytes) becomes the container's memory limit (buildResources). upaas sets no swap limit, so on a host with swap Docker allows the same amount of swap again.
  • Environment: per-app environment variables.

What routewatch has on next (df9e23d): state under /var/lib/berlin.sneak.app.routewatch (from XDG_DATA_HOME=/var/lib, set in the image); entrypoint.sh starts as root, takes ownership of that directory, then runs the daemon as the routewatch user (UID 1000), so a root-owned fresh volume works; port 8080; a HEALTHCHECK on /.well-known/healthcheck.json that always probes port 8080, whatever PORT says. Settings read from the environment: PORT, DEBUG, XDG_DATA_HOME, and the image's GOMEMLIMIT and MALLOC_ARENA_MAX.

Changes, one PR to next:

  1. A value that is set but invalid stops the start with a clear error and a non-zero exit:
    • PORT: a whole number from 1 to 65535. Today a bad value only logs an error from the listener goroutine and the daemon runs on without HTTP (internal/server/server.go, Start). Check it before anything starts.
    • XDG_DATA_HOME: an absolute path, as the XDG spec requires. Today a relative one silently puts the database somewhere else.
    • MALLOC_ARENA_MAX: glibc ignores a bad value silently, so entrypoint.sh refuses anything but a positive whole number.
    • GOMEMLIMIT: the Go runtime already refuses a malformed value at start. Confirm that with the built image; add nothing.
    • DEBUG takes any text; nothing to check.
  2. The HEALTHCHECK probes the port PORT names, 8080 when unset (shell form with ${PORT:-8080}).
  3. README.md:
    • Configuration: the Linux state directory is /var/lib/berlin.sneak.app.routewatch (or under XDG_DATA_HOME), not /var/lib/routewatch/; add XDG_DATA_HOME to the table; say that a set but invalid value stops the start.
    • A short "Running under upaas" section after Memory, listing exactly: the container port 8080; one volume at container path /var/lib/berlin.sneak.app.routewatch, with the host directory created before the first deploy (the entrypoint takes ownership of it); environment: nothing required, leave XDG_DATA_HOME, GOMEMLIMIT and MALLOC_ARENA_MAX at the image's values, DEBUG=routewatch optional for the memory line in the log; Memory Limit 5g, the 5 GiB cap from #3, and that upaas sets no swap limit; the health check path, and that upaas reads it 60 seconds after a deploy.

Add no new settings and no new environment variables; the hard-coded defaults in internal/config/config.go stay. Tests cover each refusal in item 1 that is made in Go.

Checks (verify, publish none of the evidence), on an image built with make docker:

  1. Run it with --mount type=bind,source=DIR,target=/var/lib/berlin.sneak.app.routewatch on an empty root-owned directory (make it through a throwaway root container): the container is healthy within 60 seconds.
  2. Stop and remove it, start a new container on the same directory: it starts with the first run's database.
  3. Each invalid value from item 1, a malformed GOMEMLIMIT included, stops the start with a non-zero exit; PORT=9090 still ends healthy.
  4. The memory limit cannot be exercised on the build host: Docker there refuses --memory (the cgroup is in threaded mode). Say so on the PR as a disclosure; do not work around it.

Model: opus-5-5

Implementer's brief. `prod` is cut from `main` at `ddf0b2f`; that part of the definition of done is done, and nothing else touches `prod`. What upaas does (its `main`, `97a17e5`), from its code: - Volume: a bind mount of an absolute host path onto a container path (`internal/docker/client.go`, `buildMounts`). upaas never creates the host directory and has no setting for the container user. - Port: per-app mappings of a host port to a container port. - Health check: the image's own `HEALTHCHECK`. 60 seconds after a deploy, upaas fails the deploy unless the container is `healthy` (`internal/service/deploy/deploy.go`, `checkHealthAfterDelay`). - Memory: the app's "Memory Limit" field (`256m`, `1g` or plain bytes) becomes the container's memory limit (`buildResources`). upaas sets no swap limit, so on a host with swap Docker allows the same amount of swap again. - Environment: per-app environment variables. What routewatch has on `next` (`df9e23d`): state under `/var/lib/berlin.sneak.app.routewatch` (from `XDG_DATA_HOME=/var/lib`, set in the image); `entrypoint.sh` starts as root, takes ownership of that directory, then runs the daemon as the `routewatch` user (UID 1000), so a root-owned fresh volume works; port 8080; a `HEALTHCHECK` on `/.well-known/healthcheck.json` that always probes port 8080, whatever `PORT` says. Settings read from the environment: `PORT`, `DEBUG`, `XDG_DATA_HOME`, and the image's `GOMEMLIMIT` and `MALLOC_ARENA_MAX`. Changes, one PR to `next`: 1. A value that is set but invalid stops the start with a clear error and a non-zero exit: - `PORT`: a whole number from 1 to 65535. Today a bad value only logs an error from the listener goroutine and the daemon runs on without HTTP (`internal/server/server.go`, `Start`). Check it before anything starts. - `XDG_DATA_HOME`: an absolute path, as the XDG spec requires. Today a relative one silently puts the database somewhere else. - `MALLOC_ARENA_MAX`: glibc ignores a bad value silently, so `entrypoint.sh` refuses anything but a positive whole number. - `GOMEMLIMIT`: the Go runtime already refuses a malformed value at start. Confirm that with the built image; add nothing. - `DEBUG` takes any text; nothing to check. 2. The `HEALTHCHECK` probes the port `PORT` names, 8080 when unset (shell form with `${PORT:-8080}`). 3. `README.md`: - Configuration: the Linux state directory is `/var/lib/berlin.sneak.app.routewatch` (or under `XDG_DATA_HOME`), not `/var/lib/routewatch/`; add `XDG_DATA_HOME` to the table; say that a set but invalid value stops the start. - A short "Running under upaas" section after Memory, listing exactly: the container port `8080`; one volume at container path `/var/lib/berlin.sneak.app.routewatch`, with the host directory created before the first deploy (the entrypoint takes ownership of it); environment: nothing required, leave `XDG_DATA_HOME`, `GOMEMLIMIT` and `MALLOC_ARENA_MAX` at the image's values, `DEBUG=routewatch` optional for the memory line in the log; Memory Limit `5g`, the 5 GiB cap from https://git.eeqj.de/sneak/routewatch/issues/3, and that upaas sets no swap limit; the health check path, and that upaas reads it 60 seconds after a deploy. Add no new settings and no new environment variables; the hard-coded defaults in `internal/config/config.go` stay. Tests cover each refusal in item 1 that is made in Go. Checks (verify, publish none of the evidence), on an image built with `make docker`: 1. Run it with `--mount type=bind,source=DIR,target=/var/lib/berlin.sneak.app.routewatch` on an empty root-owned directory (make it through a throwaway root container): the container is `healthy` within 60 seconds. 2. Stop and remove it, start a new container on the same directory: it starts with the first run's database. 3. Each invalid value from item 1, a malformed `GOMEMLIMIT` included, stops the start with a non-zero exit; `PORT=9090` still ends `healthy`. 4. The memory limit cannot be exercised on the build host: Docker there refuses `--memory` (the cgroup is in threaded mode). Say so on the PR as a disclosure; do not work around it. Model: opus-5-5
Author
Collaborator

PR: #32

Model: opus-5-5

PR: https://git.eeqj.de/sneak/routewatch/pulls/32 Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/routewatch#31