Sling
Zero-Downtime Blue/Green Deploy Orchestrator for a Single VPS
Highlights
- Push a commit and it goes live with zero downtime, or rolls itself back
- Health gate: 3 consecutive healthy /health probes before a container sees traffic
- Post-switch probe requires X-Sling-Upstream to name the new container, not just any 200
- 30s grace window with automatic rollback after 3 failed probes in a row
- HMAC-signed webhooks with a latest-push-wins queue (one running, one waiting)
System Overview
Sling is a small self-hosted deploy orchestrator written in Go. Given a git repo and an exact 40-character commit SHA, it builds a Docker image, starts the new version beside the old one, proves it is healthy, atomically switches a Caddy reverse proxy over to it, and keeps watching. If anything fails along the way, traffic goes back to the previous version.
The Problem & Architectural Rationale
Deploying to your own server usually means SSH, git pull, docker compose up --build, and hope. The old container stops before the new one is ready (downtime), nothing verifies the new release before users hit it, rollbacks are manual and stressful, and moving branch names mean the server can end up on a different commit than intended. Hosted PaaS platforms solve this, but cost money and run your app on their infrastructure.
Architecture & Components
1. Build: exact commit pinning
Only full 40-hex SHAs are accepted. After git checkout --detach, Sling compares git rev-parse HEAD with the requested SHA, then builds the image paas-app:<sha>. Building has no effect on users; redeploying the commit that is already live is refused before any container is touched.
2. Release: health gate before traffic
The candidate container runs beside the live one on sling-net with 256 MB / 0.5 CPU limits and a loopback-only port. It must return 3 healthy /health responses in a row directly; a crash on boot fails the gate immediately, so a broken build never gets traffic.
3. Atomic Caddy cutover & verification
The upstream is swapped in one step through Caddy's admin API (POST /load). Sling then probes through the proxy and requires the X-Sling-Upstream header to name the new container, proving the new version is the one actually answering.
4. Grace window & automatic rollback
For 30 seconds after the switch, Sling keeps probing. Three failures in a row point the proxy back at the previous version. The old container is stopped gracefully (SIGTERM, 10s drain) and its image is kept, so sling rollback can restore it in seconds with no rebuild.
5. Webhook server & deploy queue
sling serve verifies HMAC-SHA256 signatures with constant-time comparison, checks repo and branch, and takes the clone URL from its own config, never the payload. One worker, one waiting slot: a newer push replaces the waiting one, and an in-flight deploy always finishes, even during shutdown.
6. History, locking & HTTPS
An in-process mutex plus a cross-process file lock allows one deploy per host. Deploy history persists in SQLite, with crash detection that marks deploys from dead processes as interrupted. With SLING_DOMAIN set, Caddy obtains and renews Let's Encrypt certificates and forwards only /webhook to Sling.
Core Implementation
The deploy pipeline: build and release joined only by an image tag
Outcomes & Results
- A crash-on-boot commit fails the health gate and never receives traffic; v1 keeps serving.
- A release whose health starts failing after cutover is rolled back automatically within the grace window.
- Rollbacks reuse the kept image and pass through every health check, with no rebuild.
- A config mistake can't silently reconfigure a live site: mismatched proxy settings are refused before anything changes.