SwiftDeploy: How I Built a CLI That Writes Its Own Infrastructure, Gates Deployments with OPA, and Audits Everything
HNG Internship 14—DevOps Track, Stage 4A & 4B Introduction Part 1 — The Design: A Tool That Writes Its Own Config nginx: opa: network: listen 8080; Part 2 — The API Service http://localhost:8080/chaos \ curl -X POST http://localhost:8080/chaos \ curl -X POST http://localhost:8080/chaos \ Part 3 — The Guardrails: OPA Policy Enforcement Policies can be updated without touching the CLI Policy Structure import rego.v1 default allow := false allow if { violations contains msg if { violations contains msg if { import rego.v1 default allow := false allow if { violations contains msg if { violations contains msg if { ╔══ POLICY VIOLATION ══════════════════════════════╗ http://swiftdeploy-opa:8181/v1/data/swiftdeploy/infrastructure/decision" \ Part 4 — The Chaos Experiment http://localhost:8080/chaos \ Policy Compliance ╔══ POLICY VIOLATION ══════════════════════════════╗ haviour — the policy engine prevents a broken canary from being promoted to production. http://localhost:8080/chaos -d '{"mode":"recover"}' Part 5 — Observability: Status Dashboard and Audit Trail Policy Compliance History log history.jsonl 2026-05-06T20:15:30Z | policy_pass | domain=infrastructure | 2026-05-06T20:15:31Z | deploy_success | mode=stable | 2026-05-06T20:16:49Z | mode_change | from=stable, to=canary | Timestamp Event Data 2026-05-06T08:35:31Z deploy_blocked disk_free_gb=58.08 Metric Value Total scrapes 31 Avg req/s 6.74 Avg error rate 0.0000% Max P99 latency 10ms The report renders as clean GitHub Flavored Markdown and provides a complete audit trail of every deployment, mode change, policy decision, and chaos event. Part 6 — Lessons Learned Windows Git Bash Mangles Unix Paths Running docker exec container /opa eval from Git Bash on Windows translates /opa to C:/Program Files/Git/opa. The fix is MSYS_NO_PATHCONV=1: bashMSYS_NO_PATHCONV=1 docker exec swiftdeploy-opa /opa eval "data" --data /policies This was one of the most time-consuming bugs — completely invisible until you check the actual error message from Docker. The OPA Image Has No Shell, No curl, No wget The openpolicyagent/opa:latest image contains only the opa binary. No sh, no curl, no wget. This means: Healthchecks can't use CMD-SHELL CRLF Line Endings Break Bash Scripts on Linux Files edited on Windows have \r\n line endings. When bash on Linux/WSL reads them, the \r becomes part of variable values, causing cryptic failures. Always run: bashsed -i 's/\r//' swiftdeploy sed -i 's/\r//' templates/.tmpl sed -i 's/\r//' policies/.rego Port Binding Only Applies at Container Creation Docker applies port bindings when a container is created, not when it starts or restarts. docker compose restart does not re-apply port mappings. You need docker compose up --force-recreate to pick up port changes. OPA Rego v1 Requires Explicit if Keywords The latest OPA image enforces Rego v1 syntax, which requires import rego.v1 and explicit if keywords on every rule body. The older future.keywords import approach no longer works cleanly with the latest image. Conclusion Declarative infrastructure — one manifest drives everything The full source code is available at github.com/travispocr/hng14-stage-4a. Published as part of HNG Internship 14 — DevOps Track Author: travispocr | hngstage4b
