Week 26: Capstone, Part 2 — Deploy, Harden & Ship

Week 25 ended with tagged infrastructure, a managed database, and a pipeline that tests, scans and pushes an image. This final week deploys that image to Kubernetes, layers on the observability, security and reliability practices from Weeks 13, 14 and 21, and closes with the project write-up and checklist that turn a working system into a capstone you can defend to an interviewer end to end — the last stop in the 26-week course.

Module 22 of 22 Week 26 of 26 ~8–10 Hours Capstone Project

By the end of this capstone, you'll be able to

  • Deploy a containerized app to Kubernetes with a zero-downtime rollout strategy
  • Layer observability, a real SLO, and default-deny network policy onto a running system
  • Explain and defend every architectural decision across both capstone weeks

1. Deploying to Kubernetes

The manifests below use the same shapes from Weeks 11–12, wired to the RDS instance Week 25 provisioned instead of a database Pod, applied once by hand to bootstrap the cluster before the pipeline — or ArgoCD, if that's the path you chose in Week 25 — takes over updating the image going forward.

k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: capstone-app
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate: { maxUnavailable: 0, maxSurge: 1 }
  selector:
    matchLabels: { app: capstone-app }
  template:
    metadata:
      labels: { app: capstone-app }
    spec:
      containers:
        - name: app
          image: PLACEHOLDER   # set by the deploy step
          ports: [{ containerPort: 8000 }]
          env:
            - name: DATABASE_URL
              valueFrom: { secretKeyRef: { name: capstone-db, key: url } }
          readinessProbe:
            httpGet: { path: /healthz, port: 8000 }
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet: { path: /healthz, port: 8000 }
            initialDelaySeconds: 15
            periodSeconds: 20
          resources:
            requests: { cpu: "100m", memory: "128Mi" }
            limits:   { cpu: "500m", memory: "256Mi" }
---
apiVersion: v1
kind: Service
metadata:
  name: capstone-app
spec:
  selector: { app: capstone-app }
  ports: [{ port: 80, targetPort: 8000 }]

replicas: 2 with maxUnavailable: 0 is what makes the rollout genuinely zero-downtime: Kubernetes must bring up a new, ready Pod (maxSurge: 1) before removing an old one, so there's never a moment with fewer than two healthy Pods — a guarantee that only holds because a readinessProbe is defined; without one, a Pod counts as ready the moment its process starts, well before it can actually serve a request against the database correctly.

.github/workflows/deploy.yml — the deploy job, continuing Week 25's pipeline
  deploy:
    needs: build-scan-push
    runs-on: ubuntu-latest
    environment: production   # requires reviewer approval -- Week 7
    steps:
      - uses: actions/checkout@v4
      - run: |
          mkdir -p ~/.kube
          echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > ~/.kube/config
      - run: |
          kubectl set image deployment/capstone-app app=${{ needs.build-scan-push.outputs.image }}
          kubectl rollout status deployment/capstone-app --timeout=120s

kubectl rollout status is what makes this trustworthy — the job doesn't report success the instant it issues the update, it waits and confirms the new Pods actually became ready. If you chose ArgoCD instead in Week 25, this step is replaced entirely by an image-tag bump to a Git repo ArgoCD watches, per Week 17 — either is a legitimate capstone choice as long as you can explain why you picked it.

A silent, crash-looping deploy is worse than a loud, failing one

A pipeline that ships a broken Pod and reports green because it never actually waited to confirm health has hidden the exact failure it was supposed to catch. Everything in this section exists to make that impossible: the readiness probe, the rollout-status wait, and the deploy environment's required approval all exist to make a bad deploy fail loudly, immediately, and visibly instead of quietly.

2. Observability, SLOs & Security

A pipeline that deploys correctly but leaves you blind, unmeasured, and unrestricted once the app is running only solves part of the problem. Apply Weeks 13, 14 and 21 in full:

  • Metrics & dashboards — deploy the kube-prometheus-stack Helm chart to scrape both cluster-level metrics and the app's own /metrics endpoint, and build one Grafana dashboard covering request rate, error rate and Pod restarts.
  • An SLO — define one real availability or latency SLO for the app (Week 21) backed by a Prometheus query against those metrics, and calculate its error budget over a 7-day window.
  • Logging — confirm the app logs structured JSON to stdout, which kubectl logs can consume without extra parsing.
  • Secrets — every credential the pipeline or cluster uses lives in GitHub Encrypted Secrets or a Kubernetes Secret, never committed to the repo — verify with git log -p -- '*.env' '*.pem' turning up nothing.
  • Network policy — a default-deny NetworkPolicy plus one explicit allow rule for the app's actual traffic pattern to the RDS instance and inbound HTTP.
