Architecture
How the services fit together — and why the storage service sits behind the API instead of next to it.
The entry point
A single Caddy proxy on port 8080 is the only surface exposed to the browser. It routes by path: / goes to the Nuxt admin, /api/* is rewritten to the Hono API, /storage/* is rewritten to the storage service. S3/Minio is never reachable from outside — only the storage service talks to it.
Proxy (Caddy)
Routes by path: / → admin, /api/* → api (strip prefix), /storage/* → storage (strip prefix). Handles TLS in production.
Admin (Nuxt 4)
Nuxt 4 admin UI: story editor, component schema editor, datasources, assets, access tokens, audit log.
API (Hono)
Hono + Bun. Hosts CDN v2 (Storyblok-compat), Management v1, MCP (/v1/mcp), auth, webhooks. Only service that touches the Postgres schema.
Storage (Hono)
Hono + Bun. Upload, download, delete and on-the-fly /m/ image transforms. Only service with S3 credentials. Uses HMAC tokens minted by the API for upload auth.
Postgres
Canonical store. Stories, drafts, versions, components, datasources, assets, tokens, audit log. Schema owned by Drizzle.
Minio (S3)
S3-compatible object store for asset blobs. Bucket layout: spaces/<id>/assets/<n>/<filename>. Transforms live under /m/. Never exposed externally.
Redis (optional)
Optional CDN cache. If REDIS_URL is empty the cache client is a silent no-op — no try/catch, no errors, just slower responses.