check / check (push) Waiting to run
A plain docker build, as upaas runs it, passed no VERSION build arg and had no .git, so every such image stamped "unknown". .dockerignore now sends .git and every tracked file (an excluded one would read as deleted and mark the version -dirty), and the VERSION build arg loses its "unknown" default, so script/version derives the version inside the build. A VERSION build arg still takes precedence. The builder stage installs git and fails when its context carries .git and the version still comes out "unknown". Model: opus-5-5
136 lines
5.5 KiB
Docker
136 lines
5.5 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. 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/*
|
|
|
|
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 refused to 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"]
|