Traffic flow
Internet
│
▼
Linode NodeBalancer (LoadBalancer Service "woodard-tech", :80 / :443)
│ selector: app=traefik-ingress-controller
▼
Traefik v2.9 (3 replicas in the "default" namespace)
│ routed by Ingress "woodard-tech-ingress" (host: woodard.tech)
▼
Service "woodard-tech-backend" (ClusterIP, :80)
│ selector: app=woodard-tech
▼
Deployment "woodard-tech" (3× nginx pods serving baked HTML)
Each layer has a clear job:
- NodeBalancer is Linode’s managed L4 LB; it’s created
automatically when a Service of type
LoadBalanceris applied to the cluster. - Traefik is the in-cluster ingress controller. It terminates the Ingress routing rules and forwards to backend Services. Three replicas mean any one node can be lost without dropping traffic.
- The nginx pods serve static HTML straight from the container
image. The image is rebuilt by CI on every push, so the served
content is always whatever was in
./publicat build time.
TLS
cert-manager handles certificate issuance
through Let’s Encrypt’s HTTP-01 challenge, fronted by Traefik. The
cert is stored as a Kubernetes Secret (woodard-tech-tls) referenced
by the Ingress.
cert-manager and its ClusterIssuer are one-time cluster setup, not
part of the deploy pipeline — they live in the cluster across
deploys.
Container image
The image is intentionally minimal:
FROM nginx:latest
RUN rm -rf /usr/share/nginx/html/*
COPY ./public /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
That’s the whole thing. No init containers, no sidecars, no volumes — just nginx serving files baked into the image. Stateless pods are trivial to scale and replace.
Why no PVC?
An earlier iteration mounted a PersistentVolumeClaim at
/usr/share/nginx. That turned out to hide the image’s
/usr/share/nginx/html directory and made new deploys invisible to
the live site. The current setup has no volumes — every deploy is a
new image, every pod serves directly from its layers.