Guide G9 · DevOps path
The Twelve-Factor App and Cloud-Native Service Design
What makes an application easy to containerise, scale, and operate: the twelve factors mapped to Spring Boot and Kubernetes, graceful shutdown, health endpoints, trunk-based development, feature flags, and semantic versioning.
Start here
The mental model
The best platform in the world can't save an application that stores sessions in memory, writes logs to a local file, hard-codes its database URL, and takes three minutes to start. The TWELVE-FACTOR APP (from Heroku, 2011) is a checklist for writing services that platforms like Kubernetes can run well: stateless, configured from the environment, disposable, and observable.
For a developer moving into DevOps, this is where the two worlds meet: small changes in how the app is written (config, logging, shutdown) remove whole classes of operational problems.
Go deeper
How it works inside
01The twelve factors, the practical way
1 CODEBASE: one repo per service, many deploys (dev, prod) of the same build. 2 DEPENDENCIES: declared explicitly (pom.xml, lockfiles), never assumed on the host. 3 CONFIG: anything that varies between environments comes from the ENVIRONMENT (env vars, mounted files, ConfigMaps), never from code; the same image runs everywhere (Docker course). 4 BACKING SERVICES: databases, caches, and queues are attached resources addressed by URL, swappable by configuration. 5 BUILD, RELEASE, RUN: strictly separate stages; a release is an immutable image plus config, which is exactly the CI → GitOps flow (GitOps course). 6 PROCESSES: stateless; anything persistent goes to a backing service (sessions in Redis, files in S3).
7 PORT BINDING: the app serves HTTP on a port itself (Spring Boot's embedded server), no external app server needed. 8 CONCURRENCY: scale out by running more processes (replicas, HPA), not bigger ones. 9 DISPOSABILITY: fast startup and GRACEFUL SHUTDOWN, so pods can be killed and rescheduled any time. 10 DEV/PROD PARITY: same backing services and tools everywhere (Docker Compose and Testcontainers instead of H2-in-dev/Postgres-in-prod). 11 LOGS: write to stdout as an event stream; the platform collects them (Stateful Systems course, log pipeline). 12 ADMIN PROCESSES: one-off tasks (migrations) run as separate processes from the same release (Kubernetes Jobs, Argo CD hooks).
02Beyond the twelve: what modern platforms expect
HEALTH ENDPOINTS: separate readiness (can I take traffic?) and liveness (am I stuck?); Spring Boot Actuator's /actuator/health/readiness and /liveness groups do this. TELEMETRY: metrics, traces, and structured logs with trace IDs (Observability course). RESILIENCE: timeouts on every outbound call, retries with backoff and budgets, circuit breakers (Resilience4j), and graceful degradation. API CONTRACTS: versioned APIs and backward-compatible changes (expand/contract, Stateful Systems course, Unit 1.3). SECURITY: non-root images, secrets from a store (Guide G8), least-privilege identities.
03Graceful shutdown on Kubernetes
When a pod is deleted, Kubernetes removes it from Service endpoints AND sends SIGTERM at roughly the same time. The endpoint removal takes a moment to propagate to every node and load balancer, so a pod that exits instantly drops requests still being routed to it. The fix: a short preStop sleep (5–10 s) so routing catches up, then the app stops accepting new work, finishes in-flight requests (server.shutdown=graceful in Spring Boot), closes pools, and exits before terminationGracePeriodSeconds (30 s default) ends in SIGKILL.
04Trunk-based development, feature flags, and versioning
TRUNK-BASED DEVELOPMENT: everyone integrates small changes into main at least daily, behind automated tests, instead of long-lived feature branches that merge painfully weeks later. It's what makes continuous integration real, and it's strongly associated with high DORA performance (Git course, branching strategies). Unfinished work ships DARK behind FEATURE FLAGS, which decouple deploying from releasing (GitOps course, Mission 2.2). Remove flags once a feature is fully launched; stale flags are technical debt.
SEMANTIC VERSIONING (MAJOR.MINOR.PATCH): MAJOR for breaking changes, MINOR for backward-compatible features, PATCH for fixes. Use it for libraries, charts, and public APIs; for deployable services, an immutable build identifier (the Git SHA) matters more than a hand-picked version (CI/CD course, versioning and promotion). Conventional Commits plus release tooling can generate versions and changelogs automatically.
Do it
Hands-on lab
- 1
Make a Spring Boot service twelve-factor friendly
Configuration from the environment, JSON logs to stdout, graceful shutdown, and probe groups: a few lines of config remove a lot of operational pain.
src/main/resources/application.yamlwhole fileyaml server: port: ${PORT:8080} shutdown: graceful # finish in-flight requests on SIGTERM spring: lifecycle: timeout-per-shutdown-phase: 20s # < terminationGracePeriodSeconds datasource: url: ${DB_URL} # factor 3: config from the environment username: ${DB_USER} password: ${DB_PASSWORD} management: endpoint: health: probes: enabled: true # /actuator/health/liveness and /readiness endpoints: web: exposure: include: health,prometheus logging: structured: format: console: ecs # JSON logs to stdout (Spring Boot 3.4+) - 2
Wire probes and shutdown into the Deployment
deployment.yamladd to fileyaml terminationGracePeriodSeconds: 30 containers: - name: api lifecycle: preStop: sleep: { seconds: 10 } # Kubernetes 1.30+; older: exec: ["sh","-c","sleep 10"] readinessProbe: httpGet: { path: /actuator/health/readiness, port: 8080 } periodSeconds: 5 livenessProbe: httpGet: { path: /actuator/health/liveness, port: 8080 } periodSeconds: 10 failureThreshold: 3 startupProbe: httpGet: { path: /actuator/health/liveness, port: 8080 } failureThreshold: 30 periodSeconds: 2 - 3
Prove zero dropped requests during a rollout
Run load while restarting the Deployment. With graceful shutdown and preStop, the error count stays at zero; remove them and you'll see a burst of 502/503s.
terminal$ k6 run -e BASE_URL=https://api.dev.shoplite.dev smoke.js &kubectl -n shoplite-dev rollout restart deploy/api && kubectl -n shoplite-dev rollout status deploy/apiwait── expected output ──deployment "api" successfully rolled outhttp_req_failed................: 0.00% 0 out of 18342
Operate it
Knobs that matter
| Setting | Default | What it does | When to change it |
|---|---|---|---|
| server.shutdown | immediate | Whether Spring Boot drains in-flight requests on SIGTERM. | graceful for every web service. |
| terminationGracePeriodSeconds | 30 | Time between SIGTERM and SIGKILL. | Longer than preStop + the longest normal request/drain time. |
| preStop sleep | none | Delay before SIGTERM while endpoints update. | 5–10 s for services behind Services/Ingress/ALB. |
3am practice
Failure drills
Each drill is a real failure mode. Read the scenario and the output, decide what's wrong, then reveal the diagnosis.
Drill #1
Every deploy causes a burst of 502s
During each rolling update, the ALB returns 502s for about 5 seconds, although readiness probes are configured.
Drill #2
Works in dev, breaks in prod
A query using a PostgreSQL-specific JSON operator passes every test (run against H2 in-memory) and fails on the first prod deploy.
The bigger picture
Connects to
System Design · Stateless Services
The principle that makes horizontal scaling, rolling deploys, and instance-juggling boring.
System Design · Configuration Management
Config that changes without redeploys: externalized, versioned, and environment-aware.
Docker · Testcontainers
Dev/prod parity for tests.
Kubernetes · Rolling updates & rollbacks
Why graceful shutdown decides whether rollouts drop requests.
Git course
Branching strategies and trunk-based development.
CI/CD · Approvals & feature flags
Separating deploy from release.
Prove it
Interview questions
What is the Twelve-Factor App, and which factors matter most for Kubernetes?
How do you avoid dropped requests during rolling updates?
Trunk-based development vs GitFlow?