<?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: Adeolu </title>
    <description>The latest articles on DEV Community by Adeolu  (@adeolu102).</description>
    <link>https://dev.to/adeolu102</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%2F3916643%2Fa64f5e39-d396-4957-9103-13cf50c27649.jpg</url>
      <title>DEV Community: Adeolu </title>
      <link>https://dev.to/adeolu102</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adeolu102"/>
    <language>en</language>
    <item>
      <title>Building SwiftDeploy: A Declarative Infrastructure CLI with Observability and Policy Enforcement</title>
      <dc:creator>Adeolu </dc:creator>
      <pubDate>Wed, 06 May 2026 20:54:03 +0000</pubDate>
      <link>https://dev.to/adeolu102/building-swiftdeploy-a-declarative-infrastructure-cli-with-observability-and-policy-enforcement-4g8o</link>
      <guid>https://dev.to/adeolu102/building-swiftdeploy-a-declarative-infrastructure-cli-with-observability-and-policy-enforcement-4g8o</guid>
      <description>&lt;p&gt;What Is This Project?&lt;/p&gt;

&lt;p&gt;SwiftDeploy is a command-line tool that automatically sets up and manages web application deployments. Instead of manually configuring Docker containers, Nginx, and monitoring, you write one file (&lt;code&gt;manifest.yaml&lt;/code&gt;) that describes what you want, and the tool builds everything for you.&lt;/p&gt;

&lt;p&gt;The project was built in two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stage 4A&lt;/strong&gt;: Basic infrastructure generation and container management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stage 4B&lt;/strong&gt;: Monitoring, policy enforcement, and audit logging&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The Core Idea: Declarative Configuration&lt;/p&gt;

&lt;p&gt;In traditional DevOps, you manually write configuration files for each service. With SwiftDeploy, you write a single manifest file, and the tool generates all the configuration files automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;manifest.yaml&lt;/strong&gt; (the only file you edit manually):&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;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;swiftdeploy-keeds-api:v1.0.0&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;5000&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;api-service&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;nginx&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;nginx:alpine&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;8080&lt;/span&gt;
  &lt;span class="na"&gt;proxy_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;

&lt;span class="na"&gt;network&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;swiftdeploy-net&lt;/span&gt;
  &lt;span class="na"&gt;driver_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this one file, SwiftDeploy generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nginx.conf&lt;/code&gt; (web server configuration)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose.yml&lt;/code&gt; (container orchestration)&lt;/li&gt;
&lt;li&gt;All the settings for monitoring and policy checks&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;How the Tool Works&lt;/p&gt;

&lt;p&gt;The CLI tool (&lt;code&gt;swiftdeploy&lt;/code&gt;) has several commands:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads manifest.yaml and generates nginx.conf + docker-compose.yml&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;validate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks if everything is ready for deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deploy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Starts all containers and waits for them to be healthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;promote canary/stable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switches between stable and canary modes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows a live dashboard with metrics and policy compliance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;audit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generates a report of all events and policy violations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;teardown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stops and removes all containers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;Stage 4A: The Foundation&lt;/p&gt;

&lt;p&gt;API Service&lt;/p&gt;

&lt;p&gt;The API service is a Python Flask application that serves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /&lt;/code&gt; — Welcome message with current mode and version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /healthz&lt;/code&gt; — Health check endpoint&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /chaos&lt;/code&gt; — Simulates problems for testing (only in canary mode)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nginx Proxy&lt;/p&gt;

&lt;p&gt;Nginx acts as a reverse proxy, routing all traffic to the API service. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listens on port 8080 (configurable)&lt;/li&gt;
&lt;li&gt;Forwards requests to the API service&lt;/li&gt;
&lt;li&gt;Returns JSON error responses for 502, 503, 504 errors&lt;/li&gt;
&lt;li&gt;Logs all requests in a specific format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker Compose&lt;/p&gt;

&lt;p&gt;Docker Compose manages all containers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API service (your application)&lt;/li&gt;
&lt;li&gt;Nginx (web server/proxy)&lt;/li&gt;
&lt;li&gt;OPA (policy engine, added in Stage 4B)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Stage 4B: Observability and Policy Enforcement&lt;/p&gt;

&lt;p&gt;The /metrics Endpoint&lt;/p&gt;

&lt;p&gt;The API service now exposes a &lt;code&gt;/metrics&lt;/code&gt; endpoint that reports statistics in Prometheus format:&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;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/healthz"&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;http_request_duration_seconds_bucket&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0.1"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;
&lt;span class="n"&gt;app_uptime_seconds&lt;/span&gt; &lt;span class="mi"&gt;847&lt;/span&gt;
&lt;span class="n"&gt;app_mode&lt;/span&gt; &lt;span class="mi"&gt;0&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;These metrics tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many requests have been made&lt;/li&gt;
&lt;li&gt;How fast responses are&lt;/li&gt;
&lt;li&gt;How long the app has been running&lt;/li&gt;
&lt;li&gt;Whether you're in stable or canary mode&lt;/li&gt;
&lt;li&gt;Whether chaos testing is active&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OPA: The Policy Engine&lt;/p&gt;

