A zero-cost local observability stack with Docker Compose
I used to run my monitoring stack on an always-on t3.micro. It worked — until the bill reminded me that "free tier" stops being free once you add a couple of public IPv4 addresses. The fix wasn't a bigger instance; it was moving the stack to local Docker Compose and spinning it up only when I need it.
Why local first
- $0 idle. Nothing runs when I'm not demoing. No EC2 hours, no idle Elastic IPs.
- Reproducible. One
docker compose upbrings up the exact same stack on any machine. - Still cloud-aware. The local Grafana reads AWS CloudWatch over a read-only IAM role — local UI, cloud data.
The shape of it
Three services, one network, persistent volumes:
services:
grafana: # dashboards + alerts
postgres: # metrics + app data (volume)
n8n: # scheduled collection / ETL
Grafana talks to PostgreSQL as a datasource; n8n runs the scheduled pulls (CloudWatch → normalize → insert). Everything is declared, nothing is hand-clicked.
Lessons that kept it cheap and stable
- Reuse connections. One SFTP/DB connection per run, batched inserts — not one per row.
- Healthchecks +
depends_on. Grafana shouldn't boot before Postgres is actually ready. - Idempotency. Re-running the pipeline must converge, not duplicate.
- Secrets out of the image. Env files ignored by git; never bake credentials into a layer.
Cloud is a deployment target, not a place your dev loop has to live.
Takeaway
For side projects and portfolios, "always-on in the cloud" is a tax you don't need to pay. Containerize it, run it locally, and promote to the cloud only when something actually needs to be public.
Written by Juan Valentin Alducin Vázquez · Get in touch ›