The idea
What if your web app could detect that it's dead and just... come back? No PagerDuty. No 3am phone call. No "sorry, deploying a fix." Just a container that notices it's down and says "nah, I'm fine now" within seconds.
That's what I built. And I'm going to walk you through the whole thing including the parts where I'm an idiot.
self-healing-app/
├── app/ # Flask app (the thing that dies)
│ ├── app.py
│ └── Dockerfile
├── nginx/ # Reverse proxy
│ └── nginx.conf
├── prometheus/ # Monitoring + alerting
│ ├── prometheus.yml
│ └── alert.rules.yml
└── docker-compose.yml
`Four containers on a bridge network:
Container Job
web-app Flask app with /health and /metrics endpoints
nginx-proxy Routes traffic to the app
prometheus Scrapes metrics every 5s, fires alerts
grafana Makes it look like you know what you're doing
`
Five seconds of up == 0 and the alert fires. No flapping on a single missed scrape.
The debugging journey (a.k.a. the real tutorial)
Problem 1: docker-compose not found.
Fix: It's docker compose (no hyphen) now. The old v1 binary is deprecated. Install docker-compose-plugin.
**Problem 2: **Port 9090 already in use.
Fix: sudo lsof -i :9090 → it was a leftover process. Killed it.
Problem 3: Port 80 already in use.
Fix: System nginx was running. sudo systemctl stop nginx && sudo systemctl disable nginx.
Problem 4: Prometheus target stuck at health: down with error received unsupported Content-Type "text/html".
Fix: Flask returns text/html for string responses. Changed to:
return Response("up 1\n", content_type="text/plain; version=0.0.4; charset=utf-8")
Problem 5: A tab character in alert.rules.yml.
Fix: sed -i 's/\t/ /g' alert.rules.yml. I will never use tabs in YAML again.
P*roblem 6*: I pasted a YAML healthcheck block into my Python file.
Fix: I don't talk about this one.
Testing the self-healing
Kill the app
docker kill web-app
Watch Prometheus (http://localhost:9090)
- Target goes red
- up metric drops to 0
- Alert fires after 5s
- Docker restarts the container
- Target goes green
- Alert resolves
Total downtime: ~5-10 seconds. No human required.
Running it
docker compose up --build -d
App: http://localhost
Prometheus: http://localhost:9090
Grafana: http://localhost:3000 (admin/admin)
Add Prometheus as a data source in Grafana (URL: http://prometheus:9090), import dashboard ID 1860, and you've got a monitoring wall that would make your DevOps friend jealous.
What's next
- Migrate to Azure Container Apps (managed, serverless-ish, auto-scaling) Add Alertmanager for actual notifications (Slack, email, PagerDuty) Automate failure injection (no more manual docker kill) Add node_exporter for host-level metrics Maybe a Terraform module for the whole thing Final thoughts The "self-healing" part is the easiest bit. The hard part is making sure your monitoring is actually monitoring — that the scrape works, the content type is right, the network is correct, the YAML doesn't have a rogue tab.
The system doesn't heal itself. You heal it first. Then it heals itself. Then you sleep.
Top comments (0)