Goal: Build a Docker image for the app, ready for cloud deployment.
The Dockerfile uses a multi-stage build:
- Stage 1 (Build): Install all dependencies, build the Vite frontend
- Stage 2 (Production): Copy only what's needed, keep the image small
MSAL requires the Client ID and Authority at build time (they're baked into the JavaScript bundle via import.meta.env), so we pass them as build arguments.
# ── Stage 1: Build ──
FROM node:22-alpine AS build
WORKDIR /app
# MSAL values must be available at build time for Vite
ARG VITE_MSAL_CLIENT_ID
ARG VITE_MSAL_AUTHORITY
ENV VITE_MSAL_CLIENT_ID=$VITE_MSAL_CLIENT_ID
ENV VITE_MSAL_AUTHORITY=$VITE_MSAL_AUTHORITY
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # Vite bundles frontend → dist/
# ── Stage 2: Production ──
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev && npm i tsx
COPY --from=build /app/dist ./dist
COPY --from=build /app/server ./server
COPY --from=build /app/tsconfig.server.json ./
EXPOSE 3000
CMD ["npx", "tsx", "server/index.ts"]Key points:
ARG+ENVforVITE_*vars lets Vite embed them at build timenpm ci --omit=devin Stage 2 → only production dependencies (smaller image)- The server uses
tsxto run TypeScript directly (no compilation step)
docker build \
--build-arg VITE_MSAL_CLIENT_ID=YOUR_APP_CLIENT_ID \
--build-arg VITE_MSAL_AUTHORITY=https://login.microsoftonline.com/YOUR_TENANT_ID \
-t presentation-maker:local \
.Replace the values with YOUR Client ID and Tenant ID from Challenge 02.
docker run -it --rm \
-p 3000:3000 \
-e AZURE_OPENAI_ENDPOINT=https://YOUR_OPENAI_RESOURCE.openai.azure.com/ \
-e BING_SEARCH_API_KEY="" \
presentation-maker:localNote:
DefaultAzureCredentialwon't find youraz loginsession inside Docker. To test locally you would need to mount Azure credentials or use a service principal. For now, just verify the container starts and the frontend loads.
Open http://localhost:3000 — you should see the login page.
docker images presentation-maker:localWith the multi-stage build, the image should be roughly 200-300 MB (Node.js Alpine + dependencies).
| Variable | When Needed | How Set |
|---|---|---|
VITE_MSAL_CLIENT_ID |
Build time | --build-arg (baked into JS bundle) |
VITE_MSAL_AUTHORITY |
Build time | --build-arg (baked into JS bundle) |
AZURE_OPENAI_ENDPOINT |
Run time | -e flag or Container Apps config |
BING_SEARCH_API_KEY |
Run time | -e flag or Container Apps config |
Why? Vite replaces
import.meta.env.VITE_*at build time with literal values. Server-sideprocess.env.*variables are read at runtime.
- Docker image builds without errors
- Container starts and serves the frontend on port 3000
- You understand the difference between build-time and run-time variables
Previous: Challenge 03 — Entra ID | Next: Challenge 05 — Deploy to Azure