Long readFrom production

Docker on EC2: a minimalist's deploy stack

Three files. No yaml soup. Ice Bear keeps the fridge cold and the deploys cold too — here's the whole stack on one page.

Nitin Negi (Ice Bear)
Software Engineer · Sonoka.asia

When the workload fits on one box, ship it on one box. This stack runs Otto's beta in production: one EC2 instance, one Docker Compose file, one nightly cron. No control plane to maintain.

The premise

We weren't paid to run Kubernetes; we were paid to ship Otto. The boring path is a small EC2 instance, Docker Compose, and a Caddyfile. Three files cover the whole deploy.

Why not Kubernetes

Kubernetes is excellent infrastructure for problems we don't have. Multi-region failover, fleet-scale rollouts, dozens of microservices — none of that exists here. We have one app, one database, one queue. Pretending otherwise costs operator time we don't have.

The three files

# docker-compose.yml
services:
  app:    { image: otto/app:${VERSION}, restart: unless-stopped, env_file: .env }
  worker: { image: otto/worker:${VERSION}, restart: unless-stopped, env_file: .env }
  db:     { image: postgres:16, restart: unless-stopped, volumes: ["pg:/var/lib/postgresql/data"] }
volumes: { pg: {} }
# Caddyfile — TLS + HTTP/2 from the kernel up, in three lines
app.icebear.dev { reverse_proxy app:8080 }
# deploy.sh
docker compose pull && docker compose up -d

That's the entire deploy. Caddy handles TLS via Let's Encrypt automatically.

Zero-downtime, the cheap way

Compose alone gives us second-counted downtime — usually under three. Acceptable for our SLA. When we need genuine zero-downtime, we run two instances behind Caddy load balancing and roll them one at a time.

Backups & secrets

Postgres backups: pg_dump to S3 every six hours, retained 30 days, life-cycled to Glacier at 90. Secrets: AWS Parameter Store, pulled into env on deploy. Don't hand-edit .env files on the box.

What this stack costs

t4g.medium, 30GB EBS, S3 backups: about $24/month. The biggest cost is discipline — not adding moving parts until they earn their keep.

Six lessons, taped to the fridge

  • Match infra to workload. One box for one app is a feature.
  • Caddy first, nginx maybe. Auto-TLS is too useful to opt out of.
  • Pin everything. Image tags, package versions, OS versions.
  • Back up daily, test monthly. A backup you haven't restored is a guess.
  • Secrets out of the box. Parameter Store, not editor history.
  • Three files is enough. Until it isn't.