&lt;p&gt;OPA (Open Policy Agent) is a separate container that acts like a security guard. Before you can deploy or promote, the CLI asks OPA: "Is it safe?"&lt;/p&gt;

&lt;p&gt;Why use OPA instead of checking directly in the CLI?**&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Policies are separate from code — easier to update&lt;/li&gt;
&lt;li&gt;If OPA crashes, the CLI still works (just warns you)&lt;/li&gt;
&lt;li&gt;OPA is not accessible from the internet (security)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Two Policies&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure Policy&lt;/strong&gt; (checks before deploy):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is there enough disk space? (must be &amp;gt; 10GB)&lt;/li&gt;
&lt;li&gt;Is the CPU overloaded? (must be &amp;lt; 2.0)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Canary Safety Policy&lt;/strong&gt; (checks before promoting to canary):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the error rate too high? (must be &amp;lt; 1%)&lt;/li&gt;
&lt;li&gt;Is the response time too slow? (P99 must be &amp;lt; 500ms)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Data-Driven Thresholds
&lt;/h3&gt;

&lt;p&gt;The actual numbers (10GB, 2.0, 1%, 500ms) are stored in a separate JSON file, not in the policy code. This means you can change the limits without modifying the policy logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;thresholds.json:&lt;/strong&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"infrastructure"&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;"min_disk_gb"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_cpu_load"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.0&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;"canary"&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;"max_error_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_p99_latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&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="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;The Status Dashboard&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;swiftdeploy status&lt;/code&gt; command shows a live dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╔═══════════════════════════════════════╗
║     SwiftDeploy Status Dashboard      ║
╠═══════════════════════════════════════╣
║ Mode: canary                         ║
║ Chaos: none                          ║
║ Req/s: 0.98                          ║
║ P99 Latency: 5ms                     ║
║ Error Rate: 0.00%                    ║
║ Uptime: 133s                         ║
╠═══════════════════════════════════════╣
║ Policy Compliance                    ║
║   Infrastructure: PASS               ║
║   Canary Safety:  PASS               ║
╚═══════════════════════════════════════╝
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time the dashboard refreshes, it saves the data to &lt;code&gt;history.jsonl&lt;/code&gt; for the audit trail.&lt;/p&gt;

&lt;p&gt;The Audit Report&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;swiftdeploy audit&lt;/code&gt; command reads &lt;code&gt;history.jsonl&lt;/code&gt; and generates &lt;code&gt;audit_report.md&lt;/code&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A timeline of all events (mode changes, status updates)&lt;/li&gt;
&lt;li&gt;A list of policy violations (when checks failed)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Bugs We Fixed&lt;/p&gt;

&lt;p&gt;Bug 1: OPA Crashed on Startup&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: OPA wouldn't start because of "conflicting rules" error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;: We wrote &lt;code&gt;default deny := []&lt;/code&gt; in the Rego file, which conflicted with &lt;code&gt;deny contains msg if { ... }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Removed the &lt;code&gt;default deny := []&lt;/code&gt; line. The &lt;code&gt;contains&lt;/code&gt; keyword handles empty sets automatically.&lt;/p&gt;

&lt;p&gt;Bug 2: OPA Couldn't Find Threshold Values&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: OPA loaded the policy files but couldn't find the threshold numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;: The JSON file was in the wrong directory. OPA loads files based on their path structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Moved &lt;code&gt;thresholds.json&lt;/code&gt; into a &lt;code&gt;swiftdeploy/&lt;/code&gt; subdirectory so OPA could find it at the correct data path.&lt;/p&gt;

&lt;p&gt;Bug 3: Status Dashboard Showed "FAIL" Incorrectly&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: The dashboard showed "Infrastructure: FAIL" and "Canary Safety: FAIL" even when everything was within limits.&lt;/p&gt;

&lt;p&gt;Cause: The CLI didn't send a &lt;code&gt;timestamp&lt;/code&gt; field to OPA. The policy rules need &lt;code&gt;input.timestamp&lt;/code&gt; to work. Without it, the rules failed, and the CLI defaulted to "FAIL".&lt;/p&gt;

&lt;p&gt;Fix: Added &lt;code&gt;timestamp&lt;/code&gt; to all OPA queries.&lt;/p&gt;

&lt;p&gt;Bug 4: Nginx Couldn't Find the API Service&lt;/p&gt;

&lt;p&gt;Problem: Nginx returned 502 errors saying it couldn't resolve the API service hostname.&lt;/p&gt;