k8s/network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny }
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-app-traffic }
spec:
  podSelector: { matchLabels: { app: capstone-app } }
  policyTypes: [Ingress, Egress]
  ingress:
    - from: []
      ports: [{ protocol: TCP, port: 8000 }]
  egress:
    - to: []                                 # RDS lives outside the cluster
      ports: [{ protocol: TCP, port: 5432 }, { protocol: UDP, port: 53 }]  # + DNS, per Week 14

That trailing UDP/53 rule is Week 14's lesson paid forward directly — a default-deny egress policy blocks DNS resolution unless explicitly allowed, and a Pod that can't resolve the RDS endpoint's hostname fails in a confusing way that has nothing to do with the actual NetworkPolicy intent.

This section is what separates a demo from a capstone

Getting a container running in Kubernetes is a demo. Getting it there through a tested, scanned, gated pipeline, with a dashboard and a real SLO a reviewer can look at, and a network policy that isn't wide open, is the difference a hiring manager is actually screening for.

3. The Capstone Project

Capstone

Deploy, harden and ship — then defend every decision in writing

Take Weeks 25–26 fully through to a live, reachable, observed deployment backed by a real database.

Requirements:

  1. Bootstrap the cluster with the Deployment, Service and network policies above, connected to the real RDS instance from Week 25, and confirm readiness/liveness probes are correctly configured against a real endpoint that checks the database connection.
  2. Complete the deploy stage of the pipeline — direct kubectl or ArgoCD GitOps, per your Week 25 decision — gated behind a production environment approval.
  3. Deploy the observability stack, define your SLO, and take a screenshot of a working Grafana dashboard showing live traffic from at least one real deploy.
  4. Apply the default-deny NetworkPolicy, confirm secrets are nowhere in git history, and push one real code change through the full pipeline end to end — from commit to a verified zero-downtime rollout.
  5. Run one deliberate failure against the running system — a Pod kill, per Week 22's chaos practice, is a strong choice — and confirm it behaves as your SLO and readiness probes intend.
  6. Write a README/architecture doc covering: a diagram of the pipeline and infrastructure, the tradeoffs you made (k3s vs. EKS, RDS vs. self-hosted, direct deploy vs. GitOps), your SLO and its error budget, and what you'd change with more time or budget.
Tear it all down when you're done

terraform destroy the moment you've captured your screenshots and write-up — an EC2 instance and an RDS instance both cost real money every hour, and leaving a capstone running indefinitely is the single most common way this project turns into an unpleasant AWS bill.

4. Final Checklist

Before calling the capstone — and the course — done, confirm each of these honestly:

Does every piece of infrastructure, including the database, exist because Terraform created it, with zero manual console clicks?

A resource created by hand in the console isn't tracked in state, can't be reproduced by terraform apply on a fresh account, and will silently drift the moment someone edits it outside Terraform. The real test is terraform plan against a clean checkout producing zero unexpected changes — including the RDS instance, not just the compute and networking.

Does the pipeline fail loudly and stop before deploying if a test fails, a critical vulnerability is found, or the rollout doesn't become healthy?

A pipeline where deploy can run after test or the scan fails isn't actually gating anything — it's cosmetic. Verify by deliberately breaking a test and separately referencing a known-CVE image, confirming both stop the pipeline before any deploy step runs.

Is there a real SLO backed by a real query, with an error budget you can state in a specific number?

"We have monitoring" is not the same claim as "we have an SLO." Confirm you can state the exact SLI query, the target percentage, the time window, and the resulting number of allowed failed/slow requests — that specificity is what Week 21 was building toward, and it's what separates a dashboard that exists from a reliability target the team actually manages against.

Can every architectural choice across both capstone weeks be defended in the write-up, not just listed?

Anyone can list "Terraform, GitHub Actions, Kubernetes, RDS" as technologies used. A capstone that can explain specifically why k3s on one node, RDS over self-hosted Postgres, and direct deploy vs. GitOps were the right calls for this project's scope and budget — and what would actually change at real production scale — demonstrates the judgment a reviewer is screening for, which a tool list alone never does.

That's the course

From a first ls in Week 1 to a tagged, tested, scanned, observed, SLO-backed deployment with a real database behind it — every module in between exists somewhere in what you just shipped. That's the portfolio piece, and that's the story to tell in an interview.