Skip to content

2025

Traefik v3: Routing Multiple Docker Apps on One Server

Running five Docker apps on one server creates an immediate problem: which port goes to which service, and how do you keep it from becoming a mess as the stack grows? The usual answer is Nginx with manually maintained config blocks. That works until a container restarts with a new IP and your proxy is pointing at nothing.

Traefik solves this by watching the Docker socket directly. When a container starts with the right labels, Traefik builds the route. When it stops, the route disappears. No config reload, no manual upstream blocks, no downtime.

This post covers a production-ready Traefik v3 setup with named entrypoints per service, a locked-down dashboard, and Docker-label-based routing. Every other post in this series builds on this foundation.

Assumed knowledge: Docker Compose basics, what a reverse proxy does.

Traefik v3: Automatic Routing, TLS, and Middleware for Docker

Running multiple Docker apps behind Nginx means writing upstream blocks, running nginx -s reload, and doing it again every time a container restarts with a new IP. Nginx is a file-driven proxy built for static infrastructure. Docker is the opposite: containers die, restart, scale, and get replaced continuously. The mismatch is not subtle, it creates a permanent maintenance loop.

Traefik eliminates that loop. It watches the Docker socket directly. When a container starts with the right labels, Traefik builds the route. When the container stops, the route disappears. No config files per service, no reload commands, no manual certificate management.

This post covers Traefik's architecture, a direct comparison with Nginx and Nginx Proxy Manager, and a complete production setup: static config, Docker Compose, label-based routing, Let's Encrypt TLS, HTTP-to-HTTPS redirect, path-based routing, and dashboard access via SSH tunnel.

Assumed knowledge: Docker Compose basics, what a reverse proxy does.

Self-Hosted Dev Infrastructure with Docker Compose

Most dev setups run a separate Postgres, Redis, and MinIO for every project. Six containers doing the same job, different passwords, none of them shared. This post replaces that with one docker-compose.yml: a shared infra stack on a named Docker network that any app can join without owning or duplicating the services. By the end you'll have Traefik routing traffic, PostgreSQL and Redis running internally, MinIO handling object storage, and Mailpit catching all outbound email. Assumed knowledge: Docker basics and .env files.

Docker on a VPS: UFW, Port Bindings, and SSH Tunnels

Putting a Docker Compose stack on a public VPS without thinking about network exposure is one of the most reliable ways to get your infrastructure compromised. Docker bypasses UFW by default. A 0.0.0.0 port binding on Postgres means your database is reachable from anywhere on the internet, regardless of what your firewall rules say.

This post covers the exact approach used across this series: which ports go on 0.0.0.0, which stay on 127.0.0.1, how UFW and Docker interact at the iptables level, and how to reach locked-down services from your local machine using SSH tunnels. No third-party tools, no complex firewall rules. Just two knobs that actually work.

Assumed knowledge: Linux basics, Docker Compose, what UFW is.

Stateless OTP Login in FastAPI with JWT and Redis

Railway removed passwords entirely. You enter your email, get a six-digit code, and you are in. No password field, no "forgot password" flow, no credential database to rotate when the breach happens. This post implements the same pattern in FastAPI: a stateless OTP login that stores nothing in the database, embeds the code in a signed JWT, and enforces single-use via Redis. The full flow is under 120 lines of application code.

MinIO as a Self-Hosted S3 Backend in Docker

S3 is the de facto standard for object storage, but the AWS dependency and egress costs add up fast. MinIO is an S3-compatible object store you run yourself. Your app code changes nothing: same SDK, same API calls, different endpoint URL. This post covers running MinIO in Docker with a persistent volume, health checks, and a Console UI, then connecting a Python app to it using boto3. Assumed knowledge: Docker Compose basics, basic familiarity with S3 concepts (buckets, objects, access keys).

Mailpit as a Dev SMTP Catch-All Behind Traefik

Testing email flows in development has two failure modes: you disable sending entirely and miss broken templates, or you accidentally send to real users. Mailpit is an SMTP catch-all. It accepts every outbound email your app sends and displays it in a Web UI, without forwarding anything anywhere. This post covers running Mailpit in Docker, routing its Web UI through Traefik on a dedicated port, adding login auth, persisting messages to disk, and wiring it into Django, FastAPI, Node.js, and Laravel apps. Assumed knowledge: Docker Compose basics, what SMTP is.