&lt;p&gt;Cause: Nginx tried to find the API service at startup, but the container wasn't running yet.&lt;/p&gt;

&lt;p&gt;Fix: Added Docker's internal DNS resolver (&lt;code&gt;127.0.0.11&lt;/code&gt;) and used a variable for the proxy address. This tells Nginx to look up the hostname when a request comes in, not at startup.&lt;/p&gt;

&lt;p&gt;Bug 5: Container Didn't Update After Promoting&lt;/p&gt;

&lt;p&gt;Problem: After switching to canary mode, the container was still running in stable mode.&lt;/p&gt;

&lt;p&gt;Cause: Using &lt;code&gt;docker compose restart&lt;/code&gt; doesn't reload environment variables from the updated docker-compose.yml.&lt;/p&gt;

&lt;p&gt;Fix: Changed to &lt;code&gt;docker compose up -d --no-deps &amp;lt;service&amp;gt;&lt;/code&gt;, which recreates the container with new settings.&lt;/p&gt;

&lt;p&gt;Bug 6: Nginx Permission Denied&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Nginx failed to start with "Permission denied" errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;: We set &lt;code&gt;user: nginx&lt;/code&gt; and removed all Linux capabilities, which prevented Nginx from creating necessary directories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Removed the explicit user setting. The official Nginx image handles user switching internally.&lt;/p&gt;




&lt;p&gt;Key Design Decisions&lt;/p&gt;

&lt;p&gt;Why a Separate OPA Container?&lt;/p&gt;

&lt;p&gt;The task required: "The CLI must not make any allow/deny decision itself."&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The CLI asks OPA for permission before every deploy/promote&lt;/li&gt;
&lt;li&gt;OPA returns "allowed" or "denied" with a reason&lt;/li&gt;
&lt;li&gt;The CLI never makes the decision itself&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Policies can be updated without changing the CLI&lt;/li&gt;
&lt;li&gt;If OPA is down, the CLI warns but continues&lt;/li&gt;
&lt;li&gt;All decisions are logged with reasoning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why Data-Driven Thresholds?&lt;/p&gt;

&lt;p&gt;The task required: "Threshold values must not be hardcoded inside Rego files."&lt;/p&gt;

&lt;p&gt;This means the numbers (10GB, 2.0, 1%, 500ms) are in a separate JSON file, not in the policy code. This makes it easy to change limits without touching the policy logic.&lt;/p&gt;

&lt;p&gt;Why Separate Policy Files?&lt;/p&gt;

&lt;p&gt;The task required: "Organise policies by domain. Each domain owns exactly one question."&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;infrastructure.rego&lt;/code&gt; only checks disk and CPU&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;canary.rego&lt;/code&gt; only checks error rate and latency&lt;/li&gt;
&lt;li&gt;Changing one policy never requires changing another&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;How the Pieces Fit Together&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User runs: swiftdeploy deploy
    │
    ▼
CLI gets host stats (disk, CPU)
    │
    ▼
CLI asks OPA: "Is it safe to deploy?"
    │
    ▼
OPA checks infrastructure policy
    │
    ├── If safe → Start containers
    │
    └── If not safe → Block with reason
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User runs: swiftdeploy promote canary
    │
    ▼
CLI scrapes /metrics endpoint
    │
    ▼
CLI calculates error rate and P99 latency
    │
    ▼
CLI asks OPA: "Is it safe to promote?"
    │
    ▼
OPA checks canary safety policy
    │
    ├── If safe → Switch to canary mode
    │
    └── If not safe → Block with reason
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Lessons Learned&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;One file can drive everything**: A single manifest file can generate all the configuration files you need.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Policies should be separate from code**: Using OPA makes policies easier to update and test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always handle failures gracefully**: If OPA is down, the CLI warns but continues working.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simple templates work fine**: You don't need complex template engines for configuration files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Container recreation vs restart**: Restarting a container doesn't reload environment variables. You need to recreate it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker DNS is important**: Nginx needs to know how to find containers by name, which requires Docker's internal DNS resolver.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Summary&lt;/p&gt;

&lt;p&gt;SwiftDeploy is a tool that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Takes a single manifest file as input&lt;/li&gt;
&lt;li&gt;Generates all configuration files automatically&lt;/li&gt;
&lt;li&gt;Manages container lifecycle (deploy, promote, teardown)&lt;/li&gt;
&lt;li&gt;Enforces safety policies via OPA before deploy/promote&lt;/li&gt;
&lt;li&gt;Provides monitoring via /metrics endpoint&lt;/li&gt;
&lt;li&gt;Tracks all events in an audit log&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key innovation is that everything is driven by one file, and all safety checks happen automatically before any deployment action.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article documents the development of SwiftDeploy as part of the HNG Internship DevOps Track, Stage 4.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>beginners</category>
      <category>python</category>
    </item>
  </channel>
</rss>
