12-Factor App and IaC — Deep Dive
Level: Intermediate
Pre-reading: 09 · Deployment & Infrastructure
The 12-Factor App
| Factor | Principle | Implementation |
|---|---|---|
| 1. Codebase | One repo; many deploys | Git repo → dev/staging/prod |
| 2. Dependencies | Explicitly declared | pom.xml, package.json |
| 3. Config | In environment | Env vars, ConfigMaps |
| 4. Backing services | Attached via URL | DATABASE_URL |
| 5. Build/Release/Run | Strictly separated | CI builds → immutable releases |
| 6. Processes | Stateless | No sticky sessions; state in DB/cache |
| 7. Port binding | Export via port | App exposes HTTP |
| 8. Concurrency | Scale via process | Horizontal pod scaling |
| 9. Disposability | Fast start/graceful stop | SIGTERM handling |
| 10. Dev/Prod parity | Keep environments similar | Same Docker images |
| 11. Logs | Event streams | Write to stdout |
| 12. Admin processes | One-off tasks | K8s Jobs |
Config Management
// 12-Factor: Configuration from environment
@Configuration
public class DatabaseConfig {
@Value("${DATABASE_URL}")
private String databaseUrl;
@Value("${DATABASE_POOL_SIZE:10}")
private int poolSize;
}
# Kubernetes ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: order-config
data:
SPRING_PROFILES_ACTIVE: production
LOG_LEVEL: INFO
Graceful Shutdown
@PreDestroy
public void onShutdown() {
log.info("Received shutdown signal");
// Complete in-flight requests
// Close connections
// Flush buffers
}
# K8s gives 30s by default
spec:
terminationGracePeriodSeconds: 60
containers:
- lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"] # Allow LB to drain
Infrastructure as Code (IaC)
| Tool | Language | Use Case |
|---|---|---|
| Terraform | HCL | Multi-cloud infrastructure |
| Pulumi | TypeScript/Python/Go | Programming language IaC |
| AWS CDK | TypeScript/Python | AWS infrastructure |
| Crossplane | YAML (K8s CRDs) | K8s-native cloud resources |
Terraform Example
resource "aws_eks_cluster" "main" {
name = "production-cluster"
role_arn = aws_iam_role.eks.arn
version = "1.28"
vpc_config {
subnet_ids = var.subnet_ids
}
}
resource "aws_rds_instance" "orders_db" {
identifier = "orders-db"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.medium"
allocated_storage = 100
db_name = "orders"
username = "admin"
password = var.db_password
multi_az = true
skip_final_snapshot = false
}
IaC Best Practices
| Practice | Rationale |
|---|---|
| Version control | Track changes; review via PRs |
| Remote state | Share state; lock for safety |
| Modules | Reusable components |
| Environments | Separate state per env |
| CI/CD for infra | terraform plan in PR; apply on merge |
Why is 'stateless processes' important for microservices?
Stateless processes enable horizontal scaling — any instance can handle any request. No sticky sessions means load balancers can distribute freely. State lives in databases/caches, not in process memory.
Terraform vs Pulumi — when to use which?
Terraform: Industry standard; large ecosystem; declarative HCL. Pulumi: Real programming languages; better for complex logic; smaller ecosystem. Use Terraform for standard infrastructure; consider Pulumi when you need programming constructs.
How do you handle secrets in Terraform?
(1) Don't commit to Git — use .gitignore. (2) Use terraform.tfvars locally; inject in CI. (3) Reference secrets from Vault/AWS SM. (4) Use SOPS for encrypted files. (5) Terraform Cloud/Enterprise has built-in secrets management.