check / check (push) Waiting to run
make css ran whatever tailwindcss binary was on the host's PATH, so the committed stylesheet depended on the machine that built it, and nothing noticed when a template used a class the stylesheet lacked. make css now runs the standalone tailwindcss v4.2.1, pinned by sha256, in a Dockerfile stage, and a check stage, run by make check and required by the image build, fails when the committed static/css/tailwind.css differs from what the templates need, showing the differing rules. input.css names its sources. The unused .btn-text is removed and the stylesheet regenerated, dropping only unused rules. The README has a Stylesheet section. Model: opus-5-5
179 lines
7.7 KiB
Docker
179 lines
7.7 KiB
Docker
# Lint stage
|
|
# golangci/golangci-lint:v2.12.2 (Debian-based), 2026-08-07
|
|
# Using Debian-based image because mattn/go-sqlite3 (CGO) does not
|
|
# compile on Alpine musl (off64_t is a glibc type).
|
|
FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS lint
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends make && rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /src
|
|
|
|
# Copy go mod files first for better layer caching
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
# Copy source code. In CI the context also carries .ci-fingerprint, which
|
|
# holds the hash of the commit being checked (see
|
|
# .gitea/workflows/check.yml). That invalidates this layer, so the checks
|
|
# below cannot report success by replaying a cached pass. Do not add it to
|
|
# .dockerignore.
|
|
COPY . .
|
|
|
|
# Run formatting check and linter. golangci-lint is invoked directly rather
|
|
# than through `make lint`: this stage is already the pinned linter image, and
|
|
# script/lint is a wrapper that builds Dockerfile.lint, so calling it here
|
|
# would need a docker daemon inside the build. Keep these steps in step with
|
|
# Dockerfile.lint, including --network=none (see its header for why).
|
|
RUN make fmt-check
|
|
RUN script/assets
|
|
RUN --network=none golangci-lint config verify --config .golangci.yml
|
|
RUN --network=none golangci-lint run --config .golangci.yml --build-tags browser ./...
|
|
|
|
# Stylesheet stages. static/css/tailwind.css is generated, by this pinned
|
|
# tailwindcss, from static/css/input.css and the files its @source lines
|
|
# name. `make css` (script/css) writes it out from the css-output stage.
|
|
# The css-check stage fails when the committed file differs from what is
|
|
# generated; `make check` runs it, and so does the build stage below.
|
|
#
|
|
# tailwindcss v4.2.1 standalone CLI, released 2026-02-23: one binary per
|
|
# architecture, each pinned by its sha256 from the release's sha256sums.txt.
|
|
# debian:bookworm-slim, 2026-10-02: the binary needs glibc.
|
|
FROM debian:bookworm-slim@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251 AS tailwind-amd64
|
|
ADD --checksum=sha256:39e8d4e24b3c83b0a6e69e100a972fbc75d5fef8dce47b3ddac3cf92dea81fe3 --chmod=755 \
|
|
https://github.com/tailwindlabs/tailwindcss/releases/download/v4.2.1/tailwindcss-linux-x64 /usr/local/bin/tailwindcss
|
|
|
|
FROM debian:bookworm-slim@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251 AS tailwind-arm64
|
|
ADD --checksum=sha256:d87e6486bb3f70b04ef1dcaacc4ee6548a5a15fbf521b31bc24d2c774f68a951 --chmod=755 \
|
|
https://github.com/tailwindlabs/tailwindcss/releases/download/v4.2.1/tailwindcss-linux-arm64 /usr/local/bin/tailwindcss
|
|
|
|
# TARGETARCH, set by docker, is the architecture being built for.
|
|
FROM tailwind-${TARGETARCH} AS css
|
|
WORKDIR /src
|
|
COPY . .
|
|
RUN tailwindcss -i static/css/input.css -o /out/tailwind.css --minify
|
|
|
|
FROM scratch AS css-output
|
|
COPY --from=css /out/tailwind.css /
|
|
|
|
# Both files are split after each "}", one rule per line, so that when they
|
|
# differ the diff shows the rules that differ.
|
|
FROM css AS css-check
|
|
RUN sed 's/}/}\n/g' static/css/tailwind.css > /tmp/committed.css \
|
|
&& sed 's/}/}\n/g' /out/tailwind.css > /tmp/generated.css \
|
|
&& diff -U0 /tmp/committed.css /tmp/generated.css || { \
|
|
echo "static/css/tailwind.css is not what make css generates; run make css" >&2; \
|
|
exit 1; \
|
|
}
|
|
|
|
# Build stage
|
|
# golang:1.26.1-bookworm (Debian-based), 2026-03-17
|
|
# Using Debian-based image because gorm.io/driver/sqlite pulls in
|
|
# mattn/go-sqlite3 (CGO), which does not compile on Alpine musl.
|
|
FROM golang:1.26.1-bookworm@sha256:4465644228bc2857a954b092167e12aa59c006a3492282a6c820bf4755fd64a4 AS builder
|
|
|
|
# Depend on the lint and stylesheet check stages passing
|
|
COPY --from=lint /src/go.sum /dev/null
|
|
COPY --from=css-check /out/tailwind.css /dev/null
|
|
|
|
# jq is a runtime dependency of script/ci-mark-superseded, which the test
|
|
# suite executes. git is what script/version derives the version with.
|
|
RUN apt-get update && apt-get install -y --no-install-recommends make curl ca-certificates jq git && rm -rf /var/lib/apt/lists/*
|
|
|
|
# A build context sent as a tar archive keeps its files' owners, and git
|
|
# refuses to read a checkout owned by another user. Trust this one
|
|
# whoever owns it.
|
|
RUN git config --system --add safe.directory /build
|
|
|
|
WORKDIR /build
|
|
|
|
# Copy go mod files first for better layer caching
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
# Copy source code, including the .ci-fingerprint cache barrier described in
|
|
# the lint stage above.
|
|
COPY . .
|
|
|
|
# Run tests and build. Both first run script/assets, which extracts Alpine.js
|
|
# from its tarball in 3p/.
|
|
RUN make test
|
|
|
|
# Version stamped into the binary: the VERSION build arg when one is
|
|
# given, otherwise what script/version derives from the .git the build
|
|
# context carries, so any `docker build .` of a clone stamps its commit.
|
|
# With neither, as from a source tarball, it is "unknown".
|
|
#
|
|
# Declared here, below the test step, so a changed version does not
|
|
# invalidate its cached layer.
|
|
ARG VERSION
|
|
|
|
# A context that carries .git must not stamp "unknown": that means git is
|
|
# missing here or could not read the checkout, and the image could not be
|
|
# traced back to its commit.
|
|
RUN if [ -d .git ] && [ "$(make version VERSION="$VERSION")" = unknown ]; then \
|
|
echo "version is unknown although the build context carries .git" >&2; \
|
|
exit 1; \
|
|
fi
|
|
|
|
RUN make build VERSION="$VERSION"
|
|
|
|
# Rebuild with static linking for Alpine runtime.
|
|
# make build already verified compilation.
|
|
# The CGO binary from `make build` is dynamically linked against glibc,
|
|
# which doesn't exist on Alpine (musl). Rebuild with static linking so
|
|
# the binary runs on Alpine without glibc.
|
|
#
|
|
# The static flags go in through GO_LDFLAGS rather than a -ldflags of
|
|
# their own: the build target composes them with the -X that stamps the
|
|
# version, so this relink cannot silently drop the stamp.
|
|
RUN CGO_ENABLED=1 make build VERSION="$VERSION" GO_LDFLAGS='-extldflags "-static"'
|
|
|
|
# Runtime stage
|
|
# alpine:3.21, 2026-03-17
|
|
FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
|
|
|
# su-exec 0.2-r3 (Alpine 3.21), 2026-09-29: the entrypoint runs the app
|
|
# as webhooker with it.
|
|
RUN apk --no-cache add ca-certificates su-exec=0.2-r3
|
|
|
|
# Create non-root user
|
|
RUN addgroup -g 1000 -S webhooker && \
|
|
adduser -u 1000 -S webhooker -G webhooker
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy binary from builder
|
|
COPY --from=builder /build/bin/webhooker /app/webhooker
|
|
|
|
# Not under /app, which belongs to webhooker: this script runs as root.
|
|
COPY deploy/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
|
|
|
# Create data directory for all SQLite databases (main app DB +
|
|
# per-webhook event DBs). DATA_DIR defaults to /var/lib/webhooker.
|
|
RUN mkdir -p /var/lib/webhooker
|
|
|
|
RUN chown -R webhooker:webhooker /app /var/lib/webhooker
|
|
|
|
# No USER: the entrypoint starts as root to make the data directory
|
|
# webhooker's, then runs the app as webhooker.
|
|
|
|
EXPOSE 8080
|
|
|
|
# The binary defaults BIND_ADDRESS to 127.0.0.1, which is right for a
|
|
# bare host: the cleartext listener serves the admin UI and the
|
|
# unauthenticated receiver, so it must not appear on every interface
|
|
# of a machine that configured nothing. A container is the other case.
|
|
# Its network namespace is already the isolation boundary, so binding
|
|
# every address inside it exposes nothing; what decides exposure is
|
|
# the publish flag, and `-p 127.0.0.1:8080:8080` is the operator's
|
|
# control there. Shipping the image on loopback would buy no security
|
|
# and would make the process unreachable through its own published
|
|
# port.
|
|
ENV BIND_ADDRESS=0.0.0.0
|
|
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/.well-known/healthcheck || exit 1
|
|
|
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
|
CMD ["/app/webhooker"]
|