# Multi-stage build for the gateway service and the landing image. A node stage # builds the static UI (Vite) once; the `landing` target serves that build from a # static caddy container (the public landing at /, R3), while the final gateway # target embeds it โ€” minus landing.html โ€” into the Go binary # (gateway/internal/webui/dist), which serves the game SPA at /app/ and # /telegram/ (docs/ARCHITECTURE.md ยง13). The Go stage mirrors # platform/telegram/Dockerfile and ships on distroless nonroot. # # The production UI build vars are image build-args, baked into the bundle. # Build from the repository root so go.work, pkg/, gateway/, ui/ and # deploy/landing/ are all in the Docker context: # docker build -f gateway/Dockerfile \ # --build-arg VITE_GATEWAY_URL=https://example \ # -t scrabble-gateway . # docker build -f gateway/Dockerfile --target landing -t scrabble-landing . # --- UI build ---------------------------------------------------------------- FROM node:22-alpine AS ui WORKDIR /ui RUN corepack enable && corepack prepare pnpm@11.0.9 --activate # Prod UI build vars (Vite reads VITE_-prefixed env at build; baked into the bundle). # VITE_APP_VERSION carries `git describe` for the About screen (defaults to "dev"). ARG VITE_TELEGRAM_BOT_ID= ARG VITE_TELEGRAM_LINK= ARG VITE_TELEGRAM_GAME_CHANNEL_NAME= ARG VITE_VK_APP_LINK= ARG VITE_VK_APP_ID= ARG VITE_VK_ID_REDIRECT_URL= ARG VITE_GATEWAY_URL= ARG VITE_APP_VERSION= # VITE_ADS_STUB=1 substitutes a toast stub for real ads (the test contour only); production leaves it # empty so it always shows real ads (a failed real ad must not credit). ARG VITE_ADS_STUB= ENV VITE_TELEGRAM_BOT_ID=$VITE_TELEGRAM_BOT_ID \ VITE_TELEGRAM_LINK=$VITE_TELEGRAM_LINK \ VITE_TELEGRAM_GAME_CHANNEL_NAME=$VITE_TELEGRAM_GAME_CHANNEL_NAME \ VITE_VK_APP_LINK=$VITE_VK_APP_LINK \ VITE_VK_APP_ID=$VITE_VK_APP_ID \ VITE_VK_ID_REDIRECT_URL=$VITE_VK_ID_REDIRECT_URL \ VITE_GATEWAY_URL=$VITE_GATEWAY_URL \ VITE_APP_VERSION=$VITE_APP_VERSION \ VITE_ADS_STUB=$VITE_ADS_STUB # Install with the lockfile first, then build. Committed src/gen/ means no codegen here. # --ignore-scripts: the Vite build needs no dependency build script โ€” esbuild's platform binary # ships as an optional dependency (only its CLI-shim postinstall is skipped, which Vite does not use), # and core-js-bundle's prebuilt file is read directly. It notably skips sharp's native build (a # `pnpm android:assets` tool via @capacitor/assets, unused here), which would otherwise fail on this # Alpine/musl stage with no prebuilt binary and no compiler. The workspace allowBuilds stay for local # installs (where android:assets does need sharp built). COPY ui/package.json ui/pnpm-lock.yaml ui/pnpm-workspace.yaml ./ RUN pnpm install --frozen-lockfile --ignore-scripts COPY ui ./ RUN pnpm build # --- landing ------------------------------------------------------------------- # The public landing page as its own static container: the same Vite build # served by caddy at /, so stray public traffic is absorbed by static file # serving and never reaches the Go edge. FROM caddy:2-alpine AS landing COPY deploy/landing/Caddyfile /etc/caddy/Caddyfile COPY --from=ui /ui/dist /srv # --- Go build ---------------------------------------------------------------- FROM golang:1.26.3-alpine AS build WORKDIR /src COPY go.work go.work.sum ./ COPY pkg ./pkg COPY gateway ./gateway # Replace the committed placeholder with the freshly built UI before compiling, so # go:embed bakes the real bundle into the binary. The landing shell ships in the # landing image, not in the gateway. RUN rm -rf gateway/internal/webui/dist COPY --from=ui /ui/dist gateway/internal/webui/dist RUN rm gateway/internal/webui/dist/landing.html # Reduce the workspace to what the gateway needs: gateway + pkg (loadtest is not in # this context; its scrabble/gateway replace targets ./gateway, which is present here). RUN go work edit -dropuse=./backend -dropuse=./platform/telegram -dropuse=./loadtest # VERSION (the deploy passes the git tag) is stamped into the binary via the linker. ARG VERSION=dev RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-X scrabble/pkg/version.Version=${VERSION}" -o /out/gateway ./gateway/cmd/gateway # --- runtime ----------------------------------------------------------------- FROM gcr.io/distroless/static-debian12:nonroot AS gateway COPY --from=build /out/gateway /usr/local/bin/gateway ENTRYPOINT ["/usr/local/bin/gateway"]