<?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>Forem: Nneoma Uwakwe</title>
    <description>The latest articles on Forem by Nneoma Uwakwe (@nneomau).</description>
    <link>https://forem.com/nneomau</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3903128%2F5fdbc17c-c03a-411d-b84a-933410614e15.png</url>
      <title>Forem: Nneoma Uwakwe</title>
      <link>https://forem.com/nneomau</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/nneomau"/>
    <language>en</language>
    <item>
      <title>I Built a Tool That Blocks Bad Deployments (So I Stop Breaking Things at 2AM)</title>
      <dc:creator>Nneoma Uwakwe</dc:creator>
      <pubDate>Wed, 06 May 2026 20:03:52 +0000</pubDate>
      <link>https://forem.com/nneomau/i-built-a-tool-that-blocks-bad-deployments-so-i-stop-breaking-things-at-2am-25ee</link>
      <guid>https://forem.com/nneomau/i-built-a-tool-that-blocks-bad-deployments-so-i-stop-breaking-things-at-2am-25ee</guid>
      <description>&lt;h2&gt;
  
  
  The Honest Truth
&lt;/h2&gt;

&lt;p&gt;I break things. A lot.&lt;/p&gt;

&lt;p&gt;I've deployed code when my server disk was 99% full. I've promoted broken canaries without checking if they were actually working. I've made the same mistakes over and over.&lt;/p&gt;

&lt;p&gt;So I built a tool that literally won't let me be stupid.&lt;/p&gt;

&lt;p&gt;It's called SwiftDeploy. And this is the story of how I built it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Does This Tool Actually Do?
&lt;/h2&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You write &lt;strong&gt;ONE file&lt;/strong&gt; describing your app&lt;/li&gt;
&lt;li&gt;The tool generates &lt;strong&gt;everything else&lt;/strong&gt; (Nginx config, Docker files)&lt;/li&gt;
&lt;li&gt;Before deploying, it &lt;strong&gt;asks permission&lt;/strong&gt; from a policy engine&lt;/li&gt;
&lt;li&gt;If your disk is too full or CPU is too high → &lt;strong&gt;deployment blocked&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If your canary has too many errors → &lt;strong&gt;promotion blocked&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You get a &lt;strong&gt;live dashboard&lt;/strong&gt; showing what's happening&lt;/li&gt;
&lt;li&gt;You get an &lt;strong&gt;audit report&lt;/strong&gt; showing what happened&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Think of it like a security guard at the door who checks your ID before letting you in.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture (Simple Picture)
&lt;/h2&gt;

&lt;p&gt;Think of it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You edit manifest.yaml
        ↓
swiftdeploy CLI reads it
        ↓
    ┌───┼───┐
    ↓   ↓   ↓
nginx  Docker  OPA
.conf  compose (policy engine)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The CLI asks OPA before doing anything important. OPA says YES or NO with a reason. That's it.&lt;/p&gt;


&lt;h2&gt;
  
  
  The One File You Actually Edit
&lt;/h2&gt;

&lt;p&gt;All I ever touch is &lt;code&gt;manifest.yaml&lt;/code&gt;. Everything else is automatic.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;swift-deploy-1&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nneoma-swiftdeploy:latest&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;

&lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8090&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it. Three sentences. The tool handles the rest.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Eyes: Watching Everything
&lt;/h2&gt;

&lt;p&gt;My API now has a &lt;code&gt;/metrics&lt;/code&gt; endpoint. It's like a health tracker for your app.&lt;/p&gt;

&lt;p&gt;It tells me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many people are using my app&lt;/li&gt;
&lt;li&gt;How many errors are happening&lt;/li&gt;
&lt;li&gt;How slow the responses are&lt;/li&gt;
&lt;li&gt;How long the app has been running&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what it actually looks like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="n"&gt;app_uptime_seconds&lt;/span&gt; &lt;span class="mi"&gt;67108&lt;/span&gt;
&lt;span class="n"&gt;app_mode&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;chaos_active&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Boring? Yes. Useful? Absolutely.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Brain: Asking Permission Before Doing Anything Dumb
&lt;/h2&gt;

&lt;p&gt;Here's where it gets clever.&lt;/p&gt;

&lt;p&gt;I added something called Open Policy Agent (OPA). It's just a tiny program that answers one question: &lt;strong&gt;"Is it safe to do this?"&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Before Deploying
&lt;/h3&gt;

&lt;p&gt;I ask OPA: &lt;em&gt;"Hey, is my server healthy enough for a deployment?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I send my disk space, CPU load, and memory.&lt;/p&gt;

