Files
skill-deploy-app/references/caddy-patterns.md
Darren Neese 994332a3f0 Initial commit: _deploy_app skill
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
2026-03-25 21:12:30 -04:00

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
```