fabro/docker/split-web/README.md
Bryan Helmkamp 31a990c4dc
Add split web Docker Compose PoC (#445)
## Summary

Adds a standalone Docker Compose proof that runs the Fabro Rust API, a
Caddy static SPA server, and a Caddy edge proxy as separate services.
This demonstrates split web asset serving while keeping `/api/*`,
`/auth/*`, and `/health` same-origin with the API server.

## Changes

- Adds `docker-compose.split-web.yaml` with private `fabro-api` and
`fabro-web` services behind an exposed `edge` proxy on port 8080.
- Adds Caddy edge routing that sends `/api/*`, `/auth/*`, and `/health`
to Rust, while everything else goes to the static web service.
- Adds a static Caddy config for `apps/fabro-web/dist` with SPA
fallback, source-map blocking, security headers, immutable asset
caching, and `X-Fabro-PoC-Upstream` route-proof headers.
- Adds PoC server settings and a README with build, run, and validation
commands.

## Verification

- `cargo dev docker-build --tag fabro-sh/fabro:split-web-poc`
- `docker compose -f docker-compose.split-web.yaml up -d`
- `docker compose -f docker-compose.split-web.yaml ps`
- `curl` checks for `/runs`, `/assets/app.css`, `/assets/app.css.map`,
`/api/v1/health`, `/api/v1/auth/config`, `/auth/login/dev-token`,
`/api/v1/auth/me`, and `/api/v1/attach`
- Browser login flow via `browser-use`: loaded `/login`, submitted the
dev token, and landed on the authenticated Runs screen
- `docker compose -f docker-compose.split-web.yaml config`
- `caddy validate` for both Caddyfiles
- `git diff --check`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
2026-05-28 00:03:00 -04:00

1.8 KiB

Split Web Compose PoC

This Compose stack proves that Fabro can serve the React SPA from a separate static process while the Rust server remains the API and browser-auth origin.

Request ownership:

  • /api/* -> fabro-api:32276
  • /auth/* -> fabro-api:32276
  • /health -> fabro-api:32276
  • everything else -> fabro-web:80

The Rust server still contains bundled SPA assets. In this PoC they are simply not reachable through the edge service for normal web paths.

The edge proxy adds X-Fabro-PoC-Upstream to responses so manual checks can confirm which service handled a request.

Run

Build the local Fabro image from the current tree:

cargo dev docker-build --tag fabro-sh/fabro:split-web-poc

Set local auth secrets:

export SESSION_SECRET="$(openssl rand -hex 32)"
export FABRO_DEV_TOKEN="fabro_dev_$(openssl rand -hex 32)"

Start the split stack:

docker compose -f docker-compose.split-web.yaml up

Open http://localhost:8080.

Use SPLIT_WEB_PORT to expose a different local port, or FABRO_IMAGE to use a different API image.

Validate

curl -i http://localhost:8080/health
curl -i http://localhost:8080/api/v1/health
curl -I http://localhost:8080/runs
curl -I http://localhost:8080/assets/app.css

curl -c /tmp/fabro.cookies \
  -H "content-type: application/json" \
  -d "{\"token\":\"$FABRO_DEV_TOKEN\"}" \
  http://localhost:8080/auth/login/dev-token

curl -b /tmp/fabro.cookies http://localhost:8080/api/v1/auth/me

Expected results:

  • /runs and /assets/* are served by the static fabro-web container.
  • /api/*, /auth/*, and /health are served by the Rust fabro-api container through the same browser origin.
  • Dev-token login sets a same-origin session cookie, and /api/v1/auth/me accepts it.