ArchitectMind
  1. Home
  2. Tutorials
  3. Book 9 · Cloud, Containers, DevOps & Production Architecture
  4. Volume VII
  5. Twelve-Factor Applications
Book 9 · Volume VII — Cloud-Native Architecture & Multi-Region Systems · Topic 1.2

Twelve-Factor Applications

A methodology for portable, disposable, scalable services

11 min readStage 4 · Production & architecture10 interview questions
Chapter
Chapter 1 — Cloud-Native Architecture
Prerequisites
Topic 1.1
You will be able to
Apply each of the twelve factors to Java services on Kubernetes, detect and fix violations, extend the methodology with modern concerns, assess services for compliance, and plan legacy modernisation steps
Covers
Twelve-factor applications
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts — The Factors Applied (1–6)
  4. 4. How It Works Internally — The Factors Applied (7–12)
  5. 4A. Deep Dive — Config (Factor III) Done Right
  6. 4B. Deep Dive — Processes and State (Factor VI)
  7. 4C. Deep Dive — Disposability (Factor IX)
  8. 4D. Deep Dive — Beyond Twelve Factors
  9. 4E. Deep Dive — Modernising Legacy Java (NovaBank)
  10. 5. APIs / Features / Syntax
  11. 6. Practical Examples
  12. 7. Common Problems & Mistakes
  13. 8. Performance & Production Considerations
  14. 9. Architecture & Design Considerations
  15. 10. Interview Questions & Answers

1Concept / Definition

The Twelve-Factor App is a methodology (originally from Heroku, 2011) describing how to build software-as-a-service applications that are portable across environments, deployable continuously, and scalable horizontally without architectural change. Its twelve factors codify practices — config in the environment, stateless processes, disposability, logs as streams — that now underpin container and Kubernetes platforms.

The twelve factors
Figure 1 — The twelve factors
VocabularyMeaning
Backing serviceAny service consumed over the network (DB, cache, queue)
ReleaseBuild artifact + environment config
DisposabilityFast startup and graceful shutdown
Dev/prod parityMinimal differences between environments
Table 1 — Terms

2Why It Exists

ViolationConsequence
Config compiled into the artifactRebuild per environment, drift
Session state in memorySticky sessions, lost carts on scale-in
Writing logs to local filesLost on pod deletion; can't aggregate
Different DB in dev (H2) and prod (PostgreSQL)Bugs only in production
Slow startup (90 s)Autoscaling useless in spikes
Table 2 — Why
Twelve-factor compliance vs incident rate
Figure 2 — Twelve-factor compliance vs incident rate

3Core Concepts — The Factors Applied (1–6)

FactorRuleJava / Kubernetes practice
I CodebaseOne codebase, many deploysOne repo per service (or monorepo module); same commit to all envs
II DependenciesExplicitly declare and isolateMaven with BOMs; no reliance on system packages; minimal base images
III ConfigStore config in the environmentEnv vars/ConfigMaps/Secrets; Spring profiles only for structure; no secrets in images
IV Backing servicesTreat as attached resourcesAurora, Redis, Kafka addressed by URL/credentials from config; swap without code change
V Build, release, runStrictly separate stagesCI builds image once; GitOps release = digest + config; run = pods
VI ProcessesStateless, share-nothingNo in-memory sessions; files to S3; caches rebuildable
Table 3 — Factors I–VI

4How It Works Internally — The Factors Applied (7–12)

FactorRuleJava / Kubernetes practice
VII Port bindingSelf-contained, export via portEmbedded Tomcat/Netty on 8080; no app server deployment
VIII ConcurrencyScale out via process modelHPA adds pods; virtual threads within a process
IX DisposabilityFast start, graceful stopAOT cache/CRaC; graceful shutdown; idempotent work
X Dev/prod parityKeep environments similarTestcontainers with real PostgreSQL/Kafka; same image through envs
XI LogsTreat as event streamsJSON to stdout; platform ships logs
XII Admin processesRun as one-off processesKubernetes Jobs for migrations (Flyway), same image and config
Table 4 — Factors VII–XII

4ADeep Dive — Config (Factor III) Done Right

Config typeMechanism
Non-secret settingsConfigMap → env vars / mounted application.yaml
SecretsExternal Secrets → Kubernetes Secret → env/volume
Feature togglesFlag service (runtime, not deploy-time)
Environment structureKustomize overlays per env
Validation@ConfigurationProperties with @Validated — fail fast at startup
Table 5 — Config layers

4BDeep Dive — Processes and State (Factor VI)

