All checks were successful
check / check (push) Successful in 2m45s
golangci-lint no longer runs on the host. script/lint builds Dockerfile.lint, which copies the repo into the digest-pinned linter image, so the container holds only this repo and the cross-worktree cache contamination of #106 becomes structurally impossible rather than filtered after the fact. Five workers hit that contamination in one evening, in both directions. Three properties are load-bearing. --no-cache-filter=lint forces the lint stage to re-execute while deps keeps its cache, because a cached build lints nothing in 0.27s and exits 0. script/lint does not trust that flag, since Docker silently ignores an unmatched stage name: it asserts golangci-lint's own summary line appears, so no summary means no lint whatever the exit code says. And both lint steps run --network=none, which enforces rather than assumes that config verify does not fetch its schema — verify is kept, because golangci-lint run silently ignores unrecognized config keys and it is the only thing that catches a typo that disables a setting. Independently reviewed four times. Three passed the behaviour; the remaining rounds were a README merge against #151, whose premise was that the file contains no false statements. Six statements this change falsified were found and corrected across those rounds — the last reviewer re-derived every countable claim against the tree rather than reading for plausibility, and found no seventh. Supersedes #106.
102 lines
3.8 KiB
Docker
102 lines
3.8 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, whose
|
|
# value changes with every commit that touches the build context (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 --network=none golangci-lint config verify --config .golangci.yml
|
|
RUN --network=none golangci-lint run --config .golangci.yml ./...
|
|
|
|
# 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 lint stage passing
|
|
COPY --from=lint /src/go.sum /dev/null
|
|
|
|
# jq is a runtime dependency of script/ci-mark-superseded, which the test
|
|
# suite executes.
|
|
RUN apt-get update && apt-get install -y --no-install-recommends make curl ca-certificates jq && rm -rf /var/lib/apt/lists/*
|
|
|
|
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 . .
|
|
|
|
# Fetch the third-party browser assets the UI serves. They are not committed
|
|
# (REPO_POLICIES.md forbids minified bundles in version control) and
|
|
# .dockerignore keeps any host copy out of the build context, so this step is
|
|
# the only way they enter the image. Each download is checked against a
|
|
# hardcoded sha256 and the build fails on mismatch; make test re-checks the
|
|
# hashes against the bytes go:embed actually put in the binary.
|
|
RUN script/fetch-assets
|
|
|
|
# Run tests and build
|
|
RUN make test
|
|
RUN make build
|
|
|
|
# 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.
|
|
RUN CGO_ENABLED=1 go build -ldflags '-extldflags "-static"' -o bin/webhooker ./cmd/webhooker
|
|
|
|
# Runtime stage
|
|
# alpine:3.21, 2026-03-17
|
|
FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
|
|
|
RUN apk --no-cache add ca-certificates
|
|
|
|
# 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
|
|
|
|
# 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
|
|
|
|
USER webhooker
|
|
|
|
EXPOSE 8080
|
|
|
|
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
|
|
|
|
CMD ["/app/webhooker"]
|