A git push ships code to production over HTTPS — zero clicks — on a borrowed
two-core laptop, for $0.
No cloud bill. No Ethernet. No UPS. A 2016-era laptop (AMD A4-9125, 2 cores, 3.2 GB RAM) sitting on a shelf, on WiFi only, running a hardened, internet-reachable, auto-deploying, outage-resilient homelab — built to learn DevOps the way it actually breaks in production.
On the identifiers in this repo: every infrastructure detail here — domain names, IP addresses, the tunnel ID, software versions — is genericized by design. This is a sanitized public mirror of private operations docs. Nothing printed here points at a live host.
flowchart LR
dev["git push (main)"] --> gha["GitHub Actions<br/>build + push image"]
gha --> ghcr[("GHCR<br/>private image")]
ghcr -->|pull| coolify["Coolify (self-hosted)<br/>on devops-lab"]
gha -->|deploy webhook<br/>fail-loud| coolify
coolify --> traefik["Traefik<br/>routes by Host header"]
traefik --> tunnel["Cloudflare Tunnel<br/>outbound only, zero inbound ports"]
tunnel --> https["HTTPS<br/>lab-app.example.com"]
gha -.->|if success| discord["Discord #lab-deploys"]
The interesting decisions on this box all trace back to four deliberate limits:
| Constraint | Why it stays | What it forced |
|---|---|---|
| $0 budget | No card on file, ever | Ruled out managed Kubernetes, paid tunnels, cloud load-balancers, object storage — every "just pay for it" escape hatch is closed |
| Borrowed 2-core / 3.2 GB laptop | Not mine to upgrade | Coolify + Traefik + real apps had to fit in 3.2 GB; the box is a runtime, not a builder — CI builds images, the box only pulls and runs |
| WiFi-only | No wired drop where it lives | Drove an automatic WiFi → 4G cellular failover so a power cut doesn't take the site down |
| No UPS | Same reason | Made corruption-surviving backups and self-healing boot non-negotiable, not nice-to-have |
Remove any one of these and it's just another cloud tutorial. Kept in, they turn a shelf laptop into an exercise in operating under real constraints.
| Doc | What it covers |
|---|---|
| docs/architecture.md | The box, the threat model, and the two independent SSH paths — plus the weaknesses documented as lessons |
| docs/cicd-pipeline.md | The push-to-deploy loop, the three-door auth model, and the IPv6 → 404 debugging trace |
| docs/monitoring-alerts.md | Self-hosted uptime monitoring, a daily health digest, and the alert policy that fights fatigue |
| docs/backup-restore.md | A proven restore — and the two backup bugs that only a real restore test could catch |
| docs/resilience-failover.md | Automatic WiFi → 4G failover: how the site stays up through a power cut |
Diagram sources live in diagrams/ as Mermaid (.mmd) and render inline in each doc.
Config is noise; the traces are the signal. If you read nothing else:
-
A healthy container that served 404s. The health check probed
localhost, which resolved to IPv6::1first, but the app bound IPv4 → the container was marked unhealthy → Traefik 404'd it. Fixed at the health-check, then re-fixed through the pipeline rather than by hand. → cicd-pipeline.md -
A backup that restored nothing. A directory tar looked complete, but the control-plane database lived in a named Docker volume outside that directory. Only running an actual restore into a throwaway Postgres exposed the hollow bundle. → backup-restore.md
-
A verification step that lied. The backup checker reported success on a broken pipe (
SIGPIPE) — a green light over a bad backup. Both this and the hollow bundle would have surfaced only during a real disaster. → backup-restore.md
The pipeline isn't a demo — real, public apps ship through it:
- recipe-note — a Bengali
YouTube-to-recipe tool (Google Gemini extracts structured, editable recipes from cooking videos) built for non-technical relatives (live).
Static React client on Vercel; the Express + Puppeteer server is containerized on
devops-labthrough this exact pipeline. - al-Quran (formerly Quran Mazid) — a MERN Quran reader with cookie-based auth; the first stateful, authenticated app onboarded, and the one that corrected the onboarding runbook for every app after it.
Every batch of work on this box followed the same loop:
mental model → architecture sign-off → surgical steps → a gate that proves it.
Hard gates between batches — nothing advances until the previous gate is green (a build is clean, a restore actually restores, an alert actually fires). "An untested backup is not a backup" is the house rule, and it earned its keep twice.
Built by mahmud035 — a self-hosted CI/CD platform on hardware that cost nothing, documented as the operator discipline behind it.