StateTwelve-factor home
HTTP sessionStateless JWT, or Spring Session on Redis
Uploaded filesS3 via presigned URLs
CachesRedis or local caches that can be lost
Scheduled job locksShedLock on DB / Kubernetes CronJob
In-flight workQueues with at-least-once + idempotency
Table 6 — Where state goes

4CDeep Dive — Disposability (Factor IX)

AspectTarget
Startup to ready< 10 s (AOT cache), < 1 s (CRaC)
Graceful shutdownFinish in-flight within 25 s
Crash safetyWork is idempotent; messages redelivered
Kafka consumersCommit offsets after processing; cooperative rebalancing
Table 7 — Fast and safe to kill

4DDeep Dive — Beyond Twelve Factors

Beyond twelve factors
Figure 3 — Beyond twelve factors
ExtensionWhy added
API firstContracts enable parallel teams
TelemetryMetrics and traces, not only logs
Security (authN/Z, least privilege)Not covered originally
Health probesOrchestrators need readiness/liveness
Graceful degradationResilience in distributed systems
Table 8 — Modern additions (e.g. "Beyond the Twelve-Factor App")

4EDeep Dive — Modernising Legacy Java (NovaBank)

Legacy traitModern step
WAR on WebLogic with JNDI datasourcesSpring Boot executable jar, config via env
Session replication in clusterStateless JWT
Log4j to local filesJSON stdout
Nightly batch on the same serverKubernetes CronJob / AWS Batch
Property files per environment in repoConfigMaps + Secrets Manager
Table 9 — Incremental path, one factor at a time

5APIs / Features / Syntax

JAVA
// Factor III: typed, validated configuration from the environment
@ConfigurationProperties(prefix = "payments")
@Validated
public record PaymentsProperties(
        @NotBlank String partnerBaseUrl,
        @NotNull Duration timeout,
        @Min(1) @Max(5) int maxAttempts) { }
// env: PAYMENTS_PARTNERBASEURL, PAYMENTS_TIMEOUT=2s, PAYMENTS_MAXATTEMPTS=2
YAML
# Factor XII: admin process as a Job using the same image and config as the app
apiVersion: batch/v1
kind: Job
metadata:
  name: orders-migrate-2026-09-28
  annotations: { argocd.argoproj.io/hook: PreSync }
spec:
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: flyway
          image: 111122223333.dkr.ecr.ap-south-1.amazonaws.com/orders@sha256:9f2c...
          args: ["--spring.main.web-application-type=none", "--app.mode=migrate"]
          envFrom:
            - configMapRef: { name: orders-config }
            - secretRef: { name: orders-db }
JAVA
// Factor X: dev/prod parity with Testcontainers (same PostgreSQL major version as prod)
@SpringBootTest
@Testcontainers
class OrderRepositoryIT {
    @Container @ServiceConnection
    static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:17");
    @Autowired OrderRepository repo;
    @Test void savesAndLoads() { /* ... */ }
}

6Practical Examples

6.1 Config rebuilds removed

A service baked environment URLs into its jar and had three builds per release. Moving to env-based config gave one image through all environments and removed a class of "wrong endpoint in prod" incidents.

6.2 Lost carts on scale-in

In-memory carts vanished when HPA scaled pods down after a sale. Moving carts to DynamoDB made pods disposable.

6.3 H2 in tests hid a bug

A query worked on H2 but failed on PostgreSQL due to case sensitivity. Testcontainers with PostgreSQL 17 now runs in CI.

6.4 Worked example — compliance scorecard

Factorcheckoutlegacy-reports
III Config✔✘ (property files)
VI Processes✔✘ (local temp files)
XI Logs✔✘ (file appender)
Table 10 — Assess per service

6.5 Worked example — backing service swap

ChangeCode change?
Self-managed Redis → ElastiCacheNo — URL and credentials only
Table 11 — Attached resources

6.6 Worked example — build/release/run

StageArtifact
BuildImage digest sha256:9f2c…
ReleaseDigest + prod-aps1 overlay (Git commit)
RunPods
Table 12 — Traceable releases

6.7 Worked example — interview whiteboard

PromptAnswer outline
"How do the twelve factors apply to Spring Boot on Kubernetes?""One codebase, many deploys; Maven-declared dependencies in minimal images; config via env/ConfigMaps/Secrets with validated @ConfigurationProperties; backing services as attached resources by URL; build once, release as digest+config via GitOps, run as pods; stateless processes with state in Redis/DynamoDB/S3; embedded server port binding; scale out via HPA; disposability via AOT/CRaC and graceful shutdown; parity via Testcontainers and same image; JSON logs to stdout; migrations as Jobs. Extend with API-first, telemetry, security and health probes."
Table 13 — Structured answer

