Rendered from the repository — the file stays the source of truth.
Session B1 — Spike S-1: SSE streaming on Scaleway Serverless Containers (2026-08-17)
Goal
Feasibility spike S-1 (feat/spike-streaming): deploy a minimal streaming
skeleton to a real Scaleway Serverless Container and decide D-7
(serverless vs VM fallback) with actual measurements.
Verdict
Serverless confirmed. The VM fallback (D-7) is not needed.
product/feasibility.md updated: F-2 ✅, D-7 decided, SSE + body-limit risk
rows resolved.
Measurements (container marginalia-webapp, fr-par, min-scale 0, 1 vCPU/2 GB)
| Test | Result |
|---|---|
| SSE ticker (server-paced 500 ms, 10 events) | Client arrival deltas 499–508 ms — no gateway buffering, event-by-event delivery |
LLM stream (AI SDK toUIMessageStreamResponse, mistral-small-3.2) |
87 SSE events arrive incrementally over ~0.77 s; clean finish + [DONE] — no truncation |
| Warm TTFB (GET info route, ×3) | 87–96 ms |
| Warm LLM stream | TTFB 100 ms (headers immediate), 2.22 s total for a ~100-line generation (17 KB SSE) |
| Cold start (min-scale 0, 25 min idle, 407 MB image) | TTFB 3.89 s, HTTP 200 (vs ~0.09 s warm) |
| Body limit probes (POST, echo size) | 512 KB / 1 MB / 5 MB / 10 MB / 20 MB — all HTTP 200, full body received. The user-reported ~1 MB limit did not materialize |
What was done
- Throwaway route
apps/webapp/src/app/api/spike-stream/route.ts(delete after A4 exists): SSE ticker mode, LLM stream via@ai-sdk/openai-compatibleagainst Scaleway Generative APIs, body-probe mode. AI SDK 7 (ai@7.0.66). - Keeper Dockerfile
apps/webapp/Dockerfile(B2 reuses): Bun installs deps,next build+ runtime onnode:24-slim, standalone output, non-root. Verified locally before deploy. CompanionDockerfile.dockerignore(BuildKit per-Dockerfile ignore); build context is the repo root. next.config.ts:output: "standalone"+outputFileTracingRoot(repo root — required in a monorepo).- Terraform:
scaleway_containercompleted (deprecated args replaced:image,memory_limit_bytes,https_connections_only,public_endpoint); resource prefix renamed tomarginalia(product name, owner-confirmed) before first apply; manual push scriptinfrastructure/scripts/push-webapp.sh. First apply performed — state is local in the worktree (.worktrees/feat-spike-streaming/infrastructure/); migrate to the S3 backend in B2 before the worktree is deleted, or moveterraform.tfstateto the main checkout when this branch merges. - Secrets: resolved from Proton Pass (vault
marginalia, itemscaleway-api-key) viapass-cli injectinto untracked.env.local, symlinked into the worktree. New schema declarations:SCW_GENERATIVE_APIS_KEY,SCW_GENERATIVE_APIS_MODEL,SCW_GENERATIVE_APIS_BASE_URL,TF_VAR_generative_apis_key,TF_VAR_scw_project_id.
Gotchas found (worth remembering)
- Bun 1.3 isolated installs break Next standalone tracing — the
node_modules/.bunsymlink store loses@swc/helpersin the traced output. Dockerfile usesbun install --linker=hoisted. - Generative APIs needs the project-scoped URL
(
https://api.scaleway.ai/<project-id>/v1) when the IAM key’s default project isn’t the target project — bare/v1returns 403. The container getsSCW_GENERATIVE_APIS_BASE_URLfrom Terraform. bunx varlock runcurrently refuses to run anything because the two required Supabase values are empty in.env.local. Deploy commands were run with.env.localsourced directly. Once A-lane fills the Supabase values,varlock runworks as documented.- IAM key scoped to Object Storage/Registry/Containers/GenerativeApis
cannot read the account API — Terraform data sources like
scaleway_account_projectwould 403; the project ID is passed asTF_VAR_scw_project_idinstead.
Cost left running
- Container
marginalia-webappat min-scale 0: €0 idle, per-request within free tier if hit.max_scale = 2caps worst case. - Registry image (~400 MB): ~€0.01/mo. Buckets: empty, €0.
- The spike route is unauthenticated on a public URL; token spend is possible if the URL leaks. Fine for days, not weeks — B2 should gate or delete it.
Next (B2)
CI (lint/test/build), deploy pipeline reusing this Dockerfile + push script, Terraform state → S3 backend, bucket deploys for docs/marketing, delete or protect the spike route.