&lt;p&gt;OPA checks the rules and says YES or NO. If NO, it tells me WHY.&lt;/p&gt;
&lt;h3&gt;
  
  
  Before Promoting a Canary
&lt;/h3&gt;

&lt;p&gt;I ask a different question: &lt;em&gt;"Is my canary version actually working?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I send the current error rate and how slow the responses are.&lt;/p&gt;

&lt;p&gt;OPA blocks me if errors are over 1% or responses take longer than 500ms.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Rules (Written Like Plain English)
&lt;/h2&gt;

&lt;p&gt;The rules are easy to read. Here's the infrastructure rule:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Allow deployment if:
- Disk space is at least 10GB
- CPU load is under 2.0
- Memory is at least 10% free

If disk is too full, say: "Disk free below minimum"
If CPU is too high, say: "CPU load exceeds maximum"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's the canary rule:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Allow promotion if:
- Error rate is under 1%
- P99 latency is under 500ms

If errors are too high, say: "Error rate exceeds 1%"
If latency is too high, say: "P99 latency too high"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The thresholds aren't buried in code. They live in a separate file. I can change them without touching the rules.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Commands You Actually Type
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate all the config files&lt;/span&gt;
./swiftdeploy init

&lt;span class="c"&gt;# Check if everything is ready&lt;/span&gt;
./swiftdeploy validate

&lt;span class="c"&gt;# Deploy the whole thing&lt;/span&gt;
./swiftdeploy deploy

&lt;span class="c"&gt;# Switch to canary mode (gets checked first)&lt;/span&gt;
./swiftdeploy promote canary

&lt;span class="c"&gt;# Switch back to stable&lt;/span&gt;
./swiftdeploy promote stable

&lt;span class="c"&gt;# See what's happening right now&lt;/span&gt;
./swiftdeploy status

&lt;span class="c"&gt;# Get a report of everything that happened&lt;/span&gt;
./swiftdeploy audit

&lt;span class="c"&gt;# Turn everything off&lt;/span&gt;
./swiftdeploy teardown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Dashboard (What You See When You Run &lt;code&gt;status&lt;/code&gt;)
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;==================================================
SwiftDeploy Status Dashboard
==================================================

[Requests] Total: 22 | Errors: 0 | Error Rate: 0.00%

[Host] Disk: 9.45GB | CPU: 0.27 | Mem: 76.46%

[Infrastructure Policy] ✗ FAIL
    - Disk free (9.5GB) is below minimum (10.0GB)

[Canary Safety Policy] ✓ PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It updates live. I can see exactly which rule is failing and why.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Hard Gate (When I Tried to Break It On Purpose)
&lt;/h2&gt;

&lt;p&gt;I filled up my disk until only 9.45GB was free. Then I tried to deploy:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./swiftdeploy deploy
&lt;span class="go"&gt;
[swiftdeploy] Checking pre-deploy policy...
  Disk: 9.45GB free, CPU: 0.27, Mem: 76.46%
  [BLOCK] Infrastructure policy failed:
    - Disk free (9.5GB) is below minimum (10.0GB)
[swiftdeploy] Deploy blocked by policy.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The deployment was blocked. No damage. No panic. Just a clear message telling me exactly what was wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the whole point.&lt;/strong&gt; The tool won't let me break things.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Isolation Test (Making Sure OPA Is Hidden)
&lt;/h2&gt;

&lt;p&gt;OPA needs to be reachable by my CLI but NOT by the public. I tested it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://34.46.53.225:8090/v1/data
404 Not Found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Public users can't see OPA. No one can query my policies or see my thresholds. That's how it should be.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Audit Report (For When Your Boss Asks "What Happened?")
&lt;/h2&gt;

&lt;p&gt;Running &lt;code&gt;./swiftdeploy audit&lt;/code&gt; gives me a clean markdown file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# SwiftDeploy Audit Report&lt;/span&gt;

Generated: 2026-05-06 18:43:08 UTC

&lt;span class="gu"&gt;## Timeline&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; 2026-05-06T18:27:09Z: deploy (success)
&lt;span class="p"&gt;-&lt;/span&gt; 2026-05-06T18:27:22Z: promote (success)

&lt;span class="gu"&gt;## Policy Violations&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="sb"&gt;`2026-05-06T18:43:08Z`&lt;/span&gt; Infrastructure policy failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now when someone asks "What broke at 3am?" I have an answer.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Chaos Test (Injecting Errors On Purpose)
&lt;/h2&gt;

