Capstone — A Small Multi-Tier App

Kubernetes Fundamentals

Chapter 12 · Capstone — A Small Multi-Tier App

Every prior chapter contributes one piece to this capstone: a real, deployable frontend + backend + database application, built entirely from concepts already covered.

The Application

Three tiers: a frontend (a web UI), a backend (a small API service), and a database — the frontend talks to the backend, the backend talks to the database, and only the frontend is exposed to the outside world.

Step 1 — Namespace

kubectl create namespace shop-app

Chapter 10's own material: a dedicated namespace for this app, rather than everything landing in default.

Step 2 — ConfigMap & Secret for the Backend

Chapter 7's material, applied directly: LOG_LEVEL as a ConfigMap value, the database password as a Secret.

apiVersion: v1 kind: Secret metadata: name: db-credentials namespace: shop-app stringData: password: changeme

Step 3 — Database

A Deployment (Chapter 5) with a single replica, backed by a PVC (Chapter 8) for actual data persistence, with the password mounted as a Secret volume (Chapter 7), reachable internally via a ClusterIP Service (Chapter 6):

apiVersion: v1 kind: Service metadata: name: db-service namespace: shop-app spec: selector: app: db ports: - port: 5432 targetPort: 5432 type: ClusterIP
A deliberate simplification, revisited below
A real production database would more properly use a StatefulSet, covered in Course 2's k8s2-1. This capstone deliberately stays within Course 1's own toolset — the multi-tier architecture is the point here, not the deepest-correct storage pattern.

Step 4 — Backend

A multi-replica Deployment, consuming the ConfigMap and Secret (Chapter 7), with liveness/readiness probes (Chapter 11) and resource requests/limits (Chapter 10), exposed internally via another ClusterIP Service:

spec: replicas: 3 template: spec: containers: - name: api image: shop-backend:latest envFrom: - configMapRef: {name: app-config} resources: requests: {cpu: "250m", memory: "128Mi"} limits: {cpu: "500m", memory: "256Mi"} livenessProbe: httpGet: {path: /healthz, port: 8080} readinessProbe: httpGet: {path: /ready, port: 8080}

Step 5 — Frontend & Ingress

A frontend Deployment and Service, with an Ingress (Chapter 9) routing /api to the backend and everything else to the frontend — the same host/path routing pattern from Chapter 9's own example:

spec: rules: - host: shop.example.com http: paths: - path: /api pathType: Prefix backend: {service: {name: backend-service, port: {number: 80}}} - path: / pathType: Prefix backend: {service: {name: frontend-service, port: {number: 80}}}

Putting It All Together — Where Every Piece Came From

PieceChapter
NamespaceCh.10 — organizing a cluster
ConfigMap / SecretCh.7 — externalized configuration
Deployments (all 3 tiers)Ch.5 — declarative desired state, self-healing
PVC for the databaseCh.8 — persistent storage
ClusterIP Services (db, backend)Ch.6 — stable internal addressing
Ingress (frontend + /api routing)Ch.9 — external exposure, host/path routing
Resource requests/limitsCh.10 — fair scheduling and QoS
Liveness/readiness probesCh.11 — real self-healing and traffic control

Verifying It Works

kubectl get pods -n shop-app kubectl get svc -n shop-app kubectl get ingress -n shop-app curl http://shop.example.com/ # frontend curl http://shop.example.com/api # backend, via Ingress path routing

Chapter 4's own practical workflow, applied to a genuinely complete application rather than a single pod.

What's Deliberately Out of Scope — Where Course 2 Picks Up

  • A proper StatefulSet for the database, instead of this capstone's simplified Deployment+PVC (k8s2-1).
  • RBAC restricting who can access this namespace's resources (k8s2-4).
  • Autoscaling the backend based on real load, rather than a fixed replica count (k8s2-6).
  • Real observability — Prometheus/Grafana integration (k8s2-7).
  • Troubleshooting this exact app when something actually goes wrong (k8s2-8).
  • Packaging all of this as a Helm chart instead of raw YAML (k8s2-3).
  • Deploying it via GitOps rather than manual kubectl apply (k8s2-9).
Course 1's own throughline, one more time
Nearly every piece of this capstone is, underneath, the same reconciliation loop (Chapter 2) applied to a different resource type — Deployments maintaining pod count, Services maintaining Endpoint lists, liveness probes maintaining container health. Recognizing that one repeated pattern is worth more than memorizing any single YAML snippet.

Hands-On Exercises

Exercise 1

Identify which chapter of this course each of the following pieces of the capstone app came from: (a) the database's Secret-mounted password, (b) the backend's liveness/readiness probes, (c) the Ingress routing rules.

📄 View solution
Exercise 2

Explain why this capstone uses a Deployment+PVC for the database rather than a StatefulSet, and what specific problem a StatefulSet would solve that this simplified setup doesn't fully address.

📄 View solution
Exercise 3

Across all of Course 1, name at least three instances of the same underlying reconciliation-loop pattern (Chapter 2) showing up in different guises, and explain what they have in common.

📄 View solution

Chapter 12 Quick Reference — Course 1 Complete

  • A full 3-tier app assembled entirely from Chapters 1-11's own concepts — namespace, ConfigMap/Secret, Deployments, PVC, Services, Ingress, resource limits, probes
  • The database's Deployment+PVC is a deliberate simplification — Course 2's StatefulSets chapter covers the proper pattern
  • Every piece of this capstone is, underneath, the same reconciliation loop applied to a different resource type
  • Course 1 complete — Kubernetes Fundamentals, 12 chapters, from "what is Kubernetes" to a working multi-tier deployment
  • Course 2 next: Kubernetes Intermediate/Advanced — StatefulSets, Helm, RBAC, autoscaling, observability, troubleshooting, GitOps