Files
routewatch/Dockerfile
T
clawbot 6f0134bf5d
check / check (push) Successful in 3m1s
Stamp the git tag or short commit in a plain docker build (closes #46)
A plain `docker build .` stamped `unknown` into the page footer:
.dockerignore left out .git, and the Dockerfile built without the -X
flags. The context now carries .git without .git/config, which can hold
a credential. The build stage takes the VERSION build argument when one
is given, otherwise `git describe --tags --always`, fails the build if
.git is present and no version comes out, and stamps it together with
the full commit the footer links to. `make build` now uses
`git describe` as well, and script/docker is the current shared copy.

Model: opus-5-5
2026-10-02 04:14:50 +00:00

119 lines
4.3 KiB
Docker

# Lint stage — fast feedback on formatting and lint issues.
# The golangci-lint image bundles Go, gcc and make, so it can run go vet on
# the CGO sqlite package and golangci-lint without extra installs.
# golangci/golangci-lint:v2.7.2 (Go 1.25.5), 2026-09-21
FROM golangci/golangci-lint@sha256:5d6d5c70a61f1356adfd9dd6316ce286799fefc9d743421356ff1b00842368ba AS lint
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN make fmt-check
RUN make lint
# Build stage
# golang:1.24-bookworm, 2026-09-21
FROM golang@sha256:1a6d4452c65dea36aac2e2d606b01b4a029ec90cc1ae53890540ce6173ea77ac AS builder
# Install build dependencies (zstd for archive, gcc for CGO/sqlite3)
RUN apt-get update && apt-get install -y --no-install-recommends \
zstd \
gcc \
libc6-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
# Force BuildKit to run the lint stage before compiling or testing.
COPY --from=lint /src/go.sum /dev/null
# Copy everything
COPY . .
# Vendor dependencies (must be after copying source)
RUN go mod download && go mod vendor
# Run the test suite in the build stage: -race needs cgo and the C compiler
# installed above. The suite is offline (the live-feed test is opt-in).
RUN make test
# Build the binary with CGO enabled (required for sqlite3). The version the
# page footer shows is the VERSION build argument when one is given, otherwise
# `git describe --tags --always` of the .git in the build context (git comes
# with this image): 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. The footer links to the full commit.
ARG VERSION
RUN version="${VERSION:-$(git describe --tags --always || echo unknown)}"; \
if [ -e .git ] && { [ -z "$version" ] || [ "$version" = dev ] || \
[ "$version" = unknown ]; }; then \
echo "no version could be derived although the build context carries .git" >&2; \
exit 1; \
fi; \
CGO_ENABLED=1 GOOS=linux go build -o /routewatch -ldflags "\
-X git.eeqj.de/sneak/routewatch/internal/version.GitRevision=$(git rev-parse --verify HEAD || echo unknown) \
-X git.eeqj.de/sneak/routewatch/internal/version.GitRevisionShort=$version" \
./cmd/routewatch
# Create source archive with vendored dependencies
RUN tar --zstd -cf /routewatch-source.tar.zst \
--exclude='.git' \
--exclude='*.tar.zst' \
.
# Runtime stage
# debian:bookworm-slim, 2026-09-21
FROM debian@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251
# Install runtime dependencies
# - ca-certificates: for HTTPS connections
# - curl: for health checks
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -r -u 1000 -m routewatch
RUN mkdir -p /var/lib/berlin.sneak.app.routewatch && chown routewatch:routewatch /var/lib/berlin.sneak.app.routewatch
RUN mkdir /app
WORKDIR /app
# Copy binary and source archive from builder
COPY --from=builder /routewatch /app/routewatch
COPY --from=builder /routewatch-source.tar.zst /app/source/routewatch-source.tar.zst
# Set ownership
RUN chown -R routewatch:routewatch /app
ENV XDG_DATA_HOME=/var/lib
# Cap the Go heap at 1.5 GiB so the runtime collects harder before the
# container's memory limit is reached. setpriv in the entrypoint preserves this
# the way it does XDG_DATA_HOME above.
ENV GOMEMLIMIT=1536MiB
# Cap glibc's malloc arenas. The SQLite C library allocates and frees millions
# of small page-cache chunks from many threads; glibc otherwise creates up to
# eight arenas per core (hundreds on a large host) and keeps each arena's freed
# chunks resident, so process RSS climbs far above SQLite's live heap and never
# comes back down. Two arenas keep that retained memory bounded; database writes
# are already serialized, so the lost allocator concurrency costs nothing here.
ENV MALLOC_ARENA_MAX=2
# Expose HTTP port
EXPOSE 8080
COPY ./entrypoint.sh /entrypoint.sh
# Health check using the health endpoint, on the port PORT names
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -sf "http://localhost:${PORT:-8080}/.well-known/healthcheck.json" || exit 1
ENTRYPOINT ["/bin/bash", "/entrypoint.sh" ]