Infrastructure & Infrastructure as Code
Containers — Docker
Docker packages an application and all its dependencies into an immutable image. The same image runs identically on a developer laptop, CI runner, and production server.
Production-Ready Dockerfile
# Stage 1: build dependencies
FROM python:3.12-slim AS builder
WORKDIR /app
COPY uv.lock pyproject.toml ./
RUN pip install uv && uv sync --frozen --no-dev
# Stage 2: minimal runtime image
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY src/ ./src/
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
USER nobody
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
Key practices:
- Multi-stage build — build tools not in production image
- Non-root user — limits blast radius of container escape
- Health check — orchestrator knows when service is ready
- PYTHONUNBUFFERED=1 — logs appear immediately in CI
Container Orchestration — Kubernetes
Kubernetes manages containers at scale: scheduling, scaling, health, networking.
Minimal Deployment Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
spec:
replicas: 3
selector:
matchLabels:
app: api-service
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: api-service
spec:
containers:
- name: api
image: ghcr.io/org/api:v1.4.2 # always pin, never :latest
ports:
- containerPort: 8000
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: api-secrets
key: db-host
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
Infrastructure as Code (IaC)
Infrastructure defined in code: version-controlled, reviewed, reproducible.
Terraform
# Provision a managed Postgres on GCP
resource "google_sql_database_instance" "main" {
name = "api-db-${var.environment}"
database_version = "POSTGRES_16"
region = "europe-west1"
settings {
tier = var.environment == "prod" ? "db-n1-standard-4" : "db-f1-micro"
backup_configuration {
enabled = true
start_time = "02:00"
}
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.vpc.id
}
}
}
IaC principles: - Idempotent — run the same plan twice, second run changes nothing - Declarative — describe the desired state, not the steps - Version-controlled — infra changes go through pull requests - Environment parity — staging mirrors production in config, not just code
CloudFormation (AWS)
Resources:
ApiDatabase:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: !If [IsProd, db.t3.medium, db.t3.micro]
Engine: postgres
EngineVersion: "16.0"
MasterUsername: !Sub "{{resolve:secretsmanager:db-credentials:username}}"
MasterUserPassword: !Sub "{{resolve:secretsmanager:db-credentials:password}}"
MultiAZ: !If [IsProd, true, false]
IaC in CI/CD Pipeline
# Terraform plan on PR, apply on merge
- name: Terraform plan
if: github.event_name == 'pull_request'
run: terraform plan -out=tfplan
- name: Terraform apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve tfplan
Never run terraform apply without a preceding plan in the same pipeline run.