<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aegira Labs</title>
    <description>The latest articles on DEV Community by Aegira Labs (@aegira).</description>
    <link>https://dev.to/aegira</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4129552%2F285b8e5a-303d-4714-ae32-934d8a2bd215.jpg</url>
      <title>DEV Community: Aegira Labs</title>
      <link>https://dev.to/aegira</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aegira"/>
    <language>en</language>
    <item>
      <title>Why I Built a Lightweight Rust Watcher for Linux Services &amp; Docker Containers</title>
      <dc:creator>Aegira Labs</dc:creator>
      <pubDate>Thu, 17 Sep 2026 10:55:46 +0000</pubDate>
      <link>https://dev.to/aegira/why-i-built-a-lightweight-rust-watcher-for-linux-services-docker-containers-5ol</link>
      <guid>https://dev.to/aegira/why-i-built-a-lightweight-rust-watcher-for-linux-services-docker-containers-5ol</guid>
      <description>&lt;h1&gt;
  
  
  Aegira: A Lightweight Self-Healing Watcher for Linux &amp;amp; Docker
&lt;/h1&gt;

&lt;p&gt;It's 2AM. Your phone buzzes. A service is down. You drag yourself out of bed, SSH in, &lt;code&gt;systemctl restart&lt;/code&gt;, and go back to sleep. Two hours later — same thing, different container.&lt;/p&gt;

&lt;p&gt;I got tired of it. So I built &lt;strong&gt;Aegira&lt;/strong&gt; — 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.&lt;/p&gt;

&lt;p&gt;This post is about &lt;strong&gt;why&lt;/strong&gt; I built it, &lt;strong&gt;what&lt;/strong&gt; it does, and &lt;strong&gt;how&lt;/strong&gt; it thinks. No fluff.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;I never wanted to &lt;em&gt;know&lt;/em&gt; my service was down at 2AM. I wanted it &lt;em&gt;back up&lt;/em&gt; at 2AM. That's a different problem than monitoring, and it's the one I kept running into.&lt;/p&gt;

&lt;p&gt;Monitoring tells you something broke. It doesn't put it back.&lt;/p&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;p&gt;Four constraints came out of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small&lt;/strong&gt; — one binary. No database, no dashboard, no agent mesh.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic&lt;/strong&gt; — every action decided by a rule &lt;em&gt;I&lt;/em&gt; wrote. No model. No surprises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted&lt;/strong&gt; — talks to nothing outside unless &lt;em&gt;I&lt;/em&gt; tell it to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest&lt;/strong&gt; — if a rule would do something destructive, it refuses to load it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Aegira Actually Does
&lt;/h2&gt;

&lt;p&gt;Aegira is a &lt;strong&gt;watch → match → remediate → verify → alert&lt;/strong&gt; engine.&lt;/p&gt;

