# 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) RUN CGO_ENABLED=1 GOOS=linux go build -o /routewatch ./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. runuser 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" ]