check / check (push) Waiting to run
TestService_Get_ReturnsByItsDeadline failed on a busy host because its request, past its deadline, still waited for its miss count to be written, a database write with no deadline; in pixad that write waits for the one database connection every request shares. The hit, miss, upstream fetch and transform counts are now each written in a goroutine of their own, which the request waits for only until its deadline; a count not written by then is written after it returns. Shutdown waits for those writes within ShutdownTimeout and reports any left unfinished. The test phase also runs go test with -parallel 4: on a busy host, as many tests at once as there are CPUs wait so long to be scheduled that a timed request can fail. Model: opus-5-5
127 lines
5.2 KiB
Docker
127 lines
5.2 KiB
Docker
# Lint phase. script/lint builds it alone. The linter is run directly:
|
|
# `make lint` and script/lint are themselves a docker build.
|
|
# golangci/golangci-lint:v2.12.2, 2026-10-04
|
|
FROM golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS lint
|
|
|
|
# The linter compiles every package, and govips needs the libvips
|
|
# headers for that. REPO_POLICIES.md has the lint phase install them
|
|
# itself; this image is Debian, so with apt-get rather than apk.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends libvips-dev \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /src
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
COPY . .
|
|
RUN golangci-lint run --config .golangci.yml ./...
|
|
|
|
# Test phase. script/test builds it alone.
|
|
# golang:1.25.4-alpine3.22, 2026-02-25; the runtime stage uses Alpine 3.22 too
|
|
FROM golang:1.25.4-alpine3.22@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS test
|
|
|
|
WORKDIR /src
|
|
|
|
# script/bootstrap --cgo installs the build dependencies (a C compiler,
|
|
# the libvips and libheif headers, and libvips' JPEG XL support, which
|
|
# the tests need) and downloads the Go modules.
|
|
COPY script/ ./script/
|
|
COPY go.mod go.sum ./
|
|
RUN script/bootstrap --cgo
|
|
|
|
COPY . .
|
|
|
|
# Without -v first; on a failure, again with -v for the details, and
|
|
# the step fails even if the second run passes. -parallel 4: by default
|
|
# a package runs as many of its tests at once as the host has CPUs, and
|
|
# on a busy host they then wait so long to be scheduled that a test
|
|
# that times a request can see it return a second late.
|
|
RUN go test -count=1 -timeout 90s -race -parallel 4 -cover ./... || \
|
|
{ echo "--- Rerunning with -v for details ---"; \
|
|
go test -count=1 -timeout 90s -race -parallel 4 -v ./...; exit 1; }
|
|
|
|
# Build stage. Nothing is wanted from the two phases above: these copies
|
|
# make BuildKit build them first, so this stage runs only when lint and
|
|
# test passed.
|
|
# golang:1.25.4-alpine3.22, 2026-02-25; the runtime stage uses Alpine 3.22 too
|
|
FROM golang:1.25.4-alpine3.22@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS builder
|
|
|
|
COPY --from=lint /src/go.sum /dev/null
|
|
COPY --from=test /src/go.sum /dev/null
|
|
|
|
WORKDIR /src
|
|
|
|
# Build dependencies and Go modules, as in the test phase
|
|
COPY script/ ./script/
|
|
COPY go.mod go.sum ./
|
|
RUN script/bootstrap --cgo
|
|
|
|
# Copy source code
|
|
COPY . .
|
|
|
|
# VERSION is declared here, not earlier: a new value reruns only the
|
|
# build, not script/bootstrap. Given none, the version is
|
|
# `git describe --tags --always` of the .git in the build context (git
|
|
# comes from script/bootstrap): the tag on a tagged commit, tag-N-gHASH
|
|
# after one, the short commit when no tag is reachable. A context that
|
|
# carries .git and still yields no version fails the build; one without
|
|
# .git, as from a source tarball, stamps an empty version. CGO stays
|
|
# enabled for govips; -trimpath keeps build paths out of the binary, and
|
|
# -s -w leave out the symbol table and debug information.
|
|
ARG VERSION
|
|
RUN version="${VERSION:-$(git describe --tags --always)}"; \
|
|
if [ -e .git ] && { [ -z "$version" ] || [ "$version" = dev ] || \
|
|
[ "$version" = unknown ]; }; then \
|
|
echo "the build context carries .git but yields no version" >&2; \
|
|
exit 1; \
|
|
fi; \
|
|
CGO_ENABLED=1 GOTOOLCHAIN=auto go build -trimpath \
|
|
-ldflags "-s -w -X main.Version=${version}" \
|
|
-o /pixad ./cmd/pixad
|
|
|
|
# Runtime stage, and the last one: a plain `docker build .` builds this
|
|
# stage and what it depends on, and nothing else. It must use the Alpine
|
|
# release the golang image above is based on, so that pixad runs against
|
|
# the libvips and musl it was built with.
|
|
# alpine:3.22, 2026-10-08
|
|
FROM alpine:3.22@sha256:5291449c3df73caf6ed85e649dec1b9e818b39a5d8c871e97afc13e9cd5e8fa8
|
|
|
|
# Install runtime dependencies only. vips-jxl is libvips' JPEG XL
|
|
# support, without which pixad does not start.
|
|
RUN apk add --no-cache \
|
|
vips \
|
|
vips-jxl \
|
|
libheif \
|
|
ca-certificates \
|
|
tzdata \
|
|
su-exec
|
|
|
|
# Copy binary from builder
|
|
COPY --from=builder /pixad /usr/local/bin/pixad
|
|
COPY deploy/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
|
|
|
# Create non-root user, config directory, and data directory. pixad
|
|
# gets uid and gid 65532, which host login and system accounts do not
|
|
# use: a bind-mounted /var/lib/pixa is given to pixad, and on the host
|
|
# it must not belong to a person's account.
|
|
RUN addgroup -g 65532 pixad && \
|
|
adduser -D -H -s /sbin/nologin -u 65532 -G pixad pixad && \
|
|
mkdir -p /var/lib/pixa /etc/pixa && \
|
|
chown pixad:pixad /var/lib/pixa
|
|
|
|
# No USER: the entrypoint must start as root to give a bind-mounted
|
|
# /var/lib/pixa to pixad; it then runs the server as pixad.
|
|
WORKDIR /var/lib/pixa
|
|
|
|
EXPOSE 8080
|
|
|
|
# Shell form so the probe follows PORT; a port set only in a mounted
|
|
# config file is not seen here.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
CMD wget --spider -q "http://localhost:${PORT:-8080}/.well-known/healthcheck.json" || exit 1
|
|
|
|
# Settings come from PORT and the PIXA_ environment variables; only
|
|
# PIXA_SIGNING_KEY is required. A config file mounted at
|
|
# /etc/pixa/config.yml is optional and is read when present.
|
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|