# RecipesCam web — self-contained stack A Docker-hosted web build of RecipesCam. Everything it needs is in this folder: move it to another machine, run two commands, and the app is up. It does **not** need the React Native project around it. ```bash cp .env.example .env docker compose up -d --build # → http://localhost:8090 ``` ## What runs where | Service | Image | Role | | --- | --- | --- | | `frontend` | `nginx:1.27-alpine` (built by `frontend/Dockerfile`) | Static SPA + `/api/` reverse proxy | | `api` | `node:22-slim` (built by `backend/Dockerfile`) | Accounts + saved recipes, SQLite on `./data` | `frontend` resolves `api` through Docker's embedded DNS and proxies `/api/*` to it — that is why the API container is named `api` and why it is not published on the host. Only `${WEB_PORT:-8090}` is exposed. Photos never leave the browser. The CanvasKit render pipeline (grade, frame, watermarks, JPEG encode) runs in the visitor's tab; the API only stores recipes as JSON. ## Layout ``` docker-compose.yml the stack .env.example WEB_PORT data/ SQLite (created on first run, gitignored) backend/ Fastify + better-sqlite3 API, own Dockerfile frontend/ Vite + React + CanvasKit SPA, own Dockerfile + nginx.conf shared/ vendored copies of the app's types + utils (see below) src/engine/ skiaShim.ts (CanvasKit) + exportEngine.ts (render pipeline) ``` ### Vendored files `frontend/shared/{types/index.ts,utils/*.ts}` are byte-identical copies of `src/types/index.ts` and ten `src/utils/*.ts` files from the React Native project (`@shopify/react-native-skia` is aliased to `src/engine/skiaShim.ts` in `vite.config.ts` + `tsconfig.json`, so those files compile unchanged): `cinemaShader` `colorUtils` `defaultRecipes` `exifWrite` `frameUtils` `jpegDpi` `paramDefs` `skiaImage` `toneShader`. When the app changes one of them, copy it back in — the renderer is only "parity" for as long as these stay in sync: ```bash cd /docker/frontend/shared/utils cp /src/utils/.ts . ``` ## Operations ```bash docker compose logs -f api # API log docker compose restart api # after backend/src changes (rebuild: --build) docker compose down # stop; ./data survives ``` Backup is the `./data` folder — that is the whole database. ## Checks ```bash curl -s http://localhost:8090/api/health # {"ok":true} curl -sI http://localhost:8090/ # 200, index.html ``` Then open the UI, drop a photo in, and confirm the preview shows the picture and EXPORT downloads a JPEG that opens. The preview going solid black while the export still reports a plausible size is the one failure mode worth knowing: it means the CanvasKit GPU surfaces lost their shared `GrDirectContext` (see `frontend/src/engine/skiaShim.ts`), and with no GPU the raster fallback renders the same pipeline correctly, just slower.