Let’s be honest: I stayed in the “comfort zone” for too long. Nginx Proxy Manager (NPM) was my security blanket—a friendly GUI, a few easy clicks, and a “Success” toast notification that made me feel like I was winning.
But moving toward Cloud Architecture means facing a hard truth: If you’re configuring your infrastructure with a mouse, you’re not building a system; you’re just filling out forms. I was using NPM as a crutch to avoid the perceived complexity of dynamic routing. In the spirit of sharing my progress openly, here is why I finally threw that crutch away and embraced the “Monolith” that is Traefik.
The Problem: The “GUI Trap” and Manual Overhead
Manual intervention is the enemy of scale. My homelab was becoming a sprawl of “just one more manual entry.” Every time I spun up a new container, I had to:
- Leave my terminal (my natural habitat).
- Open a web browser and log into a dashboard.
- Manually map IPs and ports like a 1990s switchboard operator.
- Pray the Let’s Encrypt challenge didn’t timeout because of a UI glitch.
This wasn’t “Infrastructure as Code.” It was “Infrastructure as a Chore.” In a truly resilient system, the entry point should be smart enough to recognize life without being told to look for it. It’s the difference between a ship that needs constant steering and one with a functioning navigation computer.
The Solution: Traefik & The Power of Labels
Traefik is the Monolith from 2001: A Space Odyssey, but actually helpful. It listens to the Docker socket and configures itself dynamically. No database required, no clicking “Save”—just pure, Stoic automation.
I realized that by avoiding Traefik, I was avoiding the very skills required for modern DevOps. Here is the Docker Compose setup that replaced my manual clicking with “Self-Healing” logic.
The Core Config (docker-compose.yml)
YAML
services:
traefik:
image: traefik:v3.0
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false" # Security first!
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
- "--certificatesresolvers.myresolver.acme.email=admin@cavydev.io"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt:/letsencrypt"
# A target service that "self-configures" via Labels
whoami:
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.cavydev.io`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=myresolver"
Why this is a superior architecture:
- Self-Discovery: The moment the container breathes, Traefik sees it via the Docker provider. It’s like an automated nervous system.
- GitOps Ready: My routing logic now lives in Git, not in a hidden SQLite database. If my server burns down, I’m back online in one command.
- Resilience: If a container restarts or shifts, the proxy adapts instantly. No more “502 Bad Gateway” because I forgot to update a static IP.
The Lesson: Shifting to Systems Thinking
Ditching the NPM crutch was a lesson in Growth over Convenience.
- Observability is King: If you can’t see it via logs, it doesn’t exist. Traefik forces you to understand the flow of traffic rather than hiding it behind a pretty “On/Off” switch.
- Eliminating the Human Factor: Humans can be the primary cause of downtime. By reducing the “human clicking buttons” part of the process, the system becomes more robust—it does exactly what the code dictates, every time.
- Facing the Learning Curve: Yes, YAML is less “pretty” than a React dashboard. But in a production-grade Cloud environment, “pretty” doesn’t scale. Automation does.
Status: The crutch is gone. The “Cavy-Cloud” is now running on a dynamic, label-driven autopilot. It’s a bit more advanced than I’m used to, but that’s the point of the journey.


