This episode puts together a production-ready architecture: modular monolith versus microservices, environment config, centralized logging, containerization with multi-stage Docker, GitHub Actions CI/CD, deployment to Kubernetes and VMs, and zero-downtime strategies.

Correct code on your machine is only the first step. A production-ready architecture determines whether the application can be deployed, scaled, and recovered reliably. This episode brings together all the series' lessons into a real deployment blueprint.
Episode 21 covers modular monoliths and microservices, environment config, centralized logging, multi-stage Docker, CI/CD with GitHub Actions, deployment to Kubernetes and VMs, and zero-downtime strategies.
Not every application needs microservices. A modular monolith — one deployable with strictly separated internal modules — is often the best choice:
Microservices only make sense when a large team works independently, or workloads need separate scaling. Start with a modular monolith; split it when the team and load truly demand it.
cmd/api/main.go
internal/
user/
order/
notification/
platform/echo.goAll configuration is already managed in episode 10: a Config struct, the environment as the source of values, and slog as the single logger. In production, complete it with:
level := slog.LevelInfo
if cfg.Environment == "development" {
level = slog.LevelDebug
}
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
})))JSON logs on stdout, collected by an aggregator (episode 11), and linked to traces (episode 18). No local log files inside the container.
Multi-stage Docker produces a small, secure image: build in one stage, then copy the resulting binary into a lean runtime stage:
FROM golang:1.24-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server ./cmd/api
FROM alpine:3.20
RUN adduser -D appuser
COPY --from=build /app/server /server
USER appuser
EXPOSE 8080
ENTRYPOINT ["/server"]The build stage uses full Go; the runtime stage is only a slim alpine with a static binary and a non-root user. CGO_ENABLED=0 produces a binary without C dependencies.
docker build -t belajar-echo:latest .
docker run --rm -p 8080:8080 belajar-echo:latestAn automated CI/CD pipeline: every push to main is built, tested, and released:
name: deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.24"
- run: go mod download
- run: go build ./...
- run: go test ./... -race
- run: go vet ./...The next steps in the pipeline: build the Docker image, push it to a registry, and trigger the deployment. Every passing commit becomes a release candidate.
Kubernetes deployment uses the rolling update strategy so there's no downtime:
apiVersion: apps/v1
kind: Deployment
metadata:
name: belajar-echo
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
spec:
containers:
- name: api
image: registry.example.com/belajar-echo:v1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080Three keys to zero-downtime: more than one replica, a readinessProbe hitting /health/ready from episode 18, and a rolling update that replaces instances gradually. This combination ensures traffic is only routed to instances that are ready.
Episode 21 puts together a production blueprint: a modular monolith as the pragmatic starting point, consistent config and centralized logging, multi-stage Docker with a static binary and a non-root user, GitHub Actions CI/CD, Kubernetes deployment with rolling updates, and readiness probes for zero downtime.
Key takeaways:
CGO_ENABLED=0 and a non-root user.In episode 22 next, we'll discuss alternative ecosystems & final reflections — an in-depth comparison of Echo with Gin, Fiber, chi, and plain net/http, when to choose each, a recap of the 22-episode journey, a production-grade REST API checklist, and Echo's future in the Go ecosystem.