Skip to main content
DevOps & Cloud · Topic 1 of 5

The Twelve-Factor App

The twelve-factor methodology (from Heroku engineers) describes how to build portable, scalable, cloud-friendly services. Interviewers use it as a checklist: "is your service cloud-native?" Below, each factor with the Spring Boot / container / Kubernetes translation.

#FactorIn one line
ICodebaseOne codebase tracked in version control, many deploys
IIDependenciesExplicitly declare and isolate dependencies
IIIConfigStore config in the environment
IVBacking servicesTreat backing services as attached resources
VBuild, release, runStrictly separate build and run stages
VIProcessesExecute the app as one or more stateless processes
VIIPort bindingExport services via port binding
VIIIConcurrencyScale out via the process model
IXDisposabilityFast startup and graceful shutdown
XDev/prod parityKeep development, staging and production similar
XILogsTreat logs as event streams
XIIAdmin processesRun admin/management tasks as one-off processes

Definition​

The twelve-factor app is a checklist for services that run well in the cloud: one codebase, declared dependencies, config in the environment, stateless processes, logs as streams, and fast start and stop.

Why it exists​

Apps built for a single server break on elastic platforms: files on local disk, hard-coded settings and slow startup all get in the way. The factors make a service portable and easy to scale.

How it works​

I. Codebase​

One repository per deployable service (or a monorepo with independently built modules); the same codebase produces every environment's deployment (dev, staging, prod). Shared code becomes a versioned library, not copy-paste. Anti-pattern: separate forks per environment or customer.

II. Dependencies​

Declare all dependencies in the build file (pom.xml/build.gradle) and never rely on system-wide packages. Boot's dependency management (BOM) pins compatible versions (Starters); the fat jar/image bundles everything needed except the JRE. Pin versions, use dependency scanning, and avoid "works because the server happens to have X installed".

III. Config​

Config that varies between deploys (URLs, credentials, feature flags) lives in the environment, not in the code or the image. Spring Boot's externalised configuration (property precedence) fits: SPRING_DATASOURCE_URL, --server.port=..., mounted config trees, Spring Cloud Config. Test: could you open-source the repository right now without leaking credentials? Group config by environment variables or files per deploy, not by named environments in code (if (env == "prod")), and keep secrets in a secret manager. Use @ConfigurationProperties records with validation to fail fast on missing config.

IV. Backing services​

Databases, queues, caches, SMTP and third-party APIs are attached resources identified by a URL/credentials in config. Swapping a local PostgreSQL for a managed one, or replacing a broker, should need no code change. Spring's abstractions (DataSource, KafkaTemplate, RestClient) and Testcontainers support this. Design for their failure: timeouts, retries, circuit breakers (Resilience4j).

V. Build, release, run​

Three strictly separate stages:

  • Build: compile and package an artifact (jar → container image).
  • Release: combine the build with environment config, yielding an immutable, versioned release.
  • Run: start the release in the execution environment.

Never patch code on a running server. A fix is a new build → release. This is exactly the CI/CD principle of building once and promoting. Rollback = run the previous release.

VI. Processes (stateless, share-nothing)​

Processes are stateless and share nothing; persistent state lives in backing services. Anything in memory or local disk can vanish at any time (restart, reschedule).

  • No sticky sessions or in-JVM session state: use JWT (JWT) or Spring Session + Redis.
  • Do not store uploads on local disk: use object storage.
  • Local caches are an optimisation only (rebuildable); shared state belongs in a database or distributed cache.
  • Scheduled jobs in a scaled service run once per instance: use a distributed scheduler lock (ShedLock) or a Kubernetes CronJob.
  • Singleton Spring beans must be stateless anyway (DI).

VII. Port binding​

The service is self-contained and exports HTTP by binding to a port; it does not depend on an external web server being injected. Boot embeds Tomcat/Netty: java -jar app.jar listens on server.port (read from the environment, e.g. PORT). One service can become another's backing service via its URL.

VIII. Concurrency​

Scale out by running more identical processes (horizontal scaling), by workload type (web, workers, schedulers), rather than making one process huge. In Kubernetes: more replicas (HPA), separate Deployments for the API and background consumers. This only works if processes are stateless (VI). Within a process, use thread pools/virtual threads for concurrency, but plan capacity as replicas × per-instance throughput (Executors). Mind per-instance resource use against shared backing services (connection pool × replicas ≤ database limit).

IX. Disposability​

Processes can be started or stopped at a moment's notice: fast startup (seconds), graceful shutdown on SIGTERM, robust against sudden death.

  • Startup: avoid heavy work in initialisers; consider CDS/native for faster start (Docker); use readiness to gate traffic.
  • Shutdown: server.shutdown=graceful, finish in-flight requests, stop consuming messages, flush buffers, release locks (lifecycle); exec-form entrypoint so SIGTERM reaches the JVM.
  • Crash-safety: jobs must be idempotent and resumable; message processing at-least-once with idempotent handlers (Kafka consumers). Use Kubernetes terminationGracePeriodSeconds and preStop hooks correctly (Kubernetes basics).

