check / check (push) Waiting to run
The final image stage now apt-gets ca-certificates, nix-bin and runit from Ubuntu's archive, with no snapshot and no hash check of the package lists. The borrowed CA bundle is dropped: apt fetches the packages over plain HTTP and checks each one against the archive's signed package lists, so the install needs none. The Ubuntu base image stays pinned by digest, and every other pin is kept. README.md, SPEC.md and docs/image.md no longer describe a snapshot. Model: deepseek-v4-pro (implementation), opus-5-5 (rework)
200 lines
8.1 KiB
Docker
200 lines
8.1 KiB
Docker
# Lint phase. The linter is invoked directly rather than through `make
|
|
# lint` or `script/lint`, which are themselves a docker build and would
|
|
# recurse into a daemon that does not exist in a build step.
|
|
#
|
|
# golangci/golangci-lint v2.14.0 (built with go1.27.0), 2026-09-24
|
|
FROM golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint
|
|
|
|
WORKDIR /src
|
|
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
COPY . .
|
|
|
|
RUN golangci-lint run --config .golangci.yml ./...
|
|
|
|
# Test phase, same shape and for the same reason. The go directive in
|
|
# go.mod is a minimum, so this Go may be newer than the linter's. The
|
|
# Debian image rather than the Alpine one, because the race detector
|
|
# needs the C compiler it carries.
|
|
#
|
|
# golang 1.27.1-trixie, 2026-09-19
|
|
FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275ed5 AS test
|
|
|
|
WORKDIR /src
|
|
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
COPY . .
|
|
|
|
# go.mod and go.sum must be as `go mod tidy` writes them, which is what
|
|
# `make tidy` does. Checked before the tests, which a missing go.sum line
|
|
# fails with a message that does not name `make tidy`.
|
|
RUN go mod tidy -diff || \
|
|
{ echo "go.mod or go.sum is not tidy: run make tidy" >&2; exit 1; }
|
|
|
|
# Go's build cache is kept on a tmpfs, out of the image: nothing uses it
|
|
# after this step, and writing it into the image takes seconds. The tests
|
|
# are built with the no_fs_access tag, as the binary is in the build stage.
|
|
RUN --mount=type=tmpfs,target=/root/.cache/go-build \
|
|
go test -tags no_fs_access -timeout 90s -race -cover ./... || \
|
|
{ echo "--- Rerunning with -v for details ---"; \
|
|
go test -tags no_fs_access -timeout 90s -race -v ./...; exit 1; }
|
|
|
|
# Tidy stage: `go mod tidy` in the test phase's Go, so that the files it
|
|
# writes pass the test phase's check. Nothing else depends on it, so only
|
|
# script/tidy, which names the stage after it, builds it.
|
|
#
|
|
# golang 1.27.1-trixie, 2026-09-19
|
|
FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275ed5 AS tidy
|
|
|
|
WORKDIR /src
|
|
|
|
COPY . .
|
|
|
|
RUN go mod tidy
|
|
|
|
# go.mod and go.sum alone, which script/tidy writes into the working tree.
|
|
FROM scratch AS tidy-files
|
|
|
|
COPY --from=tidy /src/go.mod /src/go.sum /
|
|
|
|
# Build stage. Nothing is wanted from the lint and test phases; the copies
|
|
# are what make BuildKit build them first, so the image, which needs this
|
|
# stage, cannot be produced unless lint and test passed.
|
|
#
|
|
# golang 1.27.1-trixie, 2026-09-19
|
|
FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275ed5 AS builder
|
|
|
|
COPY --from=lint /src/go.sum /dev/null
|
|
COPY --from=test /src/go.sum /dev/null
|
|
|
|
# This image has git. A tar-stream context keeps the sender's file
|
|
# owners, which git refuses.
|
|
RUN git config --system --add safe.directory /src
|
|
|
|
WORKDIR /src
|
|
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
COPY . .
|
|
|
|
# The VERSION build arg when one is given, otherwise
|
|
# `git describe --tags --always` on the .git in the build context. With
|
|
# .git present, a version that is still empty, dev or unknown fails the
|
|
# build: git is missing or could not read the checkout. The no_fs_access
|
|
# tag keeps Coraza from writing the files of a multipart body to the
|
|
# system's temporary directory, since smallwebwaf writes only to its state
|
|
# directory.
|
|
ARG VERSION
|
|
RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \
|
|
if [ -e .git ]; then \
|
|
case "$VERSION" in ""|dev|unknown) \
|
|
echo "version is '$VERSION' although .git is present" >&2; \
|
|
exit 1 ;; \
|
|
esac; \
|
|
fi; \
|
|
CGO_ENABLED=0 go build -tags no_fs_access -trimpath \
|
|
-ldflags="-s -w -X main.Version=${VERSION}" \
|
|
-o /usr/local/bin/smallwebwaf ./cmd/smallwebwaf
|
|
|
|
# runsvinit, the image's entrypoint, built at the last commit of its
|
|
# archived repository. It has no go.mod, and `go build` of its directory
|
|
# needs one; it uses only the standard library, so the one written here
|
|
# names nothing else.
|
|
#
|
|
# golang 1.27.1-trixie, 2026-09-19
|
|
FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275ed5 AS runsvinit
|
|
|
|
RUN git clone --quiet https://github.com/peterbourgon/runsvinit /src
|
|
WORKDIR /src
|
|
# runsvinit v2.0.0-8-gb4b2c78, 2015-10-07
|
|
RUN git checkout --quiet --detach b4b2c785308b1ce785b6155c7fe5f16879080193 \
|
|
&& go mod init github.com/peterbourgon/runsvinit \
|
|
&& CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" \
|
|
-o /usr/local/bin/runsvinit .
|
|
|
|
# The image, described under "Deployment" in SPEC.md. It runs rproxy unless
|
|
# SMALLWEBWAF_COMMAND says servewaf, and an app's Dockerfile can build FROM it.
|
|
# It is the last stage, so a plain `docker build .` builds it.
|
|
#
|
|
# ubuntu 26.04, 2026-09-27
|
|
FROM ubuntu@sha256:f144425ff09be612d6d9ad965196e9cdc23dae1f42110a8a11a3e9a8198759f7
|
|
|
|
# runit's install creates its _runit-log user with minsysusers, which
|
|
# reads this file in place of runit's /usr/lib/sysusers.d/runit.conf.
|
|
# runit's line leaves out the shell, and minsysusers prints a Perl
|
|
# warning for that; this copy of it names /sbin/nologin, the shell
|
|
# minsysusers gives when none is named.
|
|
RUN mkdir /etc/sysusers.d \
|
|
&& echo 'u _runit-log - "runit svlogd user" /nonexistent /sbin/nologin' \
|
|
> /etc/sysusers.d/runit.conf
|
|
|
|
# ca-certificates, nix-bin and runit, from Ubuntu's archive. apt fetches
|
|
# them over plain HTTP and checks each one against the archive's signed
|
|
# package lists, so no CA certificates are needed.
|
|
RUN apt-get update \
|
|
&& DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
|
|
ca-certificates nix-bin runit \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Nix run by root expects a group of build users, which nix-bin does not
|
|
# create; with the setting empty, root's builds run without them.
|
|
RUN mkdir /etc/nix && echo 'build-users-group =' > /etc/nix/nix.conf
|
|
|
|
# nixpkgs, from its release file, checked by SHA-256, and set up for root
|
|
# as `nixpkgs`, so that an app's Dockerfile installs a package with
|
|
# `nix-env -iA nixpkgs.<name>`. curl and xz come with nix-bin.
|
|
#
|
|
# nixpkgs nixos-26.05.11045.774debe7a0d1, 2026-10-02
|
|
RUN curl -fsSL -o /tmp/nixexprs.tar.xz \
|
|
https://releases.nixos.org/nixos/26.05/nixos-26.05.11045.774debe7a0d1/nixexprs.tar.xz \
|
|
&& echo 'b2994104605601690023a5a6a3bb5a07b2bd1716b4e3b208cba1056dacd2ab08 /tmp/nixexprs.tar.xz' \
|
|
| sha256sum --check --strict \
|
|
&& mkdir -p /root/.nix-defexpr/nixpkgs \
|
|
&& tar -xJf /tmp/nixexprs.tar.xz -C /root/.nix-defexpr/nixpkgs --strip-components=1 \
|
|
&& rm /tmp/nixexprs.tar.xz
|
|
|
|
# What root installs with nix-env lands in root's profile. This path to
|
|
# it works for every user, unlike /root/.nix-profile: only root can
|
|
# enter /root. It comes last, so that no package shadows the image's
|
|
# own tools: busybox, for one, brings an sv that looks for services
|
|
# elsewhere.
|
|
ENV PATH=${PATH}:/nix/var/nix/profiles/default/bin
|
|
|
|
COPY --from=runsvinit /usr/local/bin/runsvinit /usr/local/bin/runsvinit
|
|
COPY --from=builder /usr/local/bin/smallwebwaf /usr/local/bin/smallwebwaf
|
|
|
|
# 65532 is above the uids Ubuntu keeps for system users, which end at
|
|
# 999; useradd warns about it unless --key raises that end for this call.
|
|
RUN groupadd --system --gid 65532 smallwebwaf \
|
|
&& useradd --system --key SYS_UID_MAX=65532 --uid 65532 \
|
|
--gid smallwebwaf --no-create-home --shell /usr/sbin/nologin \
|
|
smallwebwaf
|
|
|
|
# The state files' directory, SWWAF_STATE_DIR by default, where a volume
|
|
# is mounted to keep them across deploys. The run script gives it to the
|
|
# smallwebwaf user at each start of servewaf.
|
|
RUN mkdir /var/lib/smallwebwaf
|
|
|
|
# The default rule file, in SWWAF_RULES_DIR by default, where a Dockerfile
|
|
# built FROM this image can copy rule files of its own beside it.
|
|
COPY share/rules.d/00-default.rules /etc/smallwebwaf/rules.d/00-default.rules
|
|
|
|
# runsvinit starts runit's runsvdir on /etc/service, where Ubuntu's sv
|
|
# looks too.
|
|
COPY --chmod=755 share/smallwebwaf.run /etc/service/smallwebwaf/run
|
|
|
|
EXPOSE 8080
|
|
|
|
# traefik sends a container no requests until it is healthy, so the
|
|
# check runs every second from the start until it first passes, for up
|
|
# to a minute, and every 30 seconds after that.
|
|
HEALTHCHECK --start-period=1m --start-interval=1s \
|
|
CMD ["/usr/local/bin/smallwebwaf", "healthcheck"]
|
|
|
|
ENTRYPOINT ["/usr/local/bin/runsvinit"]
|