generated from atu/saiop-infrastructure
87 lines
4.3 KiB
Markdown
87 lines
4.3 KiB
Markdown
---
|
|
name: saiop-webapp-behind-traefik
|
|
description: "Deploy a self-hosted web app on the SAIOP stack behind Traefik + Cloudflare, avoiding the recurring pitfalls hit deploying Portainer, NetBox/Neo4j, and Obsidian."
|
|
version: 1.0.0
|
|
author: Claude Code (SAIOP ops session)
|
|
license: MIT
|
|
platforms: [linux]
|
|
prerequisites:
|
|
env_vars: []
|
|
commands: [docker, curl, dig]
|
|
metadata:
|
|
hermes:
|
|
tags: [SAIOP, Traefik, Cloudflare, Docker, deployment, infrastructure]
|
|
---
|
|
|
|
# Deploying a web app behind Traefik + Cloudflare on SAIOP
|
|
|
|
Checklist distilled from real incidents during the Portainer, NetBox/Neo4j, and
|
|
Obsidian deployments on this stack. Each item below caused a real outage or a
|
|
multi-hour debugging session the first time; check all of them up front.
|
|
|
|
## When to use
|
|
|
|
Any time a new container needs a public `*.atu.edu.gh` hostname through the
|
|
existing Traefik + Cloudflare setup on the `saiops` VM.
|
|
|
|
## Checklist
|
|
|
|
1. **Never publish a host port directly** (`ports: ["8080:8080"]` on the app
|
|
container). Docker's own iptables rules for published ports bypass UFW
|
|
entirely — confirmed twice (Portainer, Gitea's SSH port). Always route
|
|
through Traefik labels instead; no `ports:` block on the app service.
|
|
|
|
2. **Check for pre-provisioned secrets/DB before building from scratch.**
|
|
This stack's `.env.master` and shared Postgres instance often already have
|
|
a database/role and secrets created for a planned-but-not-yet-deployed
|
|
service (confirmed for NetBox, n8n). `grep` `.env.master` for the
|
|
service's name and `\l` the shared Postgres instance first.
|
|
|
|
3. **Check container UID before mounting host directories.** Don't assume
|
|
`aiadmin`'s UID. `docker run --rm --entrypoint id <image>` first. Official
|
|
Prometheus-ecosystem images often run as `nobody` (65534); LinuxServer.io
|
|
images use `PUID`/`PGID` env vars — set them to match the actual owning
|
|
user (this stack: `aiadmin` = 1001), not the LSIO default.
|
|
|
|
4. **Raw TCP protocols (anything not plain HTTP/S) cannot go through
|
|
Cloudflare's free proxy.** If a service needs a non-HTTP port (e.g. Neo4j
|
|
Bolt), either: (a) keep that one DNS record DNS-only and the main
|
|
hostname proxied (split-hostname pattern — see NetBox/Neo4j skill), or
|
|
(b) don't expose it publicly at all if it's only needed internally.
|
|
|
|
5. **Switching a previously-proxied hostname to DNS-only removes Cloudflare's
|
|
CDN caching for that whole hostname, not just whatever you needed
|
|
unblocked.** If large static assets (JS/CSS bundles) suddenly take 10-20s
|
|
to load after such a switch, that's why — it's not a Traefik/app bug.
|
|
Either keep the asset-serving hostname proxied and split out only the
|
|
raw-protocol piece to a second hostname, or override `Cache-Control` via
|
|
a Traefik `customResponseHeaders` middleware so Cloudflare can still cache
|
|
despite an app-set `no-store` header.
|
|
|
|
6. **Self-hosted web-app bundles sometimes ship a CSP too strict for their
|
|
own JS** (seen with Neo4j Browser needing `unsafe-eval`). If a page loads
|
|
but renders blank with no obvious network error, check the browser
|
|
console for CSP violations before assuming a deeper bug.
|
|
|
|
7. **Browsers require encrypted WebSocket (`wss://`) from an HTTPS page** —
|
|
mixed-content policy blocks plaintext `ws://` even to a different host.
|
|
If a backend protocol needs browser access over WebSocket (Neo4j Bolt,
|
|
etc.), terminate TLS on that port at Traefik too (`tls=true` +
|
|
`certresolver=letsencrypt` on the TCP router), not just the main HTTP
|
|
route.
|
|
|
|
8. **When debugging "works on the server, fails for the client," isolate by
|
|
distance before assuming the server is wrong:** test container-direct →
|
|
through-proxy-on-localhost → through-proxy-externally. Every step here
|
|
should be proven correct before trusting the next. Don't skip straight to
|
|
blaming the client/network — but don't keep patching server config either
|
|
once every layer up to "through-proxy-externally" has been proven clean.
|
|
|
|
## Don't do this
|
|
|
|
- Don't add a host port publish "just to test" — it's the single most common
|
|
way a new service silently bypasses the firewall on this box.
|
|
- Don't assume a slow page load is a config bug before checking your own
|
|
test connection's raw bandwidth against a known-good CDN endpoint
|
|
(`curl https://speed.cloudflare.com/__down?bytes=2000000`).
|