chore(deploy): single-container Dockerfile, Caddy, and Unraid template
CI / Repo hygiene (pull_request) Successful in 2s
CI / Web (lint, typecheck, build) (pull_request) Successful in 15s
CI / Migrations reversible (pull_request) Successful in 6s
CI / API (lint, types, tests) (pull_request) Successful in 53s

Builds the container the "1 container" decision (D15) actually needs, which
D15 itself deferred as follow-up work: Caddy + the FastAPI app + the static
SvelteKit build in one image, SQLite on a mounted volume. See docs/DECISIONS.md
D16 for the specific choices and why (entrypoint-run migrations instead of a
separate deploy-pipeline step, tini + a small supervisor script instead of
s6-overlay/supervisord, copying the Caddy binary out of its official image).

Removes apps/api/Dockerfile and apps/web/Dockerfile from the old 4-container
compose plan (PR #4, closed as superseded) — the root Dockerfile replaces both
with one multi-stage build.

deploy/unraid-template.xml turns VELODROME_PUBLIC_URL, VELODROME_SECRET_KEY,
etc. into fillable Unraid Community Applications web UI fields, per the
earlier decision to keep config there instead of a .env file.

.gitea/workflows/release.yml builds and pushes the image to the Gitea registry
on a version tag or manual dispatch; it does not touch the running container.

Verified by actually running the built image, not just building it: the
health endpoint responds through Caddy's proxy, the SPA serves with working
client-route fallback, alembic ran and produced a real (non-empty) SQLite file
under /data, the process runs as the non-root velodrome user, and killing the
uvicorn process brings the whole container down (exit 143) rather than
leaving Caddy serving alone — confirming the entrypoint's coupled-lifetime
behavior actually holds, not just that it reads correctly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R2ZKeWkZV7ehf7fivrAkkG
This commit is contained in:
2026-09-21 15:55:50 -04:00
co-authored by Claude Sonnet 5
parent 9fa50cb2ea
commit 6c48000d7b
12 changed files with 378 additions and 89 deletions
+31
View File
@@ -0,0 +1,31 @@
# Single-container Caddy config (docs/DECISIONS.md D15). Serves the static SvelteKit build and
# reverse-proxies /api/* to uvicorn on loopback — the same split apps/web/vite.config.ts's dev
# proxy describes, just as Caddy directives instead of Vite's dev-server proxy.
{
admin off
# Explicit, not just implied by using a bare port below: this container is never the TLS
# terminator (docs/PLAN.md "Service topology" — the host's existing reverse proxy is), so
# Caddy must never attempt to provision a certificate for whatever it's fronted by.
auto_https off
}
:8080 {
encode gzip
log {
output stdout
}
handle /api/* {
reverse_proxy 127.0.0.1:8000
}
# SPA fallback matching apps/web/vite.config.ts's `adapter({ fallback: 'index.html' })`:
# any path that isn't a real built file resolves to index.html so client-side routing works
# on a hard refresh / direct link.
handle {
root * /srv/web
try_files {path} /index.html
file_server
}
}