6.8 Worked example — PayGrid config for cells

ConfigSource
Cell ID, merchant rangesEnvironment per cell deployment
Table 14 — Same image, many cells

6.9 Worked example — anti-patterns

Anti-patternEffect
Secrets in application.yaml in GitLeaks
Profiles per environment containing URLsRebuild/drift
Local file stateData loss
Long startupPoor elasticity
Table 15 — Common mistakes

6.10 Worked example — concurrency model

LevelMechanism
Within processVirtual threads
Across processesPods via HPA
Table 16 — Both levels

6.11 Worked example — logs as streams

App doesPlatform does
Write JSON to stdoutCollect, route, store
Table 17 — Separation of concerns

6.12 Worked example — admin tasks

TaskMechanism
Data backfillJob with same image and config
REPL debuggingEphemeral debug container (audited)
Table 18 — No SSH

6.13 Worked example — dependency isolation

PracticeDetail
Distroless/chiselled JRE base imagesNo system dependencies
Table 19 — Reproducible

6.14 Worked example — dev parity for AWS services

ServiceLocal substitute
S3, SQS, DynamoDBLocalStack in Testcontainers
Table 20 — Closer to prod

6.15 Worked example — limitations

LimitationModern view
Env vars for large/complex configMounted files acceptable
Stateful workloadsUse managed stateful services or operators
Table 21 — Interpret, don't dogmatise

6.16 Worked example — interview follow-ups

Follow-upAnswer
"Are Spring profiles a violation?"Only if they encode environment values; use them for structure
"Where do secrets go?"Secret manager injected at runtime
Table 22 — Common probes

6.17 Worked example — checklist

Item✔
Config external + validated✔
Stateless✔
Build once✔
Fast, graceful lifecycle✔
Parity tests✔
Logs to stdout✔
Table 23 — Twelve-factor baseline

6.18 Worked example — NovaBank modernisation roadmap

QuarterFactors addressed
Q1III, XI
Q2VI, VII
Q3IX, X
Table 24 — Incremental

6.19 Worked example — config change without deploy

SettingMechanism
Rate limitsFlag service
Pool sizesConfigMap + rollout
Table 25 — Right mechanism per change type

6.20 Worked example — port binding and mesh

PointDetail
App on 8080; mesh sidecar interceptsTransparent to app
Table 26 — Works with service mesh

6.21 Worked example — interview scenario

QuestionAnswer
"Service takes 2 minutes to start"Profile startup, lazy init, AOT cache/CRaC, reduce classpath scanning
Table 27 — Disposability fix

6.22 Worked example — twelve-factor for Lambda

FactorLambda mapping
ConfigEnv vars/Parameters
ProcessesStateless invocations
Table 28 — Methodology transfers

6.23 Worked example — CI enforcement

CheckTool
No secrets in repogitleaks
No file appendersLogback config lint
Table 29 — Automate compliance

6.24 Worked example — config drift detection

MechanismDetail
Argo CD diff on ConfigMapsDrift alerts
Table 30 — Config in Git stays true

7Common Problems & Mistakes

MistakeFix
Baked configEnvironment
Local stateBacking services
Parity gapsTestcontainers
Slow startAOT/CRaC
Table 31 — Mistakes and fixes

Most twelve-factor violations creep in through convenience. Environment URLs end up in Spring profiles committed to the repository, so each environment needs its own build and configuration drifts. Secrets land in application.yaml in Git and leak. A service writes temporary files, sessions or job state to local disk, which disappears when a pod is rescheduled. Tests use H2 or mocks instead of the real database, so parity gaps reach production. Startup grows to a minute or more as dependencies accumulate, which quietly breaks autoscaling during traffic spikes. Catch these with automation rather than reviews: gitleaks for secrets, a lint rule against file appenders, startup time measured in CI and a compliance scorecard for every service.

8Performance & Production Considerations

ConsiderationPractice
StartupMeasured in CI
Config reloadRolling restarts
Log volumeLevels
Table 32 — Operating twelve-factor apps

Twelve-factor services behave well on a platform only if their lifecycle is fast and predictable. Measure time to readiness in CI and fail the build when it regresses beyond an agreed budget, because slow starts make scaling and rollouts slow. Changing environment configuration means restarting processes, so roll ConfigMap changes out with a rolling restart, and use a flag service for values that must change instantly, such as rate limits. Logging to stdout moves cost to the platform: control volume with levels and sampling, use structured JSON, and never log secrets or personal data. Size the termination grace period to cover the slowest in-flight request plus the load balancer's deregistration delay, so shutdowns never cut requests off.

