Skip to content

Deployment

Kubernetes manifests live in infra/. The image ships no config file — in-cluster configuration comes from GCTD_* environment variables and built-in defaults.

Manifest Purpose
service.yaml gct-control ClusterIP, port 80 → container 8080
deployment-control.yaml the serve control plane (1 replica)
deployment-worker.yaml the worker replicas (2 by default), sharing the cache PVC
ingress.yaml traefik ingress routing a host to gct-control:80

Before kubectl apply -f infra/

The cluster must provide three things:

  1. A Postgres DSN as a secret. The app reads one DSN, not separate host/user/password. Both deployments reference postgres-secret with key dsn.
kubectl -n gocrap-tracker create secret generic postgres-secret \
  --from-literal=dsn='postgres://gct_app:passw0rd@postgres.infra.svc:5432/crap_tracker?sslmode=disable'
  1. A shared repo-cache PVC. Workers share one cache directory and mount it ReadWriteMany at /var/lib/go-crap-tracker, so clones and coverage profiles survive pod restarts and are shared across workers.
kubectl -n gocrap-tracker create pvc gct-repo-cache \
  --access-mode=ReadWriteMany --storage-class=<rwxc> --size=20Gi
  1. NATS and Postgres reachable at nats.infra.svc:4222 and postgres.infra.svc:5432 — or change GCTD_NATS_URL / the DSN to match your cluster.

Environment variables

Var Control Worker Notes
GCTD_NATS_URL JetStream URL
GCTD_DSN from postgres-secret/dsn
GCTD_SERVER_PORT defaults to 8080
GCTD_WORKER_ENGINE local
GCTD_MAX_STALE_MIN reconciler stale threshold (serve only)
GCTD_CACHE_DIRECTORY must be the PVC mount path

Image

The Dockerfile is a multi-stage build. The final stage is a golang:1.24-alpine base on purpose: workers run git (clone/fetch) and go test -coverprofile as subprocesses, so both the Go toolchain and git must be present at runtime. The go-crap binary is installed into the image during the build, pinned to GOCRAP_VERSION.

Bump the go-crap version by editing GOCRAP_VERSION in the Makefile — it is passed to the image build as --build-arg and consumed by the Dockerfile's install.sh -v:

FROM golang:1.24-alpine AS builder
ARG GOCRAP_VERSION=v0.5.1
RUN apk add --no-cache git ca-certificates curl
RUN curl -fsSL https://padiazg.github.io/go-crap/install.sh | sh -s -- -v "$GOCRAP_VERSION"
# ... build ...

FROM golang:1.24-alpine
COPY --from=builder /out/go-crap-tracker /usr/local/bin/
COPY --from=builder /go/bin/go-crap /usr/local/bin/
ENTRYPOINT ["go-crap-tracker"]
CMD ["serve"]

Build and push the multi-arch image (arm64 + amd64, attestations off — the cluster's containerd rejects attestation manifests), then point the deployments at your registry tag:

make docker-build        # uses GOCRAP_VERSION and REGISTRY from the Makefile

or manually:

docker buildx build --provenance=false --sbom=false \
  --platform linux/arm64,linux/amd64 \
  --build-arg GOCRAP_VERSION=v0.5.1 \
  -t registry.local.patodiaz.dev/go-crap-tracker:latest --push .

Scheduling memory bursts

A scan's go test can burst 1–2 GB of RAM. The shipped worker manifest therefore pins workers to the worker nodes (node affinity on kubernetes.io/hostname) and carries a hard memory limit (3000Mi), so a burst never competes with the control-plane node or the control app. On memory-constrained clusters (SBCs, small VPSs) this is what keeps a large-repo scan from taking the cluster down — and per-repo go_flags tunes the burst itself. See Constrained Environments.

Scaling

  • Workers — scale deployment-worker.yaml replicas up or down. They share the crap-workers consumer and the cache PVC, so each additional replica simply takes more of the job queue and reuses the same clones/coverage.
  • Control plane — keep serve at 1 replica. It owns the reconciler, the SSE relay, and the worker registry; running more than one would duplicate those singletons.

Health & probes

serve exposes GET /livez (liveness), GET /readyz (readiness — Postgres reachable + NATS connected), and GET /health (liveness alias). The control-plane deployment probes /readyz (after 5s) and /livez (after 20s); the Docker image HEALTHCHECK uses /health.

Known Issues for operational gotchas.