🏛️

The 12-Factor App Methodology

System Architecture Intermediate 4 min read 700 words
System Design Microservices

The 12-Factor App Methodology

The 12-factor app methodology is a set of best practices for building modern, scalable, cloud-native applications. These principles help create applications that are portable, resilient, and easy to deploy.

1. Codebase

“One codebase tracked in revision control, many deploys”

A 12-factor app should have a single codebase that can be deployed to multiple environments (development, staging, production).

Key Principles:

  • One codebase per application
  • Use a logical version control system (Git)
  • Use branches for different versions, not multiple codebases
  • The repository shouldn’t house multiple applications
Repository Structure:
├── main branch → Production
├── develop branch → Staging
└── feature/* branches → Development

2. Dependencies

“Explicitly declare and isolate dependencies”

Never rely on implicit existence of system-wide packages. Declare all dependencies explicitly.

Key Principles:

  • Application must be self-containing
  • Isolate dependencies to avoid conflicts
  • Include necessary system libraries
  • Use package managers (NuGet, npm, pip)
<!-- .NET Example: csproj -->
<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
  <PackageReference Include="Serilog" Version="3.1.1" />
</ItemGroup>

3. Config

“Store config in the environment”

Never commit environment-specific configurations (passwords, connection strings) in source code.

Key Principles:

  • Application and configuration must be independent
  • Store config in environment variables
  • Simplifies updating config without redeployment
  • Config is independent of operating system and language
// ❌ Bad - hardcoded config
var connectionString = "Server=prod-db;User=admin;Password=secret";

// ✅ Good - environment variables
var connectionString = Environment.GetEnvironmentVariable("DATABASE_URL");

// ✅ Better - ASP.NET Core configuration
public class Startup
{
    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        var connectionString = Configuration.GetConnectionString("DefaultConnection");
    }
}

4. Backing Services

“Treat backing services as attached resources”

External services (databases, message queues, caches) should be treated as resources that can be attached/detached without code changes.

Key Principles:

  • Access services via URL/connection string in config
  • No distinction between local and third-party services
  • Services can be swapped without code changes
// Services accessed through configuration
services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(Configuration["Services:Database"]));

services.AddStackExchangeRedisCache(options =>
    options.Configuration = Configuration["Services:Redis"]);

5. Build, Release, Run

“Strictly separate build and run stages”

The deployment pipeline should have distinct stages that cannot be mixed.

Three Stages:

  1. Build: Convert code to executable bundle (compile, fetch dependencies)
  2. Release: Combine build with config for specific environment
  3. Run: Execute application in the target environment
# CI/CD Pipeline Example
stages:
  - build:    # Compile, run tests
  - release:  # Create versioned artifact with config
  - deploy:   # Deploy to environment

Key Principles:

  • Each release must have a unique ID (timestamp, version number)
  • Releases are immutable - changes require new release
  • Rollback by deploying previous release

6. Processes

“Execute the app as one or more stateless processes”

Applications should be stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service.

Key Principles:

  • No state stored in memory or filesystem
  • Use backing services (database, Redis) for persistent data
  • Use sticky sessions only as a last resort
// ❌ Bad - in-memory state
private static Dictionary<string, object> _cache = new();

// ✅ Good - distributed cache
public class CartService
{
    private readonly IDistributedCache _cache;

    public async Task SaveCartAsync(string userId, Cart cart)
    {
        await _cache.SetStringAsync(
            $"cart:{userId}",
            JsonSerializer.Serialize(cart));
    }
}

7. Port Binding

“Export services via port binding”

The app should be completely self-contained and not rely on runtime injection of a webserver.

Key Principles:

  • App exports HTTP as a service by binding to a port
  • Web server library included as dependency
  • App can become a backing service for other apps
// ASP.NET Core - self-contained web server
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World");
app.Run("http://localhost:5000");

8. Concurrency

“Scale out via the process model”

Design application to scale horizontally by running multiple instances.

Key Principles:

  • Scale by adding more processes/instances
  • Different workload types can be different process types
  • Never daemonize or write PID files
Scaling Model:
├── Web (3 instances) ─── Handle HTTP requests
├── Worker (5 instances) ─── Process background jobs
└── Clock (1 instance) ─── Schedule periodic tasks

9. Disposability

“Maximize robustness with fast startup and graceful shutdown”

Processes should be disposable - they can be started or stopped at any time.

Key Principles:

  • Minimize startup time
  • Shut down gracefully on SIGTERM
  • Handle sudden death gracefully
  • Make operations idempotent
// Graceful shutdown in ASP.NET Core
var app = builder.Build();

var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopping.Register(() =>
{
    // Finish current requests
    // Close connections gracefully
    Log.Information("Application shutting down...");
});

10. Dev/Prod Parity

“Keep development, staging, and production as similar as possible”

Minimize gaps between development and production environments.

Three Gaps to Minimize:

  1. Time gap: Deploy frequently (hours, not weeks)
  2. Personnel gap: Developers involved in deployment
  3. Tools gap: Use same backing services in all environments
# Docker Compose - same services everywhere
services:
  app:
    build: .
    environment:
      - DATABASE_URL=postgres://db:5432/app
  db:
    image: postgres:15
  redis:
    image: redis:7

11. Logs

“Treat logs as event streams”

Apps should not manage log files. Instead, write logs to stdout as an event stream.

Key Principles:

  • Write to stdout/stderr
  • Don’t route or store logs within the app
  • Use log aggregation tools (ELK, Splunk, Datadog)
// Serilog streaming to console
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(new JsonFormatter())
    .CreateLogger();

// Logs are captured by orchestrator and forwarded
Log.Information("Processing order {OrderId}", orderId);

12. Admin Processes

“Run admin/management tasks as one-off processes”

Administrative tasks should run as one-off processes in an identical environment.

Key Principles:

  • Run against same codebase and config
  • Use same isolation and dependencies
  • Ship admin code with application code
// Database migration as one-off process
// dotnet ef database update

// Custom admin command
public class MigrateDataCommand
{
    public async Task ExecuteAsync()
    {
        // Uses same DbContext, config, and dependencies
        // Runs in same environment as main app
    }
}

Summary Table

Factor Principle .NET Implementation
Codebase One repo, many deploys Git + CI/CD
Dependencies Explicit declaration NuGet packages
Config Environment variables IConfiguration
Backing Services Attached resources Connection strings
Build/Release/Run Separate stages Docker + K8s
Processes Stateless Distributed cache
Port Binding Self-contained Kestrel
Concurrency Horizontal scaling K8s replicas
Disposability Fast start/stop Graceful shutdown
Dev/Prod Parity Similar environments Docker
Logs Event streams Serilog + ELK
Admin Processes One-off tasks EF migrations

Sources

  • C#/Teorie/Microservices.docx

📚 Related Articles