Aegira: A Lightweight Self-Healing Watcher for Linux & Docker
It's 2AM. Your phone buzzes. A service is down. You drag yourself out of bed, SSH in, systemctl restart, and go back to sleep. Two hours later — same thing, different container.
I got tired of it. So I built Aegira — a small, fast, self-hosted recovery engine written in Rust. It watches your Linux services and Docker containers, and when something breaks, it fixes it — automatically, deterministically, without AI.
This post is about why I built it, what it does, and how it thinks. No fluff.
The Idea
I never wanted to know my service was down at 2AM. I wanted it back up at 2AM. That's a different problem than monitoring, and it's the one I kept running into.
Monitoring tells you something broke. It doesn't put it back.
So I asked myself a simple question: what would it take for a small daemon to sit on my server, watch the things I already know how to fix, and just... fix them?
Four constraints came out of that:
- Small — one binary. No database, no dashboard, no agent mesh.
- Deterministic — every action decided by a rule I wrote. No model. No surprises.
- Self-hosted — talks to nothing outside unless I tell it to.
- Honest — if a rule would do something destructive, it refuses to load it.
That's Aegira. I wrote it in Rust because I wanted a single static binary that idles at near-zero CPU on a small VPS, and because when a root daemon runs shell commands on my behalf, I want the language to make it hard to do the wrong thing.
What Aegira Actually Does
Aegira is a watch → match → remediate → verify → alert engine.
It watches four kinds of signals:
-
Log lines — tails a log file and matches against rule patterns like
"connection refused"or"Out of memory". -
Docker events — subscribes to the Docker event stream for
die,oom, andhealth_status:unhealthy. - HTTP health checks — polls a URL on an interval and compares the status code.
-
Container probes — runs a command inside a container on an interval (e.g.
pg_isready).
When a signal matches a rule, Aegira executes the rule's remediation:
-
service_restart—systemctl restart <service> -
container_restart—docker restart <container> -
command— run a host command -
container_exec—docker execa command in a container -
container_exec_background—docker exec -d(detached) -
command_sequence— a fixed series of commands, each with its own verification -
alert_only— don't touch anything, just tell me
Then it verifies. Every remediation has a verification: is the service active? Is the container running? Is the health endpoint back to 200? Does the probe command return 0?
If verification passes — incident resolved.
If it fails after retries — it escalates to a manual action state and (optionally) sends an alert.
Why No AI
This is a deliberate design choice, not a limitation.
When a rule fires at 2AM, I want to know exactly which command will run on my server. Not "the model decided to restart something." I want a readable JSON file I can cat, review, and commit to git.
That's what a rule is in Aegira:
{"id":"connection_refused","name":"Connection Refused","error_patterns":["connection refused"],"remediation":{"type":"service_restart","service":"TARGET_SERVICE"},"verification":{"type":"service_active","service":"TARGET_SERVICE"},"action":"auto_recover","priority":10}
That's it. If my service crashes with connection refused, Aegira restarts it and checks it came back. If the check fails, it escalates. No black box.
Deterministic also means auditable. If I'm woken up at 3AM, I can cat /var/log/aegira/incident.log and read, in order: which rule matched, which command ran, which verification passed or failed. There is no "I wonder why it did that."
Safety: The Destructive Command Guard
Running as root means one bad rule can ruin my week. Aegira has a destructive command guard that scans every rule at load time and at execution time.
If a rule contains a command like:
rm -rf /rm -rf /etcdd if=/dev/zero of=/dev/sdamkfs.ext4 /dev/sda1chown -R user /-
shutdown,reboot,halt kill -9 1- fork bombs
…the rule refuses to load. It logs the reason and moves on. I cannot accidentally deploy a rule that nukes my box.
Crucially, this guard is word-aware, not naive substring matching. rm -rf /tmp/aegira-test is allowed. rm -rf / is not. rm -rf /etc is not.
This mattered enough to me that I wrote a dedicated test suite for it — 13 commands that must pass, 16 that must be blocked. If any of those 29 change behavior, the test fails. The guard is a feature, not an afterthought.
Placeholders: One Rule, Many Targets
Rules can use placeholders that get expanded at runtime:
-
{CONTAINER}— the container name from the incident -
{EXIT_CODE}— the exit code (for Docker die events) -
{INCIDENT}— the matched log line -
{SOURCE}—log,docker_exit,docker_health,docker_oom,http_health,container_probe -
{RULE_ID}and{RULE_NAME}— self-reference -
TARGET_CONTAINER— resolves to whichever container is configured as the current target
This means a single built-in rule can drive recovery for any container I point it at. I don't copy-paste the same rule per service.
Dry Run Mode: Trust Before You Automate
Every rule can be set to dry_run:
"action":"dry_run"
In this mode, Aegira logs exactly what it would do — [DRY RUN] Rule 'x' matched. Would execute: systemctl restart nginx — and sends an alert. But it doesn't touch anything.
My own workflow: every new rule runs in dry_run for at least a week. I watch the incident log. I confirm the rule only fires when I want it to. Then I flip it to auto_recover.
Automation you didn't test is automation you're afraid of. I'd rather be bored for a week than surprised at 2AM.
What Aegira Is Not
Honest boundaries, because they matter:
- It's not a metrics platform. It doesn't collect CPU or memory graphs.
- It's not a log aggregator. It watches a log file for error patterns — it doesn't index logs for search.
- It's not a replacement for observability. It's a last-mile tool: once you already know what "broken" looks like, Aegira makes it fix itself.
- It's not AI-driven. Every action traces back to a rule you wrote.
If you need dashboards, root-cause analysis, or tracing, Aegira won't replace those. It fills a specific gap: "I know how to fix this. I just don't want to be the one woken up at 2AM to do it."
Try It
There are two versions:
Free — $0
- 10 built-in recovery rules
- Up to 3 custom rules
- Full recovery engine (log, Docker events, HTTP health, container probes)
- Arbitrary commands:
command,container_exec,container_exec_background,command_sequence - Placeholder expansion
- Dry run mode
- Gmail alerts
- Destructive command guard
- Single node
Pro — $19/month (Coming Soon-Join waitlist from web)
- 40+ built-in recovery rules (extended library — growing toward 100+ with community contributions)
- Unlimited custom rules
- Everything in Free
- Priority support
- Single node
Both run on the same binary, both self-hosted, both deterministic.
I'm a solo developer from Pakistan building this without a team or funding. If the 2AM wakeup problem resonates with you, I'd love to hear your feedback — especially about which recovery patterns you'd want rules for.
Install it. Write one rule. Put it in dry_run. See what Aegira would have done for you last week.
If it catches even one incident you'd have woken up for, it's doing its job.
→ https://aegiralabs-io.github.io/aegira-labs-web/
Built with Rust. Runs on Linux. Watches your services so you don't have to.