Lesson 4 · Ship it — code → prod plumbing

Build system / Skaffold

How 30+ services become one binary in one image that runs as any of them — and the two very different local workflows that get it onto a cluster.

Your win: explain the single-binary model, and tell the two local build paths (Skaffold+kind vs docker-compose) apart.

One binary, all services

The monorepo compiles every service into a single server binary. Each service is pulled in by a blank import and self-registers; the runtime picks which service to be via a cobra subcommand:

cmd/server/main.go (shape)
import (
    _ ".../cmd/server/eureka"       // blank import → self-registers
    _ ".../cmd/server/notificationmgmt"
    // ... every service
)
// root cobra command "server" → two subtrees:
//   gserver  → run the gRPC server for a service
//   gjob     → run a job (sql_migrate, crons, data migrations)
The service is chosen at runtime, not at build time There is one binary and one image. "Being eureka" vs "being notificationmgmt" is a matter of which cobra subcommand runs and which config/secrets files are mounted. Kubernetes runs the same image many times with different args — that's why you saw gjob sql_migrate (Lesson 1) and gjob notificationmgmt_consumers (Part 3): same binary, different subcommand.1

Cross-compiled for linux

The local build script cross-compiles for the container platform, not your Mac:

local/build-code-golang.bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./build/server ./cmd/server/
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./build/bdd.test ./features/
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./build/stub ./features/stub/

build/server is the hot-swapped server; build/bdd.test is the BDD runner (into gandalf); build/stub is the Learnosity mock. They only run inside a linux container.1

Two Dockerfiles — dev copies, prod compiles

dev (development.Dockerfiledeveloper)prod (release.Dockerfilerunner)
basedebian:12.11alpine:3.21.7
binarycopies the pre-built ./build/servercompiles /server inside a build stage
whyfast local loop (build once, copy in)reproducible, self-contained prod image

Both end with ENTRYPOINT ["/server"] and bundle /migrations. The dev image trusts your local cross-compiled binary; the prod image rebuilds from source in CI.2

Two local paths — and only one is Skaffold

This trips people up: there are two independent ways to run the stack locally.

Skaffold + kinddocker-compose
launchdeployments/sk.bashskaffold.local.yaml./local/run.bash (the one CLAUDE.md documents)
howcustom build cmd → docker buildkind load → Helm-deploy every servicecompose services; hot-swap build/server into a running container
usefull Kubernetes-like stackthe fast inner dev loop
Skaffold's build hides in profiles In skaffold.backend.yaml the top level is only deploy.helm.releases (one per service). The actual build lives inside profiles — the local profile uses a custom.buildCommand (not a Docker artifact block) that builds the three binaries, docker build --target developer, tags backend:locally, and kind loads it. It even fails on purpose if you try to push a local image. And ./local/run.bash doesn't touch Skaffold at all.3
Read this next

Skaffold — the docs

The build/tag/deploy loop, profiles, and custom build commands — match them to skaffold.backend.yaml.

skaffold.dev/docs
→ in-repo cmd/server/main.go, local/build-code-golang.bash, skaffold.backend.yaml, developments/*.Dockerfile

Check yourself (from memory)

Q1. How does one image run as many different services?

One binary, all services; the runtime picks one via a cobra subcommand (gserver/gjob) and the mounted config/secrets.

Q2. How do the dev and prod Docker images differ?

Dev (developer, debian) copies your cross-compiled build/server; prod (runner, alpine) compiles from source in CI.

Q3. Which local path uses Skaffold?

Skaffold+kind is deployments/sk.bash; ./local/run.bash is the separate docker-compose hot-swap path.
Recall: the single-binary model, the two images, the two local paths.
one binary + cross-compile + images + paths, then reveal
One binary, all services (cmd/server/main.go, blank imports self-register); runtime picks the service via a cobra subcommand (gserver/gjob) + mounted config — same image run many times. Cross-compile (build-code-golang.bash, GOOS=linux GOARCH=amd64) → build/{server,bdd.test,stub}. Two images: dev (development.Dockerfile:developer, debian, copies prebuilt binary) vs prod (release.Dockerfile:runner, alpine, compiles inside). Two local paths: Skaffold+kind (deployments/sk.bash → custom build → kind load → Helm; build lives in profiles) vs docker-compose (./local/run.bash, hot-swap the binary — NOT Skaffold).
🎯 Interview one-liner "How is the monorepo built and deployed?" → "Every service compiles into one binary; the runtime picks which service to be via a cobra subcommand and mounted config, so Kubernetes runs one image many times. Local dev copies a cross-compiled binary into a debian image for speed; prod compiles a slim alpine image in CI. We use Skaffold with kind for a full local cluster, and a docker-compose hot-swap loop for fast iteration."
That's Part 1 — you can ship a change: migrate the schema, pass the gates, wire the secrets, and build the image. Take the four quizzes cold, then tell me "build Part 2" for the reliability patterns — idempotency, retries, and the transactional outbox. Ask me anything first.

1. In-repo: cmd/server/main.go (blank imports, servergserver/gjob), internal/golibs/bootstrap/command.go, local/build-code-golang.bash.

2. In-repo: developments/development.Dockerfile (developer, copies prebuilt), developments/release.Dockerfile (runner, compiles inside).

3. In-repo: skaffold.backend.yaml (deploy.helm top-level; build in profiles; custom.buildCommand; push guard :317-320), skaffold.local.yaml, deployments/sk.bash, local/run.bash.