Networking & Routing Roots
Every Mezzanine hostname resolves through the same chain — DNS provider → A record → server public IP → reverse proxy → app container → TLS — but the implementation of each hop differs per box. This page traces all of them.
One invariant holds everywhere: only the reverse proxy (mezzanine-nginx, mezzanine-traefik, or system nginx) publishes ports 80/443 to the host. Every app container — the Next.js frontend on 3000, the Express backend on 3001, the control plane on 4000 — is reachable only on its box’s internal Docker network. Nothing talks to the backend except the proxy in front of it.
The routing roots at a glance
Three public IPs, three reverse-proxy strategies, two certificate mechanisms.
graph LR
subgraph "DNS providers"
CF["Cloudflare zone mezzanine.cloud"]
HOST["Hostinger zone myaccess.cloud"]
WIX["Wix nameservers myaccessio.com"]
end
subgraph "App box 5.189.154.35"
NGX["mezzanine-nginx (80 / 443) certbot"]
FE1["mezzanine-frontend (3000 internal)"]
BE1["mezzanine-backend (3001 internal)"]
STATIC["static files in /var/www"]
end
subgraph "SaaS box 62.171.156.201"
TRAEFIK["mezzanine-traefik (80 / 443) certresolver cf"]
CP["mezzanine-control-plane (4000 internal)"]
TEN["mz-slug-frontend (3000 internal)"]
end
subgraph "Development box 192.210.241.34"
SYSNGX["system nginx (80 / 443) certbot"]
INV["investors container (8090)"]
end
CF -->|"app.mezzanine.cloud"| NGX
CF -->|"docs / www / apex"| NGX
CF -->|"try.mezzanine.cloud"| TRAEFIK
CF -->|"slug.mezzanine.cloud"| TRAEFIK
HOST -->|"investors.myaccess.cloud"| SYSNGX
WIX -.->|"not via panel"| WIX
NGX -->|"proxy /"| FE1
NGX -->|"proxy /api and ws"| BE1
NGX -->|"serve"| STATIC
TRAEFIK -->|"route by label"| TEN
TRAEFIK -->|"signup"| CP
SYSNGX -->|"proxy"| INV
Domain → box → proxy → SSL → DNS host
The authoritative mapping. Use it when you need to know where a hostname actually terminates.
| Domain | Box (public IP) | Reverse proxy | Backing target | SSL method | DNS host |
|---|---|---|---|---|---|
app.mezzanine.cloud | App box (5.189.154.35) | mezzanine-nginx | frontend:3000 (/) + backend:3001 (/api, Socket.IO) | certbot / Let’s Encrypt (host, mounted into nginx) | Cloudflare |
mezzanine.cloud (apex) | App box (5.189.154.35) | mezzanine-nginx | static /var/www/mezzanine.cloud | certbot / Let’s Encrypt (host, mounted into nginx) | Cloudflare |
www.mezzanine.cloud | App box (5.189.154.35) | mezzanine-nginx | static /var/www/mezzanine.cloud | certbot / Let’s Encrypt (host, mounted into nginx) | Cloudflare |
docs.mezzanine.cloud | App box (5.189.154.35) | mezzanine-nginx | static /var/www/docs.mezzanine.cloud (this site) | certbot / Let’s Encrypt (host, mounted into nginx) | Cloudflare |
try.mezzanine.cloud | SaaS box (62.171.156.201) | mezzanine-traefik | control-plane :4000 (signup) | traefik certresolver=cf (DNS-01 wildcard) | Cloudflare |
<tenant>.mezzanine.cloud | SaaS box (62.171.156.201) | mezzanine-traefik | mz-<slug>-frontend:3000 (via label) | traefik certresolver=cf (DNS-01 wildcard) | Cloudflare |
investors.myaccess.cloud | Development box (192.210.241.34) | system nginx | investors container :8090 | certbot / Let’s Encrypt (host) | Hostinger |
One request, end to end
A browser hitting app.mezzanine.cloud/api/... resolves through Cloudflare, terminates TLS at the App box’s nginx, and is split by path: the UI to the Next.js frontend on the internal 3000, the API and the Socket.IO upgrade to the Express backend on the internal 3001. Neither app port is published to the host — nginx is the only way in.
sequenceDiagram
participant U as "Browser"
participant D as "Cloudflare DNS"
participant N as "mezzanine-nginx (443)"
participant F as "frontend (3000 internal)"
participant B as "backend (3001 internal)"
U->>D: "resolve app.mezzanine.cloud"
D-->>U: "A record 5.189.154.35"
U->>N: "HTTPS GET (TLS via certbot cert)"
alt "path is slash api or websocket"
N->>B: "proxy on docker net to 3001"
B-->>N: "JSON or Socket.IO upgrade"
else "any other path"
N->>F: "proxy on docker net to 3000"
F-->>N: "rendered HTML or assets"
end
N-->>U: "HTTPS response"
The SaaS path is the same shape with two differences: DNS still resolves at Cloudflare, but to 62.171.156.201, and TLS terminates at traefik, which reads the Host header and matches the per-tenant router rule Host(\to forward to that tenant'smz-(and from there to itsmz-, again only on the internal mezzanine-tenantsnetwork).try.mezzanine.cloudfollows the same traefik entry but routes to the control plane on:4000`.
How traefik knows where a tenant lives
On the SaaS box there is no per-host nginx vhost file. Routing is entirely label-driven: when the control-plane provisioner creates a tenant, it stamps the tenant’s frontend container with labels that traefik reads live.
graph TD PROV["control-plane provisioner"] -->|"creates container with labels"| LBL["mz-slug-frontend labels"] LBL --> R1["traefik.enable=true"] LBL --> R2["router rule Host slug.mezzanine.cloud"] LBL --> R3["entrypoint websecure"] LBL --> R4["tls.certresolver=cf"] LBL --> R5["loadbalancer.server.port=3000"] LBL --> R6["mezzanine.tenant=slug"] R1 --> T["mezzanine-traefik picks up route"] R2 --> T R3 --> T R4 --> T R5 --> T T -->|"serves slug.mezzanine.cloud"| OUT["tenant frontend 3000"]
Cross-references
- App box internals and its nginx vhosts: App box guide
- SaaS box, traefik labels, and tenant lifecycle: SaaS box guide
- Full architecture overview: Architecture