Files
PiCloud/docs/dev-guide/src/deploy/caddy-tls.md
MechaCat02 05ea29fbd0 docs: add comprehensive developer guide (mdBook)
Add a browsable mdBook developer guide under docs/dev-guide/ covering the
full PiCloud v1.1.9 surface for developers building on the platform:

- Guide: introduction, 10-minute quickstart, core concepts, writing scripts
- Tutorials: 5 end-to-end example apps (URL shortener, webhook receiver,
  TODO API with auth, scheduled report, file-upload service) + feature matrix
- SDK reference: kv/docs/files, pubsub/queue, http, email, users, secrets,
  invoke/retry/dead_letters, stdlib, ctx & events
- HTTP API reference: every /api/v1 endpoint, auth, and capability
- CLI reference: the `pic` client + the server admin recovery command
- Config: every PICLOUD_* env var, capabilities & roles
- Deployment: Docker Compose, bare binary, Caddy/TLS, production checklist
- Operations: security, best practices, troubleshooting

Every example was built and run against a live instance; SDK signatures,
endpoint paths, and env vars were verified against source. Build the site
with `mdbook serve docs/dev-guide`. A pointer is added to README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 13:54:23 +02:00

3.3 KiB

Caddy & TLS

Caddy is the single entry point in front of PiCloud. The same topology serves dev and production — only the upstream targets and TLS settings change.

Routing topology

Caddy routes by path prefix (from caddy/Caddyfile):

Path Goes to
/healthz, /version picloud (liveness + version)
/api/v1/admin/* picloud (control plane)
/api/v1/execute/* picloud (execute-by-id)
/api/* (anything else) 404 — reserved for future API versions
/admin/* the dashboard SPA
everything else picloud's user-route matcher — your scripts' routes

That final catch-all is what makes arbitrary user paths like /hello or /r/abc123 work: Caddy forwards them to picloud, which matches them against the route table (or returns a JSON 404). This is why your routes can't use the reserved prefixes /api/, /admin/, /healthz, /version — Caddy would never forward them to the matcher.

Security headers & body limit

The Caddyfile adds defense-in-depth headers and a hard body cap:

  • X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer on every response.
  • A strict CSP, X-Frame-Options: DENY, Cache-Control: no-store, and a locked-down Permissions-Policy on the dashboard and admin API.
  • A 12 MB request-body ceiling at the proxy (just above the orchestrator's 10 MiB user-route read).
  • User-route responses get no CSP on purpose — your scripts own their own response headers. The ? (set-if-missing) operator means a script's own header wins over the default.

Production: HTTPS with Let's Encrypt

Layer the production overlay, which swaps in caddy/Caddyfile.prod and exposes 80/443:

export PICLOUD_DOMAIN="picloud.example.com"
export PICLOUD_ADMIN_EMAIL="you@example.com"
export POSTGRES_PASSWORD="$(head -c 24 /dev/urandom | base64)"
export PICLOUD_SECRET_KEY="$(head -c 32 /dev/urandom | base64)"
export PICLOUD_ADMIN_USERNAME="admin"
export PICLOUD_ADMIN_PASSWORD="…"
export PICLOUD_PUBLIC_BASE_URL="https://picloud.example.com"

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build

Caddy obtains and renews a certificate for PICLOUD_DOMAIN automatically (HTTP/TLS-ALPN challenge — the host must be reachable on 80/443 from the internet). The prod overlay also:

  • removes the published Postgres port (DB reachable only inside the compose network);
  • adds HSTS to every response (on top of the dev headers);
  • sets restart: unless-stopped on all services.

The prod Caddyfile reads PICLOUD_DOMAIN and PICLOUD_ADMIN_EMAIL from the environment, so set them before up.

Custom domains for your apps

PiCloud's app domains are matched by picloud after Caddy forwards the request — they're independent of Caddy's own host config. For multi-tenant setups ({tenant}.example.com), point a wildcard DNS record + Caddy cert at the box, then let each app claim its host pattern. The most-specific app claim wins; unclaimed hosts get a 404 no app claims host.

Adding a future API version

When /api/v2/... ships, add a handle /api/v2/admin/* { … } block before the catch-all /api/* 404, mirroring the v1 block. The v1 routes stay live through the deprecation window.