Deploy new apps or push updates to existing deployments via Docker Compose + Caddy + Gitea webhooks. Multi-server profiles, auto-detection of deployment status, full infrastructure provisioning. - SKILL.md: 715-line workflow documentation - scripts/detect_deployment.py: deployment status detection - scripts/validate_compose.py: compose file validation - references/: infrastructure, compose patterns, Caddy patterns - assets/: Makefile and compose templates - config.json: mew server profile
253 lines
8.0 KiB
Markdown
253 lines
8.0 KiB
Markdown
# Caddyfile Patterns Reference
|
|
|
|
Reusable Caddyfile site block patterns for the mew server. All blocks go in `/data/docker/caddy/Caddyfile`. After editing, reload or restart Caddy (see infrastructure.md for details).
|
|
|
|
---
|
|
|
|
## 1. Standard Reverse Proxy
|
|
|
|
The most common pattern. Terminate TLS, compress responses, and forward to a container.
|
|
|
|
```
|
|
# === My App ===
|
|
myapp.lavender-daydream.com {
|
|
encode zstd gzip
|
|
reverse_proxy myapp:3000
|
|
}
|
|
```
|
|
|
|
### Breakdown
|
|
|
|
- **Domain line**: Caddy automatically provisions a Let's Encrypt certificate for this domain.
|
|
- **`encode zstd gzip`**: Compress responses with zstd (preferred) or gzip (fallback). Include this in every site block.
|
|
- **`reverse_proxy myapp:3000`**: Forward requests to the container named `myapp` on port 3000. Caddy resolves the container name via the shared `proxy` Docker network.
|
|
|
|
### Prerequisites
|
|
|
|
- DNS A record pointing the domain to `155.94.170.136`.
|
|
- The target container is running and joined to the `proxy` network.
|
|
- The container name and port match what is specified in the `reverse_proxy` directive.
|
|
|
|
---
|
|
|
|
## 2. WebSocket Support
|
|
|
|
For applications that use WebSocket connections (chat apps, real-time dashboards, collaborative editors, etc.).
|
|
|
|
```
|
|
# === Real-time App ===
|
|
realtime.lavender-daydream.com {
|
|
encode zstd gzip
|
|
reverse_proxy realtime-app:3000 {
|
|
header_up X-Real-IP {remote_host}
|
|
header_up X-Forwarded-For {remote_host}
|
|
header_up X-Forwarded-Proto {scheme}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Notes
|
|
|
|
- Caddy 2 handles WebSocket upgrades transparently. There is no special `websocket` directive needed — `reverse_proxy` detects the `Upgrade: websocket` header and handles the protocol switch automatically.
|
|
- The `header_up` directives forward the real client IP and protocol to the backend, which is important for applications that log connections or enforce security based on client IP.
|
|
- If the application uses a non-standard WebSocket path (e.g., `/ws` or `/socket.io`), this pattern still works without changes — Caddy proxies all paths by default.
|
|
|
|
---
|
|
|
|
## 3. Multiple Domains
|
|
|
|
Serve the same application from multiple domains (e.g., bare domain and `www` subdomain, or a vanity domain alongside the primary).
|
|
|
|
```
|
|
# === My App (multi-domain) ===
|
|
myapp.lavender-daydream.com, www.myapp.lavender-daydream.com {
|
|
encode zstd gzip
|
|
reverse_proxy myapp:3000
|
|
}
|
|
```
|
|
|
|
### With Redirect
|
|
|
|
Redirect one domain to the canonical domain instead of serving from both:
|
|
|
|
```
|
|
# === My App (canonical redirect) ===
|
|
www.myapp.lavender-daydream.com {
|
|
redir https://myapp.lavender-daydream.com{uri} permanent
|
|
}
|
|
|
|
myapp.lavender-daydream.com {
|
|
encode zstd gzip
|
|
reverse_proxy myapp:3000
|
|
}
|
|
```
|
|
|
|
### Notes
|
|
|
|
- Caddy provisions separate TLS certificates for each domain listed.
|
|
- Ensure DNS A records exist for every domain in the site block.
|
|
- Use `permanent` (301) redirects for SEO-friendly canonical domain enforcement.
|
|
- The `{uri}` placeholder preserves the request path and query string during the redirect.
|
|
|
|
---
|
|
|
|
## 4. HTTPS Upstream
|
|
|
|
For services that speak HTTPS internally (e.g., Cockpit, some management UIs). Caddy must be told to connect to the upstream over TLS.
|
|
|
|
```
|
|
# === Cockpit ===
|
|
cockpit.lavender-daydream.com {
|
|
encode zstd gzip
|
|
reverse_proxy https://cockpit:9090 {
|
|
transport http {
|
|
tls_insecure_skip_verify
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Notes
|
|
|
|
- Prefix the upstream address with `https://` to instruct Caddy to connect over TLS.
|
|
- `tls_insecure_skip_verify` disables certificate verification for the upstream connection. Use this when the upstream uses a self-signed certificate, which is common for management interfaces like Cockpit.
|
|
- Do NOT use `tls_insecure_skip_verify` if the upstream has a valid, trusted certificate — remove the entire `transport` block in that case.
|
|
- This pattern is uncommon. Most containers speak plain HTTP internally, and Caddy handles TLS termination on the frontend only.
|
|
|
|
---
|
|
|
|
## 5. Rate Limiting
|
|
|
|
Protect sensitive endpoints (login forms, APIs, webhooks) from abuse with rate limiting.
|
|
|
|
```
|
|
# === Rate-Limited App ===
|
|
myapp.lavender-daydream.com {
|
|
encode zstd gzip
|
|
|
|
# Rate limit login endpoint: 10 requests per minute per IP
|
|
@login {
|
|
path /api/auth/login
|
|
}
|
|
rate_limit @login {
|
|
zone login_zone {
|
|
key {remote_host}
|
|
events 10
|
|
window 1m
|
|
}
|
|
}
|
|
|
|
# Rate limit API endpoints: 60 requests per minute per IP
|
|
@api {
|
|
path /api/*
|
|
}
|
|
rate_limit @api {
|
|
zone api_zone {
|
|
key {remote_host}
|
|
events 60
|
|
window 1m
|
|
}
|
|
}
|
|
|
|
reverse_proxy myapp:3000
|
|
}
|
|
```
|
|
|
|
### Notes
|
|
|
|
- Rate limiting requires the `caddy-ratelimit` plugin. Verify it is included in the Caddy build before using these directives. If it is not available, implement rate limiting at the application level instead.
|
|
- The `@name` syntax defines a named matcher that scopes the rate limit to specific paths.
|
|
- `key {remote_host}` rate-limits per client IP address.
|
|
- `events` is the maximum number of requests allowed within the `window` period.
|
|
- Clients that exceed the limit receive a `429 Too Many Requests` response.
|
|
- Apply stricter limits to authentication endpoints and more generous limits to general API usage.
|
|
|
|
### Alternative: Application-Level Rate Limiting
|
|
|
|
If the Caddy rate-limit plugin is not installed, skip the `rate_limit` directives and use the standard reverse proxy pattern. Configure rate limiting within the application instead (e.g., `express-rate-limit` for Node.js, `slowapi` for FastAPI).
|
|
|
|
---
|
|
|
|
## 6. Path-Based Routing
|
|
|
|
Route different URL paths to different backend services. Common for monorepo deployments where `/api` goes to a backend service and `/` goes to a frontend.
|
|
|
|
```
|
|
# === Full-Stack App (path-based) ===
|
|
myapp.lavender-daydream.com {
|
|
encode zstd gzip
|
|
|
|
# API requests → backend container
|
|
handle /api/* {
|
|
reverse_proxy myapp-api:8000
|
|
}
|
|
|
|
# WebSocket endpoint → backend container
|
|
handle /ws/* {
|
|
reverse_proxy myapp-api:8000
|
|
}
|
|
|
|
# Everything else → frontend container
|
|
handle {
|
|
reverse_proxy myapp-frontend:80
|
|
}
|
|
}
|
|
```
|
|
|
|
### Notes
|
|
|
|
- `handle` blocks are evaluated in the order they appear. More specific paths must come before the catch-all.
|
|
- The final `handle` (with no path argument) is the catch-all — it matches everything not matched above.
|
|
- Use `handle_path` instead of `handle` if you need to strip the path prefix before forwarding. For example:
|
|
```
|
|
handle_path /api/* {
|
|
reverse_proxy myapp-api:8000
|
|
}
|
|
```
|
|
This strips `/api` from the request path, so `/api/users` becomes `/users` when it reaches the backend. Only use this if the backend does not expect the `/api` prefix.
|
|
- Ensure all referenced containers (`myapp-api`, `myapp-frontend`) are on the `proxy` network.
|
|
|
|
### Variation: Static Files + API
|
|
|
|
Serve static files directly from Caddy for the frontend, with API requests proxied to a backend:
|
|
|
|
```
|
|
# === Static Frontend + API Backend ===
|
|
myapp.lavender-daydream.com {
|
|
encode zstd gzip
|
|
|
|
handle /api/* {
|
|
reverse_proxy myapp-api:8000
|
|
}
|
|
|
|
handle {
|
|
root * /srv/myapp/dist
|
|
try_files {path} /index.html
|
|
file_server
|
|
}
|
|
}
|
|
```
|
|
|
|
This requires the static files to be accessible from within the Caddy container (via a volume mount).
|
|
|
|
---
|
|
|
|
## Universal Conventions
|
|
|
|
Apply these conventions to every site block:
|
|
|
|
1. **Comment header**: Place `# === App Name ===` above each site block.
|
|
2. **Compression**: Always include `encode zstd gzip` as the first directive.
|
|
3. **Container names**: Use container names, not IP addresses, in `reverse_proxy`.
|
|
4. **One domain per block** unless intentionally serving multiple domains (pattern 3).
|
|
5. **Order matters**: Place more specific `handle` blocks before less specific ones.
|
|
6. **Test after changes**: After modifying the Caddyfile, reload Caddy and verify the site responds:
|
|
```bash
|
|
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
|
|
curl -I https://myapp.lavender-daydream.com
|
|
```
|
|
If reload fails, check Caddy logs:
|
|
```bash
|
|
docker logs caddy --tail 50
|
|
```
|