All checks were successful
check / check (push) Successful in 3m6s
The binary reported "dev" in every deployment: main.version carried a placeholder and nothing ever set it. Neither `make build` nor the Dockerfile passed -X, so a tagged release produced an artifact that could not say which commit it was, and the upgrade procedure's "confirm the new build is live" step had nothing to confirm against. script/version is now the single source of the value: $VERSION when set, else `git describe --tags --always --dirty`, else "unknown". A clean checkout at a tag reports exactly that tag; a tree with no git metadata reports "unknown" rather than failing or naming a tag it may not be at. Nothing time- or host-dependent is stamped, so two builds of one commit stay byte-identical. The Makefile's build target composes the flags -- `-X main.version` plus whatever GO_LDFLAGS adds -- and every compile goes through it, including the Dockerfile's static relink, which now contributes its -extldflags through GO_LDFLAGS instead of replacing -ldflags wholesale. Since .dockerignore excludes .git/, the image cannot derive the version: it takes a VERSION build arg, defaulted to "unknown", that script/docker fills in from the host checkout. The UI footer needed the other half of the fix. It renders .Version, which nothing ever put in the template data, so it printed its literal "dev" fallback no matter what the binary was built as; renderTemplate now supplies the value on both the map and the wrapper path. An empty VERSION means unset in the Makefile too, not only in script/version: `make build VERSION=` and a `--build-arg VERSION=` reaching the Dockerfile's `make build VERSION="$VERSION"` both define the variable as the empty string, which the script's guard never sees and which would stamp no version at all -- putting the footer back on its "dev" fallback, the defect this change exists to remove. The guard needs `override`: a plain assignment loses to the command-line definition it exists to correct.
116 lines
4.4 KiB
Docker
116 lines
4.4 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
|
|
|
|
# Version stamped into the binary. .dockerignore excludes .git/, so
|
|
# nothing in this stage can derive it: script/docker resolves it on the
|
|
# host and passes it in. The default is what a bare `docker build .`
|
|
# with no --build-arg gets, and it names no tag the tree may not be at.
|
|
#
|
|
# Declared here, below the test and asset steps, so a changed version
|
|
# does not invalidate their cached layers.
|
|
ARG VERSION=unknown
|
|
|
|
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
|
|
|
|
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"]
|