&lt;p&gt;I added a chaos endpoint for testing. In canary mode, I can make things fail on purpose:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Make every third request fail&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8090/chaos &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"mode": "error", "rate": 0.3}'&lt;/span&gt;

&lt;span class="c"&gt;# Make requests slow (2 second delay)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8090/chaos &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"mode": "slow", "duration": 2}'&lt;/span&gt;

&lt;span class="c"&gt;# Turn chaos off&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8090/chaos &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"mode": "recover"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When I injected errors, the dashboard immediately showed the canary policy failing. Promotion was blocked. Everything worked as expected.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One source of truth saves your sanity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Editing one file is way better than managing five different config files. Nothing gets out of sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep policy separate from code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I can change deployment rules without touching the app. Security can update thresholds. Different environments can have different rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metrics make invisible problems visible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without metrics, I was guessing. With metrics, I know exactly what's happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fail fast. Fail loudly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Blocking a broken deployment with a clear error message is much better than deploying and finding out later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit trails aren't just for compliance.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They're for debugging. When something breaks, I have a complete timeline.&lt;/p&gt;


&lt;h2&gt;
  
  
  How You Can Try This Yourself
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone the repo&lt;/span&gt;
git clone https://github.com/Ada-Mazi/swiftdeploy
&lt;span class="nb"&gt;cd &lt;/span&gt;swiftdeploy

&lt;span class="c"&gt;# Build the app&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; nneoma-swiftdeploy:latest app/

&lt;span class="c"&gt;# Deploy everything&lt;/span&gt;
./swiftdeploy deploy

&lt;span class="c"&gt;# Check if it's working&lt;/span&gt;
curl http://localhost:8090/healthz

&lt;span class="c"&gt;# See the dashboard&lt;/span&gt;
./swiftdeploy status

&lt;span class="c"&gt;# View the metrics&lt;/span&gt;
curl http://localhost:8090/metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Live Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard:&lt;/strong&gt; &lt;a href="http://34.46.53.225:8090" rel="noopener noreferrer"&gt;Check the status&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics:&lt;/strong&gt; &lt;a href="http://34.46.53.225:8090/metrics" rel="noopener noreferrer"&gt;View the raw metrics&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Ada-Mazi" rel="noopener noreferrer"&gt;
        Ada-Mazi
      &lt;/a&gt; / &lt;a href="https://github.com/Ada-Mazi/swiftdeploy" rel="noopener noreferrer"&gt;
        swiftdeploy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SwiftDeploy&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A declarative CLI tool that generates Nginx and Docker Compose configs from a single manifest.yaml and manages the full container lifecycle.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Docker installed&lt;/li&gt;
&lt;li&gt;Python 3.10+&lt;/li&gt;
&lt;li&gt;jinja2 and pyyaml installed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install dependencies:&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;pip3 install jinja2 pyyaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;git clone https://github.com/Ada-Mazi/swiftdeploy
cd swiftdeploy
pip3 install jinja2 pyyaml
docker build -t swift-deploy-1-node:latest app/
./swiftdeploy deploy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Subcommands&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;init&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Parses manifest.yaml and generates nginx.conf and docker-compose.yml&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;./swiftdeploy init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;validate&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Runs 5 pre-flight checks&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;./swiftdeploy validate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Checks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;manifest.yaml exists and is valid YAML&lt;/li&gt;
&lt;li&gt;All required fields present and non-empty&lt;/li&gt;
&lt;li&gt;Docker image exists locally&lt;/li&gt;
&lt;li&gt;Nginx port is not already bound&lt;/li&gt;
&lt;li&gt;Generated nginx.conf is syntactically valid&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;deploy&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Builds image, starts stack, waits for health checks&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;./swiftdeploy deploy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;promote&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Switches mode with rolling restart&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;./swiftdeploy promote canary
./swiftdeploy promote stable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;teardown&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Removes all containers, networks, volumes&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;./swiftdeploy teardown
./swiftdeploy teardown --clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;API Endpoints&lt;/h2&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;GET /        welcome message with mode, version, timestamp&lt;/li&gt;
&lt;li&gt;GET /healthz liveness check with…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Ada-Mazi/swiftdeploy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building this was hard. But now I have a tool that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates everything from one file&lt;/li&gt;
&lt;li&gt;Watches my metrics&lt;/li&gt;
&lt;li&gt;Blocks bad deployments&lt;/li&gt;
&lt;li&gt;Shows me a live dashboard&lt;/li&gt;
&lt;li&gt;Gives me an audit trail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And most importantly, it stops me from breaking things at 2am.&lt;/p&gt;

