This episode weaves every pattern into a production-ready architecture: a modular router as an http.Handler, dependency injection, env config, and centralized logging. You will also build a container with multi-stage Docker, a CI/CD pipeline with GitHub Actions, deployment to Kubernetes, and zero-downtime strategies.

Writing routes is easy; running an application for years is hard. Episode 21 combines every pattern from episodes 8 through 20 into a single production-ready architecture: how a chi application is packaged, tested, deployed, and operated without downtime. After this episode, you'll have a complete blueprint — from a clean main.go to Kubernetes manifests and an automated pipeline.
func NewServer(cfg Config) http.Handler {
pool := newPool(cfg.DBURL)
userRepo := repository.NewUserRepo(pool)
userSvc := service.NewUserService(userRepo)
userH := handler.NewUserHandler(userSvc)
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(10 * time.Second))
r.Mount("/users", userH.Routes())
r.Get("/metrics", prometheusHandler)
return r
}NewServer(cfg Config) http.Handler builds dependencies from the config, then wires up middleware and routes. The whole construction happens once in main; the result can be tested and mounted again.
type Config struct {
Port string
DBURL string
JWTSecret string
RedisAddr string
}
func loadConfig() Config {
return Config{
Port: viper.GetString("PORT"),
DBURL: viper.GetString("DATABASE_URL"),
JWTSecret: viper.GetString("JWT_SECRET"),
RedisAddr: viper.GetString("REDIS_ADDR"),
}
}loadConfig() reads the environment once and shares it across all layers through Config. No handler calls os.Getenv directly.
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)slog.NewJSONHandler(os.Stdout, ...) writes every log as JSON to stdout — a format log aggregators can process directly. No logs go to local files that could disappear when the container restarts.
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /server ./cmd/server
FROM alpine:3.20
RUN adduser -D appuser
COPY --from=builder /server /usr/local/bin/server
USER appuser
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/server"]CGO_ENABLED=0 go build produces a static binary with no C dependencies. adduser -D appuser followed by USER appuser runs the server without root privileges — a standard security practice. Build the image with docker build -t chi-service . and run it with docker run and injected environment variables.
name: ci
on:
push:
branches: [main, staging]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.24"
- run: go build ./...
- run: go test ./...
- run: go vet ./...go build ./... followed by go test ./... keeps the code always green before moving on. go vet catches suspicious patterns early.
apiVersion: apps/v1
kind: Deployment
metadata:
name: chi-service
spec:
replicas: 3
selector:
matchLabels:
app: chi-service
template:
metadata:
labels:
app: chi-service
spec:
containers:
- name: chi-service
image: registry.example.com/chi-service:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080replicas: 3 runs three instances; readinessProbe uses /healthz from episode 18. A deployment with replicas and probes is the foundation of zero downtime.
A new deployment rolls out gradually: old pods keep serving until the new pods are ready (readiness green), then traffic is shifted and old pods are terminated after finishing their requests.
kubectl set image deployment/chi-service \
chi-service=registry.example.com/chi-service:1.0.1kubectl set image deployment/chi-service ... triggers the rolling update. The combination of srv.Shutdown from episode 10 and a readiness probe ensures no requests get cut off.
Key takeaways:
func NewServer(cfg Config) http.Handler.In the next episode, episode 22 — the series finale: alternative ecosystems and final reflections — a comparison of chi with Gin, Echo, Fiber, and net/http, when to choose each, a recap of the journey from episode 0 to 21, a production-grade service checklist, and the future of chi in the Go ecosystem.