Operations

How to build, deploy, and run applications.

Build, Release, Run

Strictly separate the stages. Never change code at runtime.

┌─────────┐    ┌─────────┐    ┌─────────┐
│  BUILD  │───▶│ RELEASE │───▶│   RUN   │
└─────────┘    └─────────┘    └─────────┘
 Compile       Code +          Execute
 + Assets      Config          Process

Releases are immutable and append-only. Every release has a unique ID.

Stateless Processes

Execute the app as one or more stateless processes.

Request A ──▶ Process 1 ──┐
                          ├──▶ Database/Cache
Request B ──▶ Process 2 ──┘

Any process can handle any request

Where state belongs:

Port Binding

The app is completely self-contained. It exports services by binding to a port directly — it doesn't rely on a web server being injected at runtime.

# App binds to a port and serves HTTP directly
PORT=3000 python server.py

# Routing layer forwards requests
nginx:80 ──▶ app:3000

This means one app can be another app's backing service.

Concurrency

Scale horizontally, not vertically. More processes, not bigger machines.

                    ┌─────────────────────────────────┐
                    │        PROCESS TYPES            │
                    ├─────────────────────────────────┤
Scale ▲             │ web    ████████████████         │
      │             │ worker ████████                 │
      │             │ clock  ██                       │
      │             └─────────────────────────────────┘

Disposability

Processes should start fast and stop gracefully.

START                          STOP
  │                              │
  ▼                              ▼
┌──────────┐                ┌──────────┐
│ < 10 sec │                │ Finish   │
│ startup  │                │ current  │
│          │                │ request  │
└──────────┘                │ Release  │
                            │ resources│
                            └──────────┘
import signal

def handle_sigterm(signum, frame):
    server.shutdown()
    db.disconnect()
    sys.exit(0)

signal.signal(signal.SIGTERM, handle_sigterm)

Dev/Prod Parity

Keep development, staging, and production as similar as possible.

Gap Wrong Right
Time Weeks between deploys Hours or minutes
Personnel Devs write, ops deploy Same people do both
Tools SQLite dev, Postgres prod Same everywhere

Use the same database, queue system, cache, and search backend in every environment.

# docker-compose.yml - same services everywhere
services:
  db:
    image: postgres:15
  redis:
    image: redis:7
  app:
    build: .

Logs

Apps should not manage log files. Write to stdout, one event per line.

┌─────────────┐
│    App      │
│  (stdout)   │
└──────┬──────┘
       │
       ▼
┌─────────────────────────────────┐
│       Execution Environment      │
│  ┌──────────┐  ┌──────────────┐ │
│  │ Log file │  │ Log service  │ │
│  │          │  │ (Datadog,    │ │
│  │          │  │  Splunk)     │ │
│  └──────────┘  └──────────────┘ │
└─────────────────────────────────┘

The app's only job: Write events to stdout

The environment's job: Capture, route, store, analyze

Admin Processes

Database migrations, console sessions, one-time scripts — run them as one-off processes in the same environment as the app.

┌─────────────────────────────────────┐
│           Same Release              │
├─────────────────────────────────────┤
│  web process   │   admin process    │
│  (long-lived)  │   (one-off)        │
│                │                    │
│  Serves HTTP   │  Runs migration    │
│                │  then exits        │
└─────────────────────────────────────┘