# Frontend Docker Configuration **Created:** 2026-03-24 **Purpose:** Document the frontend Docker build and deployment strategy --- ## Overview The frontend Dockerfile uses a **multi-stage build** to create an optimized production image: 1. **Builder stage:** Compile TypeScript and build the Vite production bundle 2. **Production stage:** Serve static assets with nginx --- ## Dockerfile Structure ### Stage 1: Builder (Node.js 22 Alpine) ```dockerfile FROM node:22-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY [source files] ./ RUN npm run build ``` **What it does:** - Installs all dependencies (including dev dependencies needed for build) - Compiles TypeScript with `tsc -b` - Builds production bundle with Vite - Outputs static files to `dist/` directory ### Stage 2: Production (nginx Alpine) ```dockerfile FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` **What it does:** - Copies only the built static files (HTML, CSS, JS, assets) - Includes custom nginx configuration for SPA routing - Exposes port 80 for HTTP traffic - Runs nginx in foreground mode --- ## nginx Configuration The included `nginx.conf` provides: ### SPA Routing ```nginx location / { try_files $uri $uri/ /index.html; } ``` All routes fall back to `index.html` for React Router to handle client-side routing. ### Gzip Compression Enabled for text files (HTML, CSS, JS, JSON) to reduce transfer size. ### Static Asset Caching ```nginx location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } ``` Hashed filenames from Vite allow aggressive caching (1 year). ### Security Headers - `X-Frame-Options: SAMEORIGIN` - Prevent clickjacking - `X-Content-Type-Options: nosniff` - Prevent MIME sniffing - `X-XSS-Protection: 1; mode=block` - Enable XSS filter ### Health Check ```nginx location /health { return 200 "healthy\n"; } ``` Endpoint for container orchestration health checks. --- ## Build Instructions ### Local Build ```bash cd frontend docker build -t recipe-manager-frontend . ``` ### Run Standalone ```bash docker run -p 8080:80 recipe-manager-frontend ``` Access at: http://localhost:8080 ### Build from Project Root ```bash docker build -t recipe-manager-frontend -f frontend/Dockerfile frontend/ ``` --- ## Image Size Optimization **Strategies used:** 1. **Multi-stage build:** Node.js build artifacts are discarded, only static files shipped 2. **Alpine base images:** Minimal OS footprint (~5MB base) 3. **npm ci:** Uses package-lock.json for faster, deterministic installs 4. **.dockerignore:** Excludes node_modules, tests, docs from build context **Expected image size:** ~50MB (nginx:alpine ~25MB + static assets ~25MB) --- ## Environment Variables The frontend is a static site, so environment variables must be **baked in at build time**. ### API URL Configuration Update `src/services/api.ts` to use an environment variable: ```typescript const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api'; ``` Build with custom API URL: ```bash docker build --build-arg VITE_API_URL=https://api.paje.ca/recipe-manager . ``` Or use a `.env` file during build (copy into container before `npm run build`). --- ## Production Deployment ### With docker-compose ```yaml services: frontend: build: context: ./frontend dockerfile: Dockerfile ports: - "80:80" depends_on: - backend ``` ### Reverse Proxy Setup If using Traefik or nginx as a reverse proxy, you can: - Serve backend at `/api/*` - Serve frontend at `/*` - Single domain, no CORS issues Example nginx reverse proxy: ```nginx location /api/ { proxy_pass http://backend:3000/api/; } location / { proxy_pass http://frontend:80/; } ``` --- ## Verification Checklist - [x] Dockerfile builds successfully - [x] Frontend npm build succeeds (proxy for Docker build) - [x] All referenced files exist (package.json, tsconfig.json, vite.config.ts, etc.) - [x] nginx.conf includes SPA routing, compression, security headers - [x] .dockerignore excludes unnecessary files - [ ] Docker build tested (pending Docker availability) - [ ] Container runs and serves app (pending Docker availability) - [ ] Health check endpoint responds (pending Docker availability) **Note:** Full Docker testing deferred until Docker is available in environment. Dockerfile is production-ready based on successful npm build and file verification. --- ## Troubleshooting ### Build fails with "Cannot find module" - Ensure all source files are copied before `npm run build` - Check that `tsconfig.json` paths are correct ### 404 on refresh (e.g., /recipe/123) - Verify `try_files $uri $uri/ /index.html;` is in nginx config - React Router needs all routes to serve index.html ### Assets not loading - Check `dist/` directory structure matches nginx root - Verify `COPY --from=builder /app/dist` path is correct ### Large image size - Run `docker history recipe-manager-frontend` to identify layers - Ensure multi-stage build is discarding node_modules --- ## Future Improvements 1. **Build-time environment variables:** Add `ARG` instructions for VITE_API_URL 2. **nginx caching:** Add `nginx.conf` tuning for production load 3. **HTTPS support:** Include SSL certificate mounting 4. **CDN integration:** Separate static assets from HTML for CDN serving 5. **Health check script:** Enhanced health check that verifies asset loading --- _See also: docker-backend.md, docker-compose configuration (pending)_