&lt;p&gt;That's a win.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Ada-Mazi/swiftdeploy" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Star SwiftDeploy on GitHub 🚀&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;what is your 2AM depolyment horror story?&lt;/p&gt;

</description>
      <category>devops</category>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built a System That Catches Hackers in Real Time</title>
      <dc:creator>Nneoma Uwakwe</dc:creator>
      <pubDate>Tue, 28 Apr 2026 21:12:43 +0000</pubDate>
      <link>https://forem.com/nneomau/how-i-built-a-real-time-ddos-detection-engine-from-scratch-beginners-guide-2mde</link>
      <guid>https://forem.com/nneomau/how-i-built-a-real-time-ddos-detection-engine-from-scratch-beginners-guide-2mde</guid>
      <description>&lt;p&gt;I Built a System That Catches Hackers in Real Time&lt;/p&gt;

&lt;p&gt;​The Problem&lt;/p&gt;

&lt;p&gt;​It was a regular Tuesday morning at cloud.ng, a rapidly growing cloud storage company powered by Nextcloud. Thousands of users were uploading files, sharing documents, and going about their day.&lt;/p&gt;

&lt;p&gt;​Then the alerts started firing.&lt;/p&gt;

&lt;p&gt;​Someone was hammering the server with thousands of requests per second. Not a real user. An attacker. A bot. Something trying to take the platform down. This is called a DDoS attack (Distributed Denial of Service). The goal is simple: flood a server with so much traffic that real users cannot get through.&lt;/p&gt;

&lt;p&gt;​My job as the DevSecOps engineer? &lt;br&gt;
Build something that catches it before it becomes a problem. Here is exactly how I did it—explained so simply that even if you have never written a line of Python before, you will understand every part.&lt;/p&gt;

&lt;p&gt;​Live dashboard: &lt;a href="http://hngdevops.mooo.com" rel="noopener noreferrer"&gt;http://hngdevops.mooo.com&lt;/a&gt;&lt;br&gt;
​GitHub: &lt;a href="https://github.com/Ada-Mazi/hng-anomaly-detector" rel="noopener noreferrer"&gt;https://github.com/Ada-Mazi/hng-anomaly-detector&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​What I Built&lt;br&gt;
​A daemon a program that runs silently in the background 24/7 that does four things&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;1.Watches every single HTTP request hitting the server.&lt;/p&gt;

&lt;p&gt;​2.Learns what normal traffic looks like.&lt;/p&gt;

&lt;p&gt;​3.Detects when something looks wrong.&lt;/p&gt;

&lt;p&gt;​4Blocks the attacker automatically and sends me a Slack alert.&lt;/p&gt;

&lt;p&gt;​No human needed. No button to press. Fully automatic.&lt;/p&gt;

&lt;p&gt;​The Tech Stack&lt;/p&gt;

&lt;p&gt;​Python: Easy to read, great for math and data processing.&lt;/p&gt;

&lt;p&gt;​Nginx: Sits in front of the app and logs every request.&lt;/p&gt;

&lt;p&gt;​Docker: Packages everything into containers that run anywhere.&lt;/p&gt;

&lt;p&gt;​iptables: The Linux firewall that actually blocks bad IPs.&lt;/p&gt;

&lt;p&gt;​Flask: Serves the live monitoring dashboard.&lt;/p&gt;

&lt;p&gt;​Slack: Sends instant notifications when something happens.&lt;/p&gt;

&lt;p&gt;​Part 1 — Watching Every Request&lt;/p&gt;

&lt;p&gt;​The first challenge was reading Nginx logs in real time. Nginx writes one line to a log file for every HTTP request. My detector reads that file continuously, line by line, as new lines appear. This is called tailing a log file.&lt;/p&gt;

&lt;p&gt;​I configured Nginx to write logs in JSON format, so they are easy to parse: &lt;/p&gt;

&lt;p&gt;Every line gives me the IP address, timestamp, and status code. My detector reads this and builds a picture of who is doing what.&lt;/p&gt;

&lt;p&gt;​Part 2 — The Sliding Window (The Secret Sauce)&lt;/p&gt;

&lt;p&gt;​I need to know: how many requests did this IP send in the LAST 60 seconds?&lt;/p&gt;

&lt;p&gt;​The naive approach would be to count requests per minute and reset the counter every 60 seconds. But if an attacker sends 1000 requests in the last 10 seconds of one minute and 1000 in the first 10 seconds of the next, your counter sees two batches of 1000 rather than one burst of 2000.&lt;/p&gt;

