Careeroza
The Twelve-Factor App
Twelve Rules for Cloud Services
The Twelve-Factor App is a well-known checklist (from Heroku) for building services that run well in the cloud. Spring Boot follows most of it out of the box.
| # | Factor | In a Spring Boot service |
|---|---|---|
| 1 | Codebase | One Git repo per service, many deploys (dev, test, prod) |
| 2 | Dependencies | Declared in pom.xml; the fat JAR contains them |
| 3 | Config | In the environment: SPRING_DATASOURCE_URL, profiles, Config Server |
| 4 | Backing services | DB, Kafka, Redis are attached resources selected by URL |
| 5 | Build, release, run | CI builds the JAR/image once; the same image goes to every environment |
| 6 | Processes | Stateless: no user session in memory; store state in DB/Redis |
| 7 | Port binding | Embedded Tomcat exposes server.port; no external app server |
| 8 | Concurrency | Scale out by running more instances |
| 9 | Disposability | Fast startup, graceful shutdown (server.shutdown=graceful) |
| 10 | Dev/prod parity | Same DB type locally (Docker/Testcontainers) as in prod |
| 11 | Logs | Write to stdout; the platform collects them |
| 12 | Admin processes | One-off tasks (migrations with Flyway) run as part of the release |
Config From the Environment
Example
Spring Boot maps environment variables onto properties automatically (relaxed binding): SPRING_DATASOURCE_URL → spring.datasource.url. The same JAR runs everywhere:
# same JAR, different environments
SERVER_PORT=8081 \
SPRING_DATASOURCE_URL=jdbc:mysql://prod-db:3306/product_db \
SPRING_DATASOURCE_USERNAME=product_svc \
SPRING_DATASOURCE_PASSWORD=$DB_PASSWORD \
java -jar product-service-1.0.0.jarExample output
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ... INFO c.s.product.ProductServiceApplication : Starting ProductServiceApplication v1.0.0 INFO o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8081 (http) with context path '/' INFO c.s.product.ProductServiceApplication : Started ProductServiceApplication in 3.214 seconds
Example: Stateless and Configurable
Example
A service reads its settings from an environment map (a stand-in for System.getenv()) with safe defaults, and keeps no per-user state in memory, so any instance can serve any request:
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<String, String> devEnv = Map.of();
Map<String, String> prodEnv = Map.of(
"SERVER_PORT", "8081",
"PRODUCT_DB_URL", "jdbc:mysql://prod-db:3306/product_db",
"LOG_LEVEL", "WARN");
System.out.println("dev : " + ServiceConfig.from(devEnv));
System.out.println("prod: " + ServiceConfig.from(prodEnv));
// Stateless: the cart lives in a shared store, not in an instance field
Map<String, List<String>> sharedCartStore = new TreeMap<>(); // stand-in for Redis
Instance a = new Instance("instance-A", sharedCartStore);
Instance b = new Instance("instance-B", sharedCartStore);
a.addToCart("user7", "Kettle"); // first request hits A
b.addToCart("user7", "Headphones"); // next request hits B
System.out.println("Cart seen by A: " + a.cart("user7"));
}
}
record ServiceConfig(int port, String dbUrl, String logLevel) {
static ServiceConfig from(Map<String, String> env) {
return new ServiceConfig(
Integer.parseInt(env.getOrDefault("SERVER_PORT", "8080")),
env.getOrDefault("PRODUCT_DB_URL", "jdbc:h2:mem:product_db"),
env.getOrDefault("LOG_LEVEL", "INFO"));
}
}
record Instance(String name, Map<String, List<String>> store) {
void addToCart(String user, String item) {
store.computeIfAbsent(user, k -> new ArrayList<>()).add(item);
System.out.println(name + " added " + item + " for " + user);
}
List<String> cart(String user) { return store.getOrDefault(user, List.of()); }
}Output
dev : ServiceConfig[port=8080, dbUrl=jdbc:h2:mem:product_db, logLevel=INFO] prod: ServiceConfig[port=8081, dbUrl=jdbc:mysql://prod-db:3306/product_db, logLevel=WARN] instance-A added Kettle for user7 instance-B added Headphones for user7 Cart seen by A: [Kettle, Headphones]
Tip: Never hard-code passwords or hostnames in application.yml that goes into Git. Use ${DB_PASSWORD} placeholders and set the value in the environment or a secret store.