9Architecture & Design Considerations

GuidelineDetail
Platform contractFactors
Extend with security/telemetryModern needs
Modernise incrementallyLegacy
Table 33 — Design guidance

10Interview Questions & Answers

Q1BasicConcept

What is the Twelve-Factor App?

The Twelve-Factor App is a methodology, published by Heroku engineers in 2011, for building software-as-a-service applications that are portable across environments, deployable continuously and scalable horizontally without architectural change. It sets out twelve practices: one codebase, explicit dependencies, config in the environment, backing services as attached resources, separate build, release and run stages, stateless processes, port binding, scaling through the process model, disposability, dev/prod parity, logs as event streams and admin tasks as one-off processes. Its real value is as a contract between an application and its platform: if a Spring Boot service follows it, Kubernetes can build, configure, scale, replace and observe it automatically, with no special handling.

Q2BasicConcept

Where should configuration live?

Configuration that varies between environments, such as URLs, credentials, feature settings and resource sizes, should live in the environment, not in the code or the build artifact. Then the same image moves unchanged from test to production and only the injected configuration differs. On Kubernetes that means ConfigMaps exposed as environment variables or mounted application.yaml files, with secrets coming from a secret manager through External Secrets, never stored in Git or baked into images. In Spring Boot, bind it to @ConfigurationProperties records with @Validated, so a bad value fails at startup. Profiles are fine for structure but not for environment values; ShopNow ended three builds per release by moving URLs out of the jar.

Q3IntermediateInternals

What does build, release, run mean?

It means three strictly separate stages. Build turns a commit into an immutable artifact, here a container image identified by its digest, built once by CI. Release combines that exact artifact with environment-specific configuration, for example the digest plus a production overlay committed to the GitOps repository, and every release is versioned and traceable. Run executes the release as processes, pods on Kubernetes, without changing code or config. Because nothing is modified at run time or rebuilt per environment, what was tested is what runs, any release can be traced to its commit, and rolling back simply means deploying a previous release. Patching code inside a running container breaks the model.

Q4IntermediateConcept

Why stateless processes?

Because instances that hold no unique state can be added, removed or replaced at any time without losing data or breaking user sessions. Twelve-factor processes are stateless and share nothing; anything that must outlive a request goes to a backing service. HTTP sessions become stateless JWTs or Spring Session on Redis, uploaded files go to S3, carts and other data go to DynamoDB or a database, scheduled job locks use ShedLock or Kubernetes CronJobs, and in-flight work sits in queues with idempotent consumers. Local caches are fine only if losing them is harmless. ShopNow learned this when in-memory carts vanished as the autoscaler removed pods after a sale; moving carts to DynamoDB fixed it.

Q5Senior EngineerCode

How do you achieve dev/prod parity in Java?

Run the same image everywhere and test against the same backing services as production, not lightweight substitutes. In Spring Boot tests, Testcontainers starts real dependencies in Docker, for example PostgreSQL 17 to match the production major version, Kafka and Redis, and the @ServiceConnection annotation wires their connection details into the application context automatically. For AWS services, LocalStack in Testcontainers emulates S3, SQS and DynamoDB closely enough for integration tests. Avoid H2 as a stand-in for PostgreSQL; ShopNow had a query that passed on H2 but failed in production because of case sensitivity. Keep the time, people and tools gaps small as well: deploy often, let developers deploy, and use identical manifests across environments.

Q6Senior EngineerInternals

What is disposability?

Disposability means processes can be started or stopped at a moment's notice, which is what lets a platform scale, deploy and recover automatically. It has two halves. Fast startup keeps autoscaling and rollouts useful; for Java that means trimming classpath scanning and using the JDK AOT cache, or CRaC checkpoints, to reach readiness in seconds. Graceful shutdown means that on SIGTERM the service stops accepting new work, finishes in-flight requests within the termination grace period and releases its resources; Spring Boot supports this with server.shutdown=graceful. Because crashes still happen, work must also be safe to repeat: idempotent handlers, Kafka offsets committed after processing, and messages redelivered rather than lost.

Q7Technical ArchitectDesign

How are admin processes run on Kubernetes?

As Kubernetes Jobs that use the same image and the same configuration as the application, so a migration or backfill runs exactly the code and settings that production runs. The note's example runs a Flyway migration as an Argo CD PreSync hook Job pinned to the image digest, with envFrom pulling in the service's ConfigMap and Secret, backoffLimit 0 and restartPolicy Never, so a failed migration stops the sync instead of retrying silently. Recurring tasks become CronJobs. Interactive investigation uses audited ephemeral debug containers rather than SSH into a server. The anti-pattern is running admin scripts from a laptop or a special admin host, with different code, credentials and dependencies.

