diff --git a/arc-paste/Caddyfile b/arc-paste/Caddyfile new file mode 100644 index 0000000..1c2f792 --- /dev/null +++ b/arc-paste/Caddyfile @@ -0,0 +1,30 @@ +# Caddyfile — TLS termination + reverse proxy for arc-paste. +# +# Mounted read-only at /etc/caddy/Caddyfile by compose.yaml. Caddy reads this +# automatically on startup, so the compose service has no `command:` block. +# +# To change the public hostname, edit the site address below. To change the +# ACME contact, edit `email` in the global block. Reload without downtime via: +# docker compose -f arc-paste/compose.yaml exec caddy caddy reload --config /etc/caddy/Caddyfile + +{ + # Let's Encrypt sends expiry + revocation notices here. Without it, you + # only learn about cert problems when the site goes down. + email ops@company.com +} + +arcpaste.company.com { + # Negotiate compression with the client. zstd preferred, gzip fallback. + encode zstd gzip + + # arc-paste listens on the internal Docker network — the service name + # resolves via Compose's embedded DNS. + reverse_proxy arc-paste:7433 + + # Structured access logs to stdout so `docker compose logs caddy` + # stays parseable by jq / log shippers. + log { + output stdout + format json + } +} diff --git a/arc-paste/compose.yaml b/arc-paste/compose.yaml index 1ed5118..b5935f1 100644 --- a/arc-paste/compose.yaml +++ b/arc-paste/compose.yaml @@ -7,6 +7,10 @@ # Stop: # docker compose -f arc-paste/compose.yaml down # +# Caddy terminates HTTPS for arcpaste.company.com. Make sure DNS points to this +# host and inbound ports 80/443 are reachable for Let's Encrypt certificate +# issuance and renewal. +# # The Dockerfile lives in arc-paste/ but builds from the repo root because it # needs the parent Go module — that's what `context: ..` below selects. @@ -17,8 +21,8 @@ services: dockerfile: arc-paste/Dockerfile image: arc-paste:latest container_name: arc-paste - ports: - - "7433:7433" + expose: + - "7433" volumes: # Named volume keeps the SQLite db across container rebuilds. # arc-paste creates the directory itself on startup, so a fresh @@ -33,8 +37,33 @@ services: # No healthcheck: the runtime image is `scratch`, which has no shell, # wget, curl, or anything else compose's `healthcheck.test` could exec. # For production monitoring, run an external probe (Cloudflare health - # check, Uptime Kuma, etc.) against the host port instead. + # check, Uptime Kuma, etc.) against the Caddy HTTPS endpoint instead. + + caddy: + # Pin minor version for reproducible deploys; bump deliberately when you + # want new features. Floating `:2-alpine` would silently follow upstream. + image: caddy:2.11-alpine + container_name: arc-paste-caddy + depends_on: + - arc-paste + ports: + - "80:80" + - "443:443" + - "443:443/udp" + volumes: + # Caddyfile drives the reverse proxy + ACME config. Mounted read-only + # so the container can't accidentally rewrite it on `caddy reload`. + - ./Caddyfile:/etc/caddy/Caddyfile:ro + # Persist ACME certificates and Caddy autosave config across restarts. + # Losing /data means losing the ACME account + certs — risks LE rate limits. + - caddy-data:/data + - caddy-config:/config + restart: unless-stopped volumes: arc-paste-data: name: arc-paste-data + caddy-data: + name: arc-paste-caddy-data + caddy-config: + name: arc-paste-caddy-config diff --git a/docs/runbooks/paste-server.md b/docs/runbooks/paste-server.md index 2c86abb..3e416ea 100644 --- a/docs/runbooks/paste-server.md +++ b/docs/runbooks/paste-server.md @@ -147,21 +147,30 @@ Remote mode runs the standalone `arc-paste` binary (a thin wrapper around the sa ARC_PASTE_ADDR=:7433 ARC_PASTE_DB=/tmp/arc-paste.db ./bin/arc-paste ``` -Or via Docker (uses the `arc-paste/Dockerfile` scratch image and a named volume): +Or via Docker for the production-style HTTPS stack (uses the `arc-paste/Dockerfile` scratch image, a named SQLite volume, and Caddy for `https://arcpaste.company.com`): ```bash docker compose -f arc-paste/compose.yaml up -d --build docker compose -f arc-paste/compose.yaml logs -f ``` -The compose file binds host port 7433, persists SQLite to the `arc-paste-data` named volume, and sets `restart: unless-stopped`. Note: the runtime image is `scratch`, so there's no in-container healthcheck — pair with an external probe (Cloudflare health check, Uptime Kuma, etc.) for production. +The compose file publishes Caddy on host ports 80/443 (including UDP 443 for HTTP/3), exposes `arc-paste` only on the internal Docker network, persists SQLite to the `arc-paste-data` named volume, persists Caddy certificate state to named volumes, and sets `restart: unless-stopped`. Caddy is configured via `arc-paste/Caddyfile` (mounted read-only) — edit the site address there to change the public hostname, or `email` in the global block to change the ACME contact. After editing, reload without downtime: -### Create a shared paste pointed at the standalone server +```bash +docker compose -f arc-paste/compose.yaml exec caddy caddy reload --config /etc/caddy/Caddyfile +``` + +Make sure DNS for `arcpaste.company.com` points to the host and ports 80/443 are reachable for Let's Encrypt issuance and renewal. Note: the runtime image is `scratch`, so there's no in-container healthcheck — pair with an external probe (Cloudflare health check, Uptime Kuma, etc.) against the HTTPS endpoint for production. + +### Create a shared paste pointed at the remote server ```bash -# Terminal 2 -./bin/arc share create /tmp/test-plan.md --share --server http://localhost:7433 -# → Share URL: http://localhost:7433/share/#k= +# Terminal 2 (Docker/Caddy stack) +./bin/arc share create /tmp/test-plan.md --share --server https://arcpaste.company.com +# → Share URL: https://arcpaste.company.com/share/#k= + +# If you launched the standalone binary directly instead, use: +# ./bin/arc share create /tmp/test-plan.md --share --server http://localhost:7433 ``` That URL is what you'd send to a real reviewer (Slack, email, etc.). It contains everything they need: the share id and the decryption key in the fragment.