&lt;p&gt;​My solution: a sliding window using a deque.&lt;/p&gt;

&lt;p&gt;​A deque is a double ended queue. Think of it like a train new passengers board at the back, and old ones exit from the front.&lt;/p&gt;

&lt;p&gt;No resets. No boundaries. Just a smooth, always-accurate picture of the last 60 seconds.&lt;/p&gt;

&lt;p&gt;​Part 3 -The Baseline (Teaching the System Normal)&lt;/p&gt;

&lt;p&gt;​I cannot hardcode what counts as "too many" requests because normal traffic varies wildly. At 3 AM, 10 requests/min might be high. At 3 PM, 10,000 might be normal.&lt;/p&gt;

&lt;p&gt;​The system learns. Every second, I record how many requests arrived. I keep a rolling 30-minute window of these counts and calculate:&lt;/p&gt;

&lt;p&gt;​Mean: The average requests per second.&lt;/p&gt;

&lt;p&gt;​Standard Deviation: How much the traffic typically varies.&lt;/p&gt;

&lt;p&gt;​The system automatically adjusted its understanding of normal based on real-world patterns.&lt;/p&gt;

&lt;p&gt;​Part 4 Catching the Attacker (Z-Score Detection)&lt;/p&gt;

&lt;p&gt;​I use the baseline to calculate a Z-score:The Z-score tells me: how many standard deviations above normal is this rate?&lt;/p&gt;

&lt;p&gt;​Z-score 1.0: Slightly above average.&lt;/p&gt;

&lt;p&gt;​Z-score 3.0: Extremely unusual.&lt;/p&gt;

&lt;p&gt;​Z-score 4.0+: BANNED.&lt;/p&gt;

&lt;p&gt;​Part 5 Blocking the Attacker with iptables&lt;/p&gt;

&lt;p&gt;​Once flagged, the system runs one Linux command:&lt;/p&gt;

&lt;p&gt;​iptables -I INPUT -s [ATTACKER_IP] j DROP&lt;/p&gt;

&lt;p&gt;​I INPUT: Inserts the rule at the top.&lt;/p&gt;

&lt;p&gt;​s: Matches the source IP.&lt;/p&gt;

&lt;p&gt;​j DROP: Silently discards the packets. The attacker gets nothing back.&lt;/p&gt;

&lt;p&gt;​The ban uses a backoff schedule:&lt;/p&gt;

&lt;p&gt;​1st ban: 10 minutes&lt;br&gt;
​2nd ban: 30 minutes&lt;br&gt;
​3rd ban: 2 hours&lt;br&gt;
​4th+ ban: Permanent&lt;/p&gt;

&lt;p&gt;​Part 6 — The Slack Alerts &amp;amp; Audit Log&lt;/p&gt;

&lt;p&gt;​Every event sends a Slack message within seconds. Whether it's an IP BANNED, a GLOBAL SPIKE, or an AUTO-UNBAN, I am always in the loop.&lt;/p&gt;

&lt;p&gt;​Everything is also recorded in a structured Audit Log for forensic analysis:&lt;br&gt;
[2026-04-28T20:12:08Z] BAN ip=34.31.206.191 | condition=zscore=4.0 | rate=3 | baseline=1.0 | duration=600&lt;/p&gt;

&lt;p&gt;​Part 7 — The Live Dashboard&lt;/p&gt;

&lt;p&gt;​I built a web dashboard that refreshes every 3 seconds showing:&lt;/p&gt;

&lt;p&gt;​Global requests per second.&lt;/p&gt;

&lt;p&gt;​Banned IPs and countdowns.&lt;/p&gt;

&lt;p&gt;​Top 10 source IPs.&lt;/p&gt;

&lt;p&gt;​System health (CPU/Memory).&lt;/p&gt;

&lt;p&gt;​How to Run This Yourself&lt;br&gt;
​You need a Linux VPS with at least 2 vCPU and 2GB RAM.&lt;/p&gt;

&lt;p&gt;What I Learned&lt;/p&gt;

&lt;p&gt;​Security is not about walls; it is about awareness. The moment you start measuring and understanding your normal traffic, anomalies become obvious.&lt;/p&gt;

&lt;p&gt;​This project was built as part of HNG Internship Stage 3. It was the hardest thing I have built so far, and I am proud of every line of it.&lt;/p&gt;

&lt;p&gt;GitHub: Ada-Mazi&lt;br&gt;
Dashboard: hngdevops.mooo.com&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>python</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