X. Dev/prod parity​

Minimise gaps in time (deploy quickly and often), people (developers deploy and operate), and tools (same backing services in dev and prod). Do not develop on H2 and run on PostgreSQL: use Docker Compose/Testcontainers with the real engines and the same migrations (Testcontainers). Use the same image locally, in staging and in production. Keep profile-specific behaviour minimal (profiles).

XI. Logs​

The app does not manage log files; it writes an unbuffered event stream to stdout/stderr, and the platform routes and stores it (Fluent Bit → Loki/Elasticsearch/CloudWatch). Practices: structured JSON logs (Boot 3.4+ built-in structured logging), a correlation/trace id on every line (Observability), log levels per environment via configuration, no secrets or PII, and avoid multi-line stack traces that break aggregation (structured logging handles them as one event).

XII. Admin processes​

One-off administrative tasks (database migrations, data fixes, reports) run as separate short-lived processes using the same release, code and config as the app: Kubernetes Jobs or kubectl run, Flyway as an init container/Job (schema migrations), or a Spring Boot CommandLineRunner activated by a profile/argument. Avoid SSH-ing into servers to run scripts and avoid hidden admin endpoints in the main app.

Beyond the original twelve​

Cloud-native practice added more factors (Kevin Hoffman's "Beyond the Twelve-Factor App"):

  • API first: design the contract (OpenAPI) before the implementation; version it (Communication).
  • Telemetry: metrics, traces, health endpoints as first-class features (Actuator, Observability).
  • Authentication and authorisation: security built in, zero-trust service-to-service (OAuth2/OIDC).
  • Resilience: assume dependencies fail; timeouts, bulkheads, circuit breakers.
  • Infrastructure as code and declarative delivery (GitOps).

System design considerations​

  • Keep services stateless and store state in backing services
  • Make startup and shutdown fast and graceful so autoscaling works
  • Use the same build artifact in every environment, differing only in configuration

When to use it, and when not​

Use it when

  • You build services to run on containers or a PaaS
  • You review a service for operational readiness
  • You explain cloud-native design in an interview

Think twice when

  • You treat the list as dogma where a trade-off is justified
  • A batch or stateful system genuinely needs local state (design for it explicitly)
  • You stop at twelve and ignore observability and security

A quick self-assessment for a Spring Boot service​

QuestionGood answer
Where do DB credentials come from?Secret manager/environment at runtime
Can you kill any instance at any time?Yes: no local state, graceful shutdown, idempotent work
Can you scale to 10 instances?Yes: sessions external, schedulers coordinated
Is the production image the one tested?Yes: same immutable artifact
Where do logs go?stdout as structured JSON with trace ids
How do you run a migration?A Job/init step from the same release
Does local dev use the same DB engine?Yes: containers

Common mistakes​

  • Environment-specific config files baked into images.
  • In-memory sessions or local file storage.
  • Different database engines in dev and prod.
  • File-based logging inside containers without rotation or shipping.
  • Hot-patching running containers.
  • Cron-in-the-app running on every replica.

Interview questions​

1. What are the twelve factors? Name five and give a Spring Boot example for each.

Config (SPRING_* env vars), Backing services (DataSource URL swap), Processes (stateless with JWT/Spring Session), Logs (stdout JSON), Disposability (graceful shutdown), also Build/release/run, Dev/prod parity, Port binding (embedded Tomcat).

2. Why must processes be stateless?

So any instance can serve any request and be replaced or scaled freely; state is externalised to backing services.

3. How do you handle configuration across environments?

Same artifact everywhere; config injected through environment variables, mounted files, config servers and secret managers; never baked into the image.

4. What is dev/prod parity and how do you achieve it?

Keeping environments similar in time, people and tools: same images, same backing service types via containers, frequent deployment.

5. Why log to stdout?

The execution environment owns log routing and retention; apps stay simple and portable across platforms.

6. How do you run database migrations in a twelve-factor system?

As a one-off admin process (Job or init container) from the same release before or during rollout.

7. How does disposability relate to Kubernetes?

Pods are ephemeral: fast starts, graceful SIGTERM handling, readiness gating and crash-safe idempotent work are required for rolling updates and autoscaling.

8. Which twelve-factor principles do sticky sessions violate?

Stateless processes (VI) and concurrency via scale-out (VIII); externalise session state.

Check yourself​

Quick quiz3 questions

1Where should environment-specific settings such as database URLs live?

2How should a twelve-factor app handle logs?

3Why keep processes stateless?

Key takeaways​

  • Same immutable build everywhere; configuration comes from the environment.
  • Stateless, disposable processes that scale horizontally.
  • Backing services are swappable resources; logs are streams; admin tasks are one-off jobs.
  • Add observability, security and API-first design to complete the picture.

Next: Deployment strategies.

Next topicDocker for Java

Finished this page? Track your progress (saved only in your browser).