Compare commits

6 Commits

Author SHA1 Message Date
2d7c788202 Revert Ghost router split 2025-11-13 11:44:10 +08:00
f52df70343 Point Ghost router to docker service 2025-11-13 11:08:30 +08:00
6388eed264 Move Ghost router to file provider 2025-11-13 10:57:54 +08:00
d436ed1cf4 Rewrite README and finalize template flow 2025-11-13 01:53:26 +08:00
56055187f8 Keep secrets out of repo 2025-11-13 01:44:01 +08:00
f8e38599b0 Parameterize sensitive settings 2025-11-13 01:39:41 +08:00
5 changed files with 77 additions and 86 deletions

3
.gitignore vendored
View File

@@ -22,5 +22,8 @@ node_modules/
.env .env
.env.* .env.*
# Ignore generated secrets
dynamic.d/middlewares/crowdsec.yml
# Ignore backup files # Ignore backup files
*.~* *.~*

132
README.md
View File

@@ -1,105 +1,75 @@
# GB Traefik Setup # Traefik Edge Stack
This repository contains the configuration files and setup instructions for deploying [Traefik](https://traefik.io/), a modern reverse proxy and load balancer. Reverse proxy for everything on this host. The goal: keep Cloudflare in front, expose a private LAN entrypoint, and let Docker stacks self-register through labels without leaking secrets.
Configuration files is customized for Gbanyan personal usage. ## Architecture Snapshot
## Prerequisites - **Static config (`traefik.yml`)**
- EntryPoints: `web/websecure` on `10.0.0.225`, `internal_web/internal_websecure` on `192.168.50.4`.
- Trusted IP lists are managed by `scripts/update_cloudflare_ips.py`.
- Docker provider is discovery-only; every container opts in with labels.
- File provider loads everything in `dynamic.d/`.
- Docker installed on your system - **Dynamic config (`dynamic.d/`)**
- Docker Compose (if using `docker-compose.yml`) - `middlewares/` retry, compression, CrowdSec (rendered from template).
- `transports/fast-upstreams.yml` shared connection pool tuning.
- `routers/` internal-only routers (public ones stay in labels).
## Getting Started - **Host networking**
Traefik runs with `network_mode: host` so it can bind to both IPs simultaneously. Switching to bridge mode would require duplicating Traefik or adding another L4 hop, so host mode stays.
1. Clone this repository: ## Secrets Workflow
1. Copy `.env.example``.env` and fill:
- `CLOUDFLARE_EMAIL`, `CLOUDFLARE_DNS_API_TOKEN`
- `CROWDSEC_LAPI_KEY`
2. Render secret-aware dynamic files:
```bash ```bash
git clone https://gitea.gbanyan.net/gbanyan/GB-Traefik.git ./scripts/render_dynamic.sh
cd GB-Traefik
``` ```
This uses `templates/crowdsec.yml.tmpl` and writes `dynamic.d/middlewares/crowdsec.yml` (ignored by git).
2. Update the `traefik.yml` and `docker-compose.yml` files as needed for your environment. ## Runbook
3. Start Traefik: ```bash
```bash # start / update Traefik
docker compose up -d docker compose up -d traefik
```
4. Access the Traefik dashboard (if enabled) at `http://<your-domain-or-ip>:8080`. # refresh Cloudflare IPs and restart safely
python scripts/update_cloudflare_ips.py
## Configuration # tail logs
tail -f traefik.log
- **.env**: Cloudflare E-mail and API Token for SSL DNS Challenge tail -f access.log
- **Traefik Configuration**: Modify `traefik.yml`, `dynamic.yml` to customize Traefik's behavior.
- **Docker Compose**: Use `docker-compose.yml` to define services and networks.
## Detail:
My traefik is split into internal and external entrypoint.
Internal entrypoint is for private and secure service without exposing.
Each entrypoint is binded to different ip address for isolation.
Then, other docker service is attached to different entrypoint guided by label in docker compose
```yaml
label:
- "traefik.http.routers.service-name.entrypoints=websecure"
``` ```
Besides the entrypoint setup, I add CrowdSec firewall bouncer plus a compression middleware (brotli/gzip/zstd) defined in `dynamic.yml`. Cloudflares IP ranges are injected directly into `traefik.yml` by a helper script, so no extra plugin middleware is required anymore. Rotate CrowdSec keys? Edit `.env`, rerun `render_dynamic.sh`, then `docker compose up -d traefik`.
Adding middlewares is also guided by labels: ## Service Labels Cheat Sheet
```yaml
label:
- "traefik.http.routers.service-name.middlewares=crowdsec@file,compress-middleware@file"
```
The order of middlewares is meaningful.
Traefik has ability to apply SSL certs automatically.
Just offer the required DNS API authentication (Like cloudflare).
Please refer the traefik documentation.
The following is an example of a docker service I hosted in its docker-compose.yaml:
```yaml ```yaml
labels: labels:
- "traefik.enable=true" - traefik.enable=true
- "traefik.http.routers.ghost.entrypoints=websecure" - traefik.http.routers.myapp.rule=Host(`app.example.com`)
- "traefik.http.routers.ghost.rule=Host(`blog.gbanyan.net`)" - traefik.http.routers.myapp.entrypoints=websecure
- "traefik.http.services.ghost.loadbalancer.server.port=2368" - traefik.http.routers.myapp.tls.certresolver=letsencrypt
- "traefik.http.routers.ghost.tls.certresolver=letsencrypt" - traefik.http.routers.myapp.middlewares=crowdsec@file,retry-fast@file,compress-middleware@file
- "traefik.http.routers.ghost.middlewares=crowdsec@file,compress-middleware@file" - traefik.http.services.myapp.loadbalancer.serversTransport=fast-upstreams@file
- "com.centurylinklabs.watchtower.enable=true" - traefik.http.services.myapp.loadbalancer.server.port=3000
- "traefik.docker.network=traefik_default" - traefik.docker.network=traefik_default
``` ```
I mount the access.log for crowdsec firewall to read. Most stacks use the same middleware chain: CrowdSec bouncer (plugin), retry, and compression. Internal-only services skip CrowdSec by pointing at the `internal_*` entrypoints.
PS: Because I access my traefik dashboard through my local network. I commented out the authetication method for dashboard. ## Performance Notes
## Discussion and Changelog - Access logs are buffered with headers trimmed to keep syscalls down.
- Compression enforces a 1KB minimum and respects the clients preferred encoding.
- Shared transport keeps 64 idle connections per backend with aggressive idle/response timeouts.
- `retry-fast` retries once after 50ms, smoothing transient Puma/Node hiccups without hammering backends.
1. Traefik vs Nginx ## Things to Remember
- Performance: Nginx is still better at high traffic. After all it is written in C. Traefik 3 though claims it has higher 20% performance than before. The latency still showed a little higher than nginx.
- Docker Deployment Ease: Traefik is easier for docker service deployment. In my environment, I can assign each docker stack with labels and then guides the traefik to add Let's encrypt SSL.
2. ChangeLog: - Watchtower is still enabled for Traefik; pin the image tag when you need deterministic upgrades.
- `scripts/update_cloudflare_ips.py` rewrites the static trusted IP block and restarts Traefik—run it via cron.
- 2025.4.21 Add the defaulthost rule for container name for lazy writing. But commented out for precision. - Dashboard auth is intentionally disabled because access only happens from the LAN entrypoint. If that changes, re-enable `basicauth`.
- 2025.4.21 Fix the trusted IP settings; later replaced by an internal updater instead of the traefik-plugin-cloudflare.
- 2025.4.18 Add Souin HTTP Cache Middleware (in feature branch, not merge into main)
- 2025.4.18 Temp disable the compression middleware. It has MIME type bugs.
## Notes on Host Networking
Traefik currently runs with `network_mode: host` so it can bind directly to both `10.0.0.225` (public) and `192.168.50.4` (internal) entrypoints. Moving back to bridge mode would break that dual-IP isolation because Docker cannot publish the same container port on two different host interfaces. Host networking also means:
- Traefik reaches app containers like any other host process, ignoring `traefik.docker.network` labels.
- Linux handles firewalling/routing between the two interfaces; Dockers conntrack optimizations arent used.
If you ever want to switch to bridge networking, youd need either separate Traefik instances (one per subnet) or an external L4 proxy in front of a single Traefik that listens on generic `:80/:443` ports. For now the host-mode trade-off is intentional to keep the internal/external split simple.

View File

@@ -17,7 +17,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
- ./certs:/letsencrypt - ./certs:/letsencrypt
#- ./dashboard_authfile:/dashboard_authfile:ro #- ./dashboard_authfile:/dashboard_authfile:ro
- ./dynamic.yml:/dynamic.yml - ./dynamic.d:/dynamic.d
- ./traefik.yml:/traefik.yml - ./traefik.yml:/traefik.yml
- ./traefik.log:/var/log/traefik/traefik.log - ./traefik.log:/var/log/traefik/traefik.log
- ./access.log:/var/log/traefik/access.log - ./access.log:/var/log/traefik/access.log
@@ -47,4 +47,3 @@ networks:
config: config:
- subnet: 172.19.0.0/16 - subnet: 172.19.0.0/16
gateway: 172.19.0.1 gateway: 172.19.0.1

19
scripts/render_dynamic.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
if [[ ! -f .env ]]; then
echo "Missing .env file. Copy .env.example and fill in secrets." >&2
exit 1
fi
set -a
# shellcheck disable=SC1091
source .env
set +a
: "${CROWDSEC_LAPI_KEY:?CROWDSEC_LAPI_KEY must be set in .env}"
if ! command -v envsubst >/dev/null 2>&1; then
echo "envsubst is required to render templates." >&2
exit 1
fi
envsubst < templates/crowdsec.yml.tmpl > dynamic.d/middlewares/crowdsec.yml
echo "Rendered dynamic.d/middlewares/crowdsec.yml"

View File

@@ -6,4 +6,4 @@ http:
enabled: true enabled: true
crowdsecMode: stream crowdsecMode: stream
crowdsecLapiHost: "localhost:8080" crowdsecLapiHost: "localhost:8080"
crowdsecLapiKey: gFJjSzdbB0GCe/1Y9HcxMPP1vQmoa4psZOFyleJZJVQ crowdsecLapiKey: "${CROWDSEC_LAPI_KEY}"