Q8Technical ArchitectTricky

What's missing from the original twelve factors?

The original factors were written for a single platform and a mostly HTTP world, so several concerns that matter today are missing. API first: contracts defined up front so teams can work in parallel. Telemetry: metrics and traces, not just logs, because distributed systems cannot be diagnosed without them. Security: authentication, authorisation, least-privilege identities and secret management, which the original text barely covers. Health: liveness and readiness probes, so orchestrators know when to route traffic to a process or restart it. Graceful degradation and resilience patterns for calls between services matter too. Kevin Hoffman's Beyond the Twelve-Factor App adds most of these, and modern platform checklists treat them as equally mandatory.

Q9Solution ArchitectScenario

How do you modernise a legacy app?

Assess the application against each factor, then fix violations incrementally in order of impact, not all at once. NovaBank's path is typical. Config and logs came first, moving per-environment properties files into ConfigMaps and Secrets Manager and replacing Log4j file appenders with JSON on stdout, because together they unblock containerisation. Then state and port binding: stateless JWTs instead of session replication, and a Spring Boot executable jar instead of a WebLogic WAR. Then disposability and parity, with faster startup, graceful shutdown and Testcontainers, while nightly batch moved to CronJobs or AWS Batch. Score each service on a compliance scorecard, deliver changes quarter by quarter, and keep the application running in production throughout.

Q10Solution ArchitectTrade-off

Is twelve-factor still relevant?

Yes, though it should be applied pragmatically rather than as dogma. The factors became the default assumptions of container platforms: Kubernetes expects configuration injected at run time, stateless replicas it can reschedule, a fast and graceful lifecycle, logs on stdout and one-off Jobs, and serverless platforms such as Lambda assume the same. A service that ignores them fights its platform. Some guidance has aged: large or complex configuration is better mounted as files than spread across environment variables, and stateful workloads run well on managed services or operators. The methodology also needs the later additions of telemetry, security and health. Used as a checklist for the application-platform contract, it remains highly relevant.

Newsletter

Get new tutorials every week

New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 9

Cloud, Containers, DevOps & Production Architecture

Volume I · Topic 1.1

What Is Cloud Computing?

Cloud Computing & Cloud Architecture Fundamentals

Volume I · Topic 1.2

Cloud Service Models

Cloud Computing & Cloud Architecture Fundamentals

Volume I · Topic 1.3

Cloud Deployment Models

Cloud Computing & Cloud Architecture Fundamentals

Volume II · Topic 1.1

Regions

AWS & Cloud Services

Volume II · Topic 1.2

Availability Zones

AWS & Cloud Services

Volume II · Topic 1.3

Edge Locations

AWS & Cloud Services

Volume III · Topic 1.1

Containers

Containers & Kubernetes

Volume III · Topic 1.2

Containers vs Virtual Machines

Containers & Kubernetes

Volume III · Topic 1.3

Container Lifecycle

Containers & Kubernetes

Volume IV · Topic 1.1

IaC Fundamentals

Infrastructure as Code & Platform Engineering

Volume IV · Topic 1.2

Declarative vs Imperative

Infrastructure as Code & Platform Engineering

Volume IV · Topic 1.3

Immutable Infrastructure

Infrastructure as Code & Platform Engineering

Volume V · Topic 1.1

DevOps

CI/CD & DevOps Architecture

Volume V · Topic 1.2

Development + Operations

CI/CD & DevOps Architecture

Volume V · Topic 1.3

Continuous Integration

CI/CD & DevOps Architecture

Volume VI · Topic 1.1

Production Readiness Review

Production Architecture & Reliability Engineering

Volume VI · Topic 1.2

Operational Requirements

Production Architecture & Reliability Engineering

Volume VI · Topic 1.3

Capacity

Production Architecture & Reliability Engineering

Volume VII · Topic 1.1

Cloud-Native Principles

Cloud-Native Architecture & Multi-Region Systems

Volume VII · Topic 1.3

Immutable Infrastructure

Cloud-Native Architecture & Multi-Region Systems

Volume VIII · Topic 1.1

Architecture Drivers

Cloud Architecture Design, Cost & Migration

Volume VIII · Topic 1.2

Cloud Service Selection

Cloud Architecture Design, Cost & Migration

Volume VIII · Topic 1.3

Managed vs Self-Managed

Cloud Architecture Design, Cost & Migration