This episode automates gRPC's journey to production: build pipelines for proto compilation and code generation, contract validation with automated protobuf tests, and GitOps deployment for gRPC services on Kubernetes.

Writing a .proto contract and running a local server is easy. The real challenge begins when a service has to be built, tested, and deployed automatically by a different team. Episode 17 brings gRPC into the world of CI/CD and GitOps: a pipeline that compiles .proto, runs linting and contract tests, builds images, and deploys to Kubernetes with Git as the single source of truth.
The end goal: every change in the repo ends up as a running service — no manual clicks and no configuration that only lives in someone's head.
A healthy gRPC pipeline has five stages: lint (contract quality), generate (codegen), test (unit and integration), build (image), and publish. A GitHub Actions job definition to validate the contract:
name: proto-ci
on:
pull_request:
paths: ["proto/**"]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-setup-action@v1
- run: buf lint
- run: buf breaking --against https://github.com/org/contracts.git#branch=main
- run: protoc -I proto \
--go_out=gen --go_opt=paths=source_relative \
proto/catalog/v1/product.protoNote paths: ["proto/**"]: the pipeline only runs when the contract changes — saving time on irrelevant changes. The two buf commands ensure contract quality and compatibility.
buf lint ensures the contract follows conventions — for example, enum fields prefixed with the enum name, messages in PascalCase, and proto3 fields in snake_case. It's like ESLint for protobuf contracts, and it's the first gate in the pipeline.
Contract changes that break compatibility must fail the build, not cause production incidents. The buf breaking command compares the new contract against the version on main:
buf breaking --against https://github.com/org/contracts.git#branch=mainbuf breaking --against ... checks rules like "fields are not removed" and "field numbers are not reused". If anything is violated, the pipeline fails with a message pointing at the offending change.
Beyond linting, run a test that actually calls the server. The most robust pattern: spin up a real server in the pipeline, then verify it with grpcurl:
go run ./server &
sleep 2
grpcurl -plaintext localhost:50051 \
catalog.v1.CatalogService/GetProduct \
-d '{"id":"p-001"}' || exit 1
kill %1The grpcurl ... -d '{"id":"p-001"}' command verifies that the generated contract actually runs. Any failure — from a server error to a mismatched contract — is caught immediately in CI.
The GitOps principle: all deployment state is stored as files in Git, and a controller reconciles the cluster with Git. A basic gRPC service manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: catalog
spec:
replicas: 3
selector:
matchLabels:
app: catalog
template:
metadata:
labels:
app: catalog
spec:
containers:
- name: catalog
image: registry.example.com/catalog:v1.4.2
ports:
- containerPort: 50051
env:
- name: GRPC_PORT
value: "50051"
readinessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
---
apiVersion: v1
kind: Service
metadata:
name: catalog
spec:
selector:
app: catalog
ports:
- port: 50051
targetPort: 50051This YAML file defines a Deployment with three replicas and a Service to discover them. The readinessProbe from episode 15 ensures only healthy pods receive traffic. The image is pinned to a version tag — v1.4.2 — so it can always be rolled back.
A GitOps controller like ArgoCD watches the repo, compares it with the cluster, and syncs whenever they drift:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: catalog
spec:
destination:
namespace: catalog
server: https://kubernetes.default.svc
source:
repoURL: https://github.com/org/catalog-deploy.git
path: manifests
targetRevision: main
syncPolicy:
automated:
prune: true
selfHeal: truesyncPolicy.automated with selfHeal: true makes ArgoCD return the cluster to the Git state on any drift. Rolling back becomes as simple as reverting a commit — not running secret commands in a terminal.
Key takeaways:
buf lint enforces conventions; buf breaking fails changes that break compatibility.grpcurl prove the contract actually runs.In episode 18 — the final episode — we cover the modern ecosystem and latest stable tooling: buf, grpcurl, ghz, and grpc-health-probe, the latest stable protobuf and gRPC features like reflection and xDS server-side load balancing, plus production trends: service mesh, API contract enforcement, and observability pipelines.