The image apps build FROM, with its health check (closes #45)
check / check (push) Failing after 3s

The Dockerfile's last stage is now the image of "Deployment" in SPEC.md:
Ubuntu 26.04 with ca-certificates, nix-bin and runit from a dated
snapshot whose InRelease files are checked by hash, nixpkgs from its
release file checked by SHA-256, runsvinit built at a fixed commit, and
smallwebwaf as a runit service. smallwebwaf answers
/_smallwebwaf/healthz, and `smallwebwaf healthcheck`, which takes no
further argument, is the image's HEALTHCHECK. script/example-app builds
an app on the image and checks it end to end.

The Nix profile comes last on the PATH: first, busybox from nixpkgs
replaced runit's own runsvdir and sv. SPEC.md is corrected to match
what was built.

Model: opus-5-5
This commit was merged in pull request #57.
This commit is contained in:
2026-10-04 10:05:30 +02:00
parent 0750879e58
commit d4f90dba37
17 changed files with 617 additions and 71 deletions
+37 -24
View File
@@ -1263,14 +1263,19 @@ image's digest does. `apt-get update` keeps the snapshot's `InRelease` files,
which apt checks against the archive's signature, in `/var/lib/apt/lists/`; each
lists the SHA-256 hash of the package lists it covers, and each package list the
hash of every package in it. The Dockerfile also names the SHA-256 hash of each
`InRelease` file apt uses, and the build checks them after `apt-get update` and
before `apt-get install`, so every package apt installs is checked, through
those files, against hashes the Dockerfile names. The snapshot service is
reached over HTTPS, and the Ubuntu image has no CA certificates of its own, so
this one install uses those of the Go image that `smallwebwaf` is built in,
which is pinned by digest too: apt's `Acquire::https::CaInfo` option names that
image's CA certificate file, `/etc/ssl/certs/ca-certificates.crt`, copied into
the build.
of the snapshot's `InRelease` files, and the build checks them after
`apt-get update` and before `apt-get install`, so every package apt installs is
checked, through those files, against hashes the Dockerfile names.
`apt-get update` also fetches the live archive's `InRelease` files into the same
directory; their hashes change whenever the archive does, and the install does
not use them, so the check leaves them out. The hashes are those of the archive
for amd64, and so the image is built for amd64: other architectures use Ubuntu's
ports archive, whose `InRelease` files differ. The snapshot service is reached
over HTTPS, and the Ubuntu image has no CA certificates of its own, so this one
install uses those of the Go image that `smallwebwaf` is built in, which is
pinned by digest too: apt's `Acquire::https::CaInfo` option names that image's
CA certificate file, `/etc/ssl/certs/ca-certificates.crt`, mounted for that one
step.
Packages from nixpkgs: nixpkgs is fixed at one commit of its newest release
branch, `nixos-26.05` today. For each commit of the branch that has passed its
@@ -1281,15 +1286,18 @@ bytes can change. The image's Dockerfile names the release and the SHA-256 hash
of that file, which the release's page lists, and the build checks the hash
before unpacking it. nixpkgs is set up for root under the name `nixpkgs`, so the
app's Dockerfile installs a package with `nix-env -iA nixpkgs.<name>`, and
whatever it installs is on the `PATH` of every service. Because nixpkgs stays at
that commit, an app built on the same `smallwebwaf` image gets the same packages
each time it is built. Unpacked, nixpkgs takes about 500 MiB of disk, more on
some filesystems such as ZFS, and each package an app installs from it adds its
own size, with everything it depends on. A newer commit of the branch, with its
security fixes, comes with a newer `smallwebwaf` image, as do Ubuntu's own
fixes; an app takes them by changing the digest in its `FROM` line. When nixpkgs
makes its next release, every six months, the image moves to that release's
branch.
whatever it installs is on the `PATH` of every service: the image adds root's
Nix profile, `/nix/var/nix/profiles/default/bin`, at the end of the `PATH`,
after Ubuntu's own directories, so that no package hides the image's own
commands. busybox, for one, brings its own `sv`, which looks for services
elsewhere. Because nixpkgs stays at that commit, an app built on the same
`smallwebwaf` image gets the same packages each time it is built. Unpacked,
nixpkgs takes about 500 MiB of disk, more on some filesystems such as ZFS, and
each package an app installs from it adds its own size, with everything it
depends on. A newer commit of the branch, with its security fixes, comes with a
newer `smallwebwaf` image, as do Ubuntu's own fixes; an app takes them by
changing the digest in its `FROM` line. When nixpkgs makes its next release,
every six months, the image moves to that release's branch.
The two processes:
@@ -1315,12 +1323,15 @@ The two processes:
- The container's root filesystem stays writable: runit writes each service's
status into its directory under `/etc/service`.
The health check: the image's `HEALTHCHECK` passes while `smallwebwaf` answers
`GET /_smallwebwaf/healthz` on `127.0.0.1`, at the port in `SWWAF_LISTEN_ADDR`,
and the app accepts connections at the address in `SWWAF_UPSTREAM_URL`, and
fails when either does not. The container therefore shows as healthy only while
both processes are up. An app with a health check of its own can replace the
image's `HEALTHCHECK` with one that checks both.
The health check: the image's `HEALTHCHECK` runs `smallwebwaf healthcheck`,
which passes while `smallwebwaf` answers `GET /_smallwebwaf/healthz` on
`127.0.0.1`, at the port in `SWWAF_LISTEN_ADDR`, and the app accepts connections
at the address in `SWWAF_UPSTREAM_URL`, and fails when either does not. The
container therefore shows as healthy only while both processes are up. traefik
sends a container no requests until it shows as healthy, so the check runs every
second from the container's start until it first passes, for up to a minute, and
every 30 seconds after that. An app with a health check of its own can replace
the image's `HEALTHCHECK` with one that checks both.
Ports: `smallwebwaf` listens on port 8080 on every address and on no other port;
its health check, metrics and ban management are all on that listener, under
@@ -1609,7 +1620,9 @@ holds any token file.
- The container image described under "Deployment", with runit and the
container's health check. The health check calls `/_smallwebwaf/healthz`,
so milestone 2 answers that path, although the other admin endpoints come
later.
later. The image's `/var/lib/smallwebwaf`, which the `run` script gives to
the `smallwebwaf` user, and `/etc/smallwebwaf/rules.d` come with the state
files and the rule files.
- Milestone 3 and later: the rest of the design, in this order:
- static lists, the bans that broken request limits lead to, the ban ledger
and the JSON state files with edits taken in while running, exemptions,