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
This commit is contained in:
252
references/caddy-patterns.md
Normal file
252
references/caddy-patterns.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user