Files
Obsidian-Vault/Personal/Areas/Servers/TrueNAS/Traefik Multi-Stack Setup.md
2025-10-25 20:11:21 +02:00

581 lines
14 KiB
Markdown

# Traefik Multi-Stack Setup
## Architecture Overview
**One Traefik instance** serves as the reverse proxy for **multiple independent Docker Compose stacks**.
```
┌─────────────────────────────────────────┐
│ Traefik (Port 80/443) │
│ (Handles SSL & Routing) │
└─────────────────┬───────────────────────┘
│ traefik_proxy network
┌─────────┼─────────┬──────────────┐
│ │ │ │
┌───▼───┐ ┌──▼────┐ ┌──▼─────┐ ┌───▼────┐
│ Gitea │ │ Other │ │ Future │ │ Future │
│ Stack │ │ Stack │ │ Stack │ │ Stack │
└───────┘ └───────┘ └────────┘ └────────┘
```
**Benefits:**
- Each service stack is independent (update/restart without affecting others)
- One SSL certificate manager for all services
- Easy to add new services
- Clean separation of concerns
- Each service has its own docker-compose.yml
---
## Directory Structure
```
/mnt/tank/stacks/
├── traefik/ # Traefik stack
│ ├── docker-compose.yml
│ ├── traefik.yml
│ └── letsencrypt/
│ └── acme.json
├── gitea/ # Gitea stack
│ ├── docker-compose.yml
│ └── data/
├── servarr/ # Your existing Servarr stack
│ ├── docker-compose.yml
│ └── data/
└── future-service/ # Future additions
└── docker-compose.yml
```
---
## Step 1: Deploy Traefik (Once)
This is the **only** Traefik instance you need.
### Create Directory
```bash
mkdir -p /mnt/tank/stacks/traefik/letsencrypt
cd /mnt/tank/stacks/traefik
```
### Create `traefik.yml`
```yaml
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false # Only expose containers with traefik.enable=true
network: traefik_proxy # Default network to use
certificatesResolvers:
cloudflare:
acme:
email: your-email@example.com
storage: /letsencrypt/acme.json
# Use DNS challenge for Cloudflare (better for wildcard certs)
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"
```
### Create `docker-compose.yml`
```yaml
version: '3.8'
networks:
traefik_proxy:
name: traefik_proxy
driver: bridge
services:
traefik:
image: traefik:v2.10
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- traefik_proxy
ports:
- "80:80"
- "443:443"
environment:
- TZ=America/New_York
# Cloudflare API credentials for DNS challenge
- CF_API_EMAIL=your-cloudflare-email@example.com
- CF_API_KEY=your-cloudflare-global-api-key
# Or use API Token (recommended):
# - CF_DNS_API_TOKEN=your-cloudflare-api-token
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/traefik.yml:ro
- ./letsencrypt:/letsencrypt
labels:
- "traefik.enable=true"
# Dashboard
- "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=cloudflare"
- "traefik.http.routers.traefik.service=api@internal"
# Basic auth for dashboard
# Generate: echo $(htpasswd -nb admin yourpassword) | sed -e s/\\$/\\$\\$/g
- "traefik.http.routers.traefik.middlewares=traefik-auth"
- "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/"
```
### Get Cloudflare API Key
**Option A: Global API Key (easier but less secure)**
1. Cloudflare Dashboard → Profile → API Tokens
2. View Global API Key
3. Use with `CF_API_EMAIL` and `CF_API_KEY`
**Option B: API Token (recommended)**
1. Cloudflare Dashboard → Profile → API Tokens → Create Token
2. Use template: "Edit zone DNS"
3. Permissions: Zone → DNS → Edit
4. Zone Resources: Include → Specific zone → yourdomain.com
5. Use with `CF_DNS_API_TOKEN`
### Deploy Traefik
```bash
cd /mnt/tank/stacks/traefik
docker compose up -d
# Check logs
docker logs traefik -f
```
---
## Step 2: Connect Gitea to Traefik
Gitea runs in its **own separate stack**, but connects to Traefik's network.
### Create `docker-compose.yml` for Gitea
```bash
mkdir -p /mnt/tank/stacks/gitea
cd /mnt/tank/stacks/gitea
```
**Key changes from standalone:**
- Connects to **external** `traefik_proxy` network
- Uses Traefik labels for routing
- No exposed ports except SSH (Traefik handles HTTPS)
```yaml
version: '3.8'
networks:
traefik_proxy:
external: true # Uses Traefik's network
gitea_internal:
driver: bridge # Internal network for Gitea (if needed for DB)
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__server__DOMAIN=git.yourdomain.com
- GITEA__server__SSH_DOMAIN=git.yourdomain.com
- GITEA__server__ROOT_URL=https://git.yourdomain.com
- GITEA__server__SSH_PORT=2222
- GITEA__server__SSH_LISTEN_PORT=22
networks:
- traefik_proxy # Connect to Traefik
- gitea_internal # Internal network
volumes:
- ./data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "2222:22" # Only SSH needs to be exposed directly
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_proxy"
# HTTP/HTTPS routing
- "traefik.http.routers.gitea.rule=Host(`git.yourdomain.com`)"
- "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.tls.certresolver=cloudflare"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
```
### Deploy Gitea
```bash
cd /mnt/tank/stacks/gitea
docker compose up -d
# Check logs
docker logs gitea -f
```
---
## Step 3: Connect Your Other Stack to Traefik
For any other service, follow the same pattern:
### Example: Generic Service
```yaml
version: '3.8'
networks:
traefik_proxy:
external: true # Connect to Traefik's network
services:
your-service:
image: your-service-image:latest
container_name: your-service
restart: unless-stopped
networks:
- traefik_proxy
volumes:
- ./data:/data
# NO ports exposed - Traefik handles routing
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_proxy"
- "traefik.http.routers.your-service.rule=Host(`service.yourdomain.com`)"
- "traefik.http.routers.your-service.entrypoints=websecure"
- "traefik.http.routers.your-service.tls.certresolver=cloudflare"
- "traefik.http.services.your-service.loadbalancer.server.port=8080" # Internal port
```
### Deploy Your Service
```bash
cd /mnt/tank/stacks/your-service-name
docker compose up -d
```
**That's it!** Traefik automatically:
- Detects the new container
- Creates routes based on labels
- Generates SSL certificate
- Starts routing traffic
---
## Cloudflare DNS Configuration
For each service, add an A record in Cloudflare:
### DNS Records
| Type | Name | Content | Proxy Status |
|------|------|---------|--------------|
| A | git | Your-Public-IP | DNS Only (gray) |
| A | traefik | Your-Public-IP | DNS Only (gray) |
| A | service | Your-Public-IP | DNS Only (gray) |
**Important:** Set to "DNS only" (gray cloud), not proxied (orange cloud), unless you want Cloudflare's proxy in front (requires additional Traefik config).
---
## Port Forwarding
Only forward **once** for Traefik:
| External Port | Internal Port | Protocol | Service |
|--------------|---------------|----------|---------|
| 80 | 80 | TCP | HTTP (Traefik) |
| 443 | 443 | TCP | HTTPS (Traefik) |
| 2222 | 2222 | TCP | SSH (Gitea only) |
**Additional service-specific ports** (like Gitea SSH on 2222) are forwarded individually.
---
## Managing Multiple Stacks
### Start/Stop Individual Stacks
```bash
# Stop Gitea only (doesn't affect Traefik or other services)
cd /mnt/tank/stacks/gitea
docker compose down
# Start Gitea
docker compose up -d
# Restart Gitea
docker compose restart
```
### Update Individual Stacks
```bash
# Update just Gitea
cd /mnt/tank/stacks/gitea
docker compose pull
docker compose up -d
# Update just Traefik
cd /mnt/tank/stacks/traefik
docker compose pull
docker compose up -d
```
### View All Services
```bash
# See all containers connected to Traefik
docker network inspect traefik_proxy
# See all running containers
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
```
---
## Adding New Services (Quick Template)
1. **Create service directory:**
```bash
mkdir -p /mnt/tank/stacks/new-service
cd /mnt/tank/stacks/new-service
```
2. **Create `docker-compose.yml`:**
```yaml
version: '3.8'
networks:
traefik_proxy:
external: true
services:
service-name:
image: service-image:latest
container_name: service-name
restart: unless-stopped
networks:
- traefik_proxy
volumes:
- ./data:/data
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_proxy"
- "traefik.http.routers.service-name.rule=Host(`service.yourdomain.com`)"
- "traefik.http.routers.service-name.entrypoints=websecure"
- "traefik.http.routers.service-name.tls.certresolver=cloudflare"
- "traefik.http.services.service-name.loadbalancer.server.port=INTERNAL_PORT"
```
3. **Add DNS record** in Cloudflare: `service.yourdomain.com → Your-IP`
4. **Deploy:**
```bash
docker compose up -d
```
5. **Access:** `https://service.yourdomain.com`
---
## Common Patterns
### Service with Database
```yaml
version: '3.8'
networks:
traefik_proxy:
external: true
internal:
driver: bridge
services:
app:
image: app-image:latest
networks:
- traefik_proxy # For external access
- internal # For database access
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_proxy"
- "traefik.http.routers.app.rule=Host(`app.yourdomain.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=cloudflare"
- "traefik.http.services.app.loadbalancer.server.port=3000"
db:
image: postgres:15
networks:
- internal # Only on internal network (not exposed)
volumes:
- ./db-data:/var/lib/postgresql/data
```
### Multiple Domains for One Service
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.yourdomain.com`) || Host(`app2.yourdomain.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=cloudflare"
```
### Service with Path Prefix
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`yourdomain.com`) && PathPrefix(`/app`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=cloudflare"
- "traefik.http.middlewares.app-stripprefix.stripprefix.prefixes=/app"
- "traefik.http.routers.app.middlewares=app-stripprefix"
```
---
## Troubleshooting Multi-Stack Setup
### Service Not Accessible
```bash
# Check if container is on traefik_proxy network
docker network inspect traefik_proxy
# Check Traefik logs for routing issues
docker logs traefik | grep -i error
# Verify DNS resolves correctly
nslookup service.yourdomain.com
# Check if Traefik sees the service
docker exec traefik wget -O- http://localhost:8080/api/http/routers | grep service-name
```
### SSL Certificate Issues
```bash
# Check Traefik logs for ACME errors
docker logs traefik | grep -i acme
# Verify Cloudflare credentials are correct
docker exec traefik env | grep CF_
# Delete acme.json to force renewal (last resort)
docker compose down
rm letsencrypt/acme.json
docker compose up -d
```
### Network Conflicts
```bash
# List all networks
docker network ls
# Inspect traefik_proxy network
docker network inspect traefik_proxy
# Recreate network if needed
docker network rm traefik_proxy
cd /mnt/[pool]/docker/traefik
docker compose up -d
```
---
## Stack Independence Benefits
✅ **Update one service without affecting others**
```bash
cd /mnt/tank/stacks/gitea
docker compose pull && docker compose up -d
# Other services keep running
```
✅ **Restart one service without downtime for others**
```bash
docker restart gitea
# Traefik and other services unaffected
```
✅ **Remove a service cleanly**
```bash
cd /mnt/tank/stacks/old-service
docker compose down
rm -rf /mnt/tank/stacks/old-service
# Done! No leftover configs in other stacks
```
✅ **Easy backups per service**
```bash
tar -czf gitea-backup.tar.gz /mnt/tank/stacks/gitea/
tar -czf servarr-backup.tar.gz /mnt/tank/stacks/servarr/
```
---
## Next Steps
- [ ] Deploy Traefik (one time)
- [ ] Get Cloudflare API credentials
- [ ] Configure DNS records for services
- [ ] Deploy Gitea stack
- [ ] Connect your existing stack to Traefik
- [ ] Test accessing all services via HTTPS
- [ ] Set up automated backups per stack
---
## Example Full Setup
```
/mnt/tank/stacks/
├── traefik/
│ ├── docker-compose.yml # Traefik
│ ├── traefik.yml
│ └── letsencrypt/
├── gitea/
│ ├── docker-compose.yml # Gitea stack
│ └── data/
├── servarr/
│ ├── docker-compose.yml # Servarr stack (your existing)
│ └── ...
├── nextcloud/
│ ├── docker-compose.yml # Nextcloud stack
│ ├── app-data/
│ └── db-data/
└── vaultwarden/
├── docker-compose.yml # Vaultwarden stack
└── data/
```
Each stack is independent, but all route through Traefik! 🎉