Build the smallwebwaf image on Ubuntu 26.04 LTS with nixpkgs #37

Merged
clawbot merged 1 commits from issue-34-ubuntu-nixpkgs into next 2026-09-29 02:43:56 +02:00
2 changed files with 66 additions and 30 deletions
Showing only changes of commit 9462144f33 - Show all commits
+16 -8
View File
@@ -147,20 +147,23 @@ For each request `smallwebwaf`:
due, and writes the log line.
A minimal deployment is the app's own Dockerfile, built on the `smallwebwaf`
image, with no setting. Beyond its `FROM` line it adds the app's binary, any
packages it needs, and the app's runit service, which starts the app as a user
of its own, listening on `127.0.0.1:8081`:
image, with no setting. That image is built on Ubuntu 26.04 LTS, the newest
long-term support release of Ubuntu, pinned by digest, and moves to the next one
when it ships. It has nixpkgs installed, so the app adds the packages it needs
from nixpkgs. Beyond its `FROM` line the app's Dockerfile adds the app's binary,
any packages it needs, and the app's runit service, which starts the app as a
user of its own, listening on `127.0.0.1:8081`:
```dockerfile
# The smallwebwaf image, pinned by digest.
FROM <registry>/smallwebwaf:<pinned digest>
# Packages the app needs, if any.
RUN apk add --no-cache tzdata
# Packages the app needs, if any, from the nixpkgs in the image.
RUN nix-env -iA nixpkgs.git
# The app's binary, and a user of its own to run it.
COPY app /usr/local/bin/app
RUN adduser -D -H -s /sbin/nologin app
RUN useradd --system --no-create-home --shell /usr/sbin/nologin app
# The app's runit service.
COPY --chmod=755 app.run /etc/service/app/run
@@ -169,8 +172,9 @@ COPY --chmod=755 app.run /etc/service/app/run
with `app.run` beside the Dockerfile, where `--listen` and `--trusted-proxies`
stand for the app's own options:
```sh
#!/bin/sh
```bash
#!/usr/bin/env bash
set -euo pipefail
sleep 1
exec chpst -u app:app /usr/local/bin/app \
--listen 127.0.0.1:8081 \
@@ -180,6 +184,10 @@ exec chpst -u app:app /usr/local/bin/app \
- The image's entrypoint, `runsvinit`, has runit start `smallwebwaf` and the app
side by side, each as its own user, and start either again a second after it
exits. Leave out `ENTRYPOINT` and `USER` from the app's Dockerfile.
- `nix-env -iA nixpkgs.<name>` installs a package from the nixpkgs in the image,
and the app finds it on its `PATH`. That nixpkgs is fixed at one commit, so
the same `smallwebwaf` image always gives the app the same packages; newer
ones come with a newer `smallwebwaf` image.
- Deploy it as you deploy any app, with traefik's labels on this one container
pointing at port 8080. upaas needs no change for this.
- The app has to trust `127.0.0.1` and `::1` for forwarded headers, besides the
+50 -22
View File
@@ -44,10 +44,11 @@ from a directory of hand-editable text files.
## Architecture
- One statically linked Go binary, shipped in an Alpine Linux image that is the
base of the app's own image, so that `smallwebwaf` and the app run in one
container (see "Deployment"). There runit starts `smallwebwaf` as its own
non-root user, and `smallwebwaf` writes only to its state directory.
- One statically linked Go binary, shipped in an image built on Ubuntu 26.04 LTS
with nixpkgs installed, which is the base of the app's own image, so that
`smallwebwaf` and the app run in one container (see "Deployment"). There runit
starts `smallwebwaf` as its own non-root user, and `smallwebwaf` writes only
to its state directory.
- One listener, on port 8080, which traefik routes to. It forwards requests to
the app on `127.0.0.1:8081`, and it answers the endpoints of `smallwebwaf`
itself for health, metrics and ban management, under `/_smallwebwaf/`, which
@@ -1136,18 +1137,27 @@ One JSON object per alert:
The recommended deployment puts `smallwebwaf` inside the app's own container:
the app's Dockerfile builds `FROM` the `smallwebwaf` image. Each app remains one
container, deployed as before, and an app is hardened just by changing its base
image and perhaps installing on top of it the packages it needs. In the
container, `smallwebwaf` listens on port 8080, which traefik routes to, and
forwards each request to the app on `127.0.0.1:8081`, its default backend
image and perhaps installing on top of it, from nixpkgs, the packages it needs.
In the container, `smallwebwaf` listens on port 8080, which traefik routes to,
and forwards each request to the app on `127.0.0.1:8081`, its default backend
(`SWWAF_UPSTREAM_URL`). No setting is required.
The image holds:
- Alpine Linux, so that an app built on it installs packages with `apk add`.
- runit, and `runsvinit` as the entrypoint. `runsvinit` starts runit's
`runsvdir`, which starts a `runsv` for each directory under `/etc/service`;
each `runsv` runs the `run` script in its directory, and runs it again
whenever it exits.
- Ubuntu 26.04 LTS, the newest long-term support release of Ubuntu, pinned by
digest. The image moves to the next LTS release when that ships.
- Nix, the package manager, from Ubuntu's own `nix-bin` package, and nixpkgs,
the collection of packages Nix installs from, fixed at one commit (see
"Packages from nixpkgs" below). Root uses Nix directly, and no Nix daemon runs
in the container.
- runit, from Ubuntu's own `runit` package, and `runsvinit` as the entrypoint.
Neither Ubuntu nor nixpkgs packages `runsvinit`, so the image builds it from
its source (`github.com/peterbourgon/runsvinit`) at a version fixed by hash.
`runsvinit` starts runit's `runsvdir`, which starts a `runsv` for each
directory under `/etc/service`; each `runsv` runs the `run` script in its
directory, and runs it again whenever it exits. Ubuntu's runit looks for
services in `/etc/service` too, so when `docker stop` has `runsvinit` stop
each service with runit's `sv`, `sv` finds it.
- The `smallwebwaf` binary, and a user of its own, `smallwebwaf` (uid and gid
65532).
- The service directory `/etc/service/smallwebwaf`, whose `run` script waits one
@@ -1158,15 +1168,20 @@ The image holds:
- Port 8080 declared (`EXPOSE 8080`), and the health check described below
(`HEALTHCHECK`).
Every `run` script, the app's included, is a bash script that starts with
`#!/usr/bin/env bash` and `set -euo pipefail`, as the owner's code style guide
asks; Ubuntu ships bash.
Beyond its `FROM` line, the app's Dockerfile adds:
- its binary;
- any packages it needs, with `apk add`;
- any packages it needs, from nixpkgs, with `nix-env -iA nixpkgs.<name>`;
- its runit service: a directory under `/etc/service` named after the app, never
`smallwebwaf`, whose `run` script starts with `sleep 1` and then starts the
app with `chpst`, listening on `127.0.0.1:8081`, as a user of its own that the
Dockerfile creates. That user is neither root nor `smallwebwaf`, so that the
app cannot touch the state files or the process of `smallwebwaf`.
`smallwebwaf`, whose `run` script waits one second (`sleep 1`) and then starts
the app with `chpst`, listening on `127.0.0.1:8081`, as a user of its own that
the Dockerfile creates with `useradd`. That user is neither root nor
`smallwebwaf`, so that the app cannot touch the state files or the process of
`smallwebwaf`.
It sets no `ENTRYPOINT` or `USER` of its own: the container must start
`runsvinit`, as root, so that it can start each service as its own user.
@@ -1178,12 +1193,12 @@ listens on and the proxies it trusts as options of its own:
# The smallwebwaf image, pinned by digest.
FROM <registry>/smallwebwaf:<pinned digest>
# Packages the app needs, if any.
RUN apk add --no-cache tzdata
# Packages the app needs, if any, from the nixpkgs in the image.
RUN nix-env -iA nixpkgs.git
# The app's binary, and a user of its own to run it.
COPY app /usr/local/bin/app
RUN adduser -D -H -s /sbin/nologin app
RUN useradd --system --no-create-home --shell /usr/sbin/nologin app
# The app's runit service.
COPY --chmod=755 app.run /etc/service/app/run
@@ -1191,14 +1206,27 @@ COPY --chmod=755 app.run /etc/service/app/run
with `app.run` beside the Dockerfile:
```sh
#!/bin/sh
```bash
#!/usr/bin/env bash
set -euo pipefail
sleep 1
exec chpst -u app:app /usr/local/bin/app \
--listen 127.0.0.1:8081 \
--trusted-proxies 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32,::1/128
```
Packages from nixpkgs: nixpkgs is fixed at one commit of its newest release
branch, `nixos-26.05` today. The image's Dockerfile names the commit and the
hash of its contents, and the build checks that hash. 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. 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:
- `runsvinit` is the container's first process and runs as root, as do runit's