&lt;p&gt;It watches four kinds of signals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Log lines&lt;/strong&gt; — tails a log file and matches against rule patterns like &lt;code&gt;"connection refused"&lt;/code&gt; or &lt;code&gt;"Out of memory"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker events&lt;/strong&gt; — subscribes to the Docker event stream for &lt;code&gt;die&lt;/code&gt;, &lt;code&gt;oom&lt;/code&gt;, and &lt;code&gt;health_status:unhealthy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP health checks&lt;/strong&gt; — polls a URL on an interval and compares the status code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container probes&lt;/strong&gt; — runs a command inside a container on an interval (e.g. &lt;code&gt;pg_isready&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a signal matches a rule, Aegira executes the rule's &lt;strong&gt;remediation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;service_restart&lt;/code&gt; — &lt;code&gt;systemctl restart &amp;lt;service&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;container_restart&lt;/code&gt; — &lt;code&gt;docker restart &amp;lt;container&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;command&lt;/code&gt; — run a host command&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;container_exec&lt;/code&gt; — &lt;code&gt;docker exec&lt;/code&gt; a command in a container&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;container_exec_background&lt;/code&gt; — &lt;code&gt;docker exec -d&lt;/code&gt; (detached)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;command_sequence&lt;/code&gt; — a fixed series of commands, each with its own verification&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;alert_only&lt;/code&gt; — don't touch anything, just tell me&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it &lt;strong&gt;verifies&lt;/strong&gt;. 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?&lt;/p&gt;

&lt;p&gt;If verification passes — incident resolved.&lt;br&gt;
If it fails after retries — it escalates to a &lt;strong&gt;manual action&lt;/strong&gt; state and (optionally) sends an alert.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why No AI
&lt;/h2&gt;

&lt;p&gt;This is a deliberate design choice, not a limitation.&lt;/p&gt;

&lt;p&gt;When a rule fires at 2AM, I want to know &lt;strong&gt;exactly&lt;/strong&gt; which command will run on my server. Not "the model decided to restart something." I want a readable JSON file I can &lt;code&gt;cat&lt;/code&gt;, review, and commit to git.&lt;/p&gt;

&lt;p&gt;That's what a rule is in Aegira:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connection_refused"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Connection Refused"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"connection refused"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remediation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service_restart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TARGET_SERVICE"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verification"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service_active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TARGET_SERVICE"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auto_recover"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. If my service crashes with &lt;code&gt;connection refused&lt;/code&gt;, Aegira restarts it and checks it came back. If the check fails, it escalates. No black box.&lt;/p&gt;

&lt;p&gt;Deterministic also means &lt;strong&gt;auditable&lt;/strong&gt;. If I'm woken up at 3AM, I can &lt;code&gt;cat /var/log/aegira/incident.log&lt;/code&gt; and read, in order: which rule matched, which command ran, which verification passed or failed. There is no "I wonder why it did that."&lt;/p&gt;




&lt;h2&gt;
  
  
  Safety: The Destructive Command Guard
&lt;/h2&gt;

&lt;p&gt;Running as root means one bad rule can ruin my week. Aegira has a &lt;strong&gt;destructive command guard&lt;/strong&gt; that scans every rule &lt;em&gt;at load time&lt;/em&gt; and &lt;em&gt;at execution time&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If a rule contains a command like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rm -rf /&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm -rf /etc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dd if=/dev/zero of=/dev/sda&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mkfs.ext4 /dev/sda1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chown -R user /&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shutdown&lt;/code&gt;, &lt;code&gt;reboot&lt;/code&gt;, &lt;code&gt;halt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kill -9 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;fork bombs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…the rule &lt;strong&gt;refuses to load&lt;/strong&gt;. It logs the reason and moves on. I cannot accidentally deploy a rule that nukes my box.&lt;/p&gt;

&lt;p&gt;Crucially, this guard is &lt;strong&gt;word-aware&lt;/strong&gt;, not naive substring matching. &lt;code&gt;rm -rf /tmp/aegira-test&lt;/code&gt; is allowed. &lt;code&gt;rm -rf /&lt;/code&gt; is not. &lt;code&gt;rm -rf /etc&lt;/code&gt; is not.&lt;/p&gt;

&lt;p&gt;This mattered enough to me that I wrote a dedicated test suite for it — 13 commands that &lt;em&gt;must&lt;/em&gt; pass, 16 that &lt;em&gt;must&lt;/em&gt; be blocked. If any of those 29 change behavior, the test fails. The guard is a feature, not an afterthought.&lt;/p&gt;




&lt;h2&gt;
  
  
  Placeholders: One Rule, Many Targets
&lt;/h2&gt;

&lt;p&gt;Rules can use placeholders that get expanded at runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{CONTAINER}&lt;/code&gt; — the container name from the incident&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{EXIT_CODE}&lt;/code&gt; — the exit code (for Docker die events)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{INCIDENT}&lt;/code&gt; — the matched log line&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{SOURCE}&lt;/code&gt; — &lt;code&gt;log&lt;/code&gt;, &lt;code&gt;docker_exit&lt;/code&gt;, &lt;code&gt;docker_health&lt;/code&gt;, &lt;code&gt;docker_oom&lt;/code&gt;, &lt;code&gt;http_health&lt;/code&gt;, &lt;code&gt;container_probe&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{RULE_ID}&lt;/code&gt; and &lt;code&gt;{RULE_NAME}&lt;/code&gt; — self-reference&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TARGET_CONTAINER&lt;/code&gt; — resolves to whichever container is configured as the current target&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means a single built-in rule can drive recovery for &lt;strong&gt;any&lt;/strong&gt; container I point it at. I don't copy-paste the same rule per service.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dry Run Mode: Trust Before You Automate
&lt;/h2&gt;

&lt;p&gt;Every rule can be set to &lt;code&gt;dry_run&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dry_run"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this mode, Aegira logs exactly what it &lt;em&gt;would&lt;/em&gt; do — &lt;code&gt;[DRY RUN] Rule 'x' matched. Would execute: systemctl restart nginx&lt;/code&gt; — and sends an alert. But it doesn't touch anything.&lt;/p&gt;

&lt;p&gt;My own workflow: every new rule runs in &lt;code&gt;dry_run&lt;/code&gt; 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 &lt;code&gt;auto_recover&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Automation you didn't test is automation you're afraid of. I'd rather be bored for a week than surprised at 2AM.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Aegira Is Not
&lt;/h2&gt;

&lt;p&gt;Honest boundaries, because they matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's not a metrics platform.&lt;/strong&gt; It doesn't collect CPU or memory graphs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's not a log aggregator.&lt;/strong&gt; It watches a log file for error patterns — it doesn't index logs for search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's not a replacement for observability.&lt;/strong&gt; It's a &lt;em&gt;last-mile&lt;/em&gt; tool: once you already know what "broken" looks like, Aegira makes it fix itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's not AI-driven.&lt;/strong&gt; Every action traces back to a rule you wrote.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need dashboards, root-cause analysis, or tracing, Aegira won't replace those. It fills a specific gap: &lt;strong&gt;"I know how to fix this. I just don't want to be the one woken up at 2AM to do it."&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;There are two versions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free — $0&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 built-in recovery rules&lt;/li&gt;
&lt;li&gt;Up to 3 custom rules&lt;/li&gt;
&lt;li&gt;Full recovery engine (log, Docker events, HTTP health, container probes)&lt;/li&gt;
&lt;li&gt;Arbitrary commands: &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;container_exec&lt;/code&gt;, &lt;code&gt;container_exec_background&lt;/code&gt;, &lt;code&gt;command_sequence&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Placeholder expansion&lt;/li&gt;
&lt;li&gt;Dry run mode&lt;/li&gt;
&lt;li&gt;Gmail alerts&lt;/li&gt;
&lt;li&gt;Destructive command guard&lt;/li&gt;
&lt;li&gt;Single node&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pro — $19/month (Coming Soon-Join waitlist from web)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;40+ built-in recovery rules (extended library — growing toward 100+ with community contributions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unlimited&lt;/strong&gt; custom rules&lt;/li&gt;
&lt;li&gt;Everything in Free&lt;/li&gt;
&lt;li&gt;Priority support&lt;/li&gt;
&lt;li&gt;Single node&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both run on the same binary, both self-hosted, both deterministic.&lt;/p&gt;




&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Install it. Write one rule. Put it in &lt;code&gt;dry_run&lt;/code&gt;. See what Aegira would have done for you last week.&lt;/p&gt;

&lt;p&gt;If it catches even one incident you'd have woken up for, it's doing its job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://aegiralabs-io.github.io/aegira-labs-web/" rel="noopener noreferrer"&gt;https://aegiralabs-io.github.io/aegira-labs-web/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Rust. Runs on Linux. Watches your services so you don't have to.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>rust</category>
      <category>linux</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
