# syntax=docker/dockerfile:1 # # docker/web.Dockerfile # --------------------------------------------------------------------------- # Multi-stage build for the Wisp web app. # # Stage 1 (builder): Uses the official Bun image to install dependencies and # run the Expo/Metro static web export. The output is a # fully static SPA in /app/dist. # # Stage 2 (runtime): A tiny nginx:alpine image that serves the exported # static files. nginx is configured (docker/nginx.conf) # to send the cross-origin isolation headers that the # multi-threaded WASM Whisper backend requires, namely: # Cross-Origin-Opener-Policy: same-origin # Cross-Origin-Embedder-Policy: require-corp # # Build: docker build -f docker/web.Dockerfile -t wisp-web:latest . # Run: docker run --rm -p 8080:8080 wisp-web:latest # # NOTE: build context must be the repository root (so the whole repo + lockfile # are available to the builder). # --------------------------------------------------------------------------- # --------------------------------------------------------------------------- # Stage 1: build the static site with Bun + Expo export # --------------------------------------------------------------------------- # Pin to the Bun 1.x line. `oven/bun:1` tracks the latest 1.x patch release; # pin to an exact digest/tag in production if you want fully reproducible builds. FROM oven/bun:1 AS builder WORKDIR /app # Copy manifest + lockfile first so Docker can cache the (slow) dependency # install layer independently of source changes. `bun.lock` is Bun's lockfile. COPY package.json bun.lock ./ # Install EXACTLY what the lockfile specifies. --frozen-lockfile fails the build # if package.json and bun.lock have drifted, which keeps CI deterministic. RUN bun install --frozen-lockfile # Now copy the rest of the repository (app code, assets, config, etc.). # Anything matched by .dockerignore (node_modules, dist, .git ...) is skipped. COPY . . # Produce the static web bundle. `bun run export:web` maps to # `expo export --platform web`, which writes the static site to ./dist # (Expo's default --output-dir). app.json sets web.output = "static", so this # emits prerendered HTML + the JS/WASM assets for the SPA. RUN bun run export:web # --------------------------------------------------------------------------- # Stage 2: serve the static site with nginx # --------------------------------------------------------------------------- FROM nginx:alpine AS runtime # Drop the stock nginx site config and install ours. Our config: # - listens on 8080 (non-privileged port; matches docker-compose + reverse proxy) # - SPA fallback (try_files ... /index.html) # - COOP/COEP/CORP cross-origin isolation headers (for threaded WASM) # - gzip + long-lived caching for hashed static assets # - serves /wisp.apk if an APK has been dropped into the web root COPY docker/nginx.conf /etc/nginx/conf.d/default.conf # Copy the exported static site from the builder stage into nginx's web root. COPY --from=builder /app/dist /usr/share/nginx/html # Document the port the container listens on (informational; publish with -p). EXPOSE 8080 # nginx:alpine's default entrypoint already runs nginx in the foreground # ("daemon off") via /docker-entrypoint.sh, so no CMD override is needed.