5.5 KiB
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:
- Builder stage: Compile TypeScript and build the Vite production bundle
- Production stage: Serve static assets with nginx
Dockerfile Structure
Stage 1: Builder (Node.js 22 Alpine)
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)
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
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
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 clickjackingX-Content-Type-Options: nosniff- Prevent MIME sniffingX-XSS-Protection: 1; mode=block- Enable XSS filter
Health Check
location /health {
return 200 "healthy\n";
}
Endpoint for container orchestration health checks.
Build Instructions
Local Build
cd frontend
docker build -t recipe-manager-frontend .
Run Standalone
docker run -p 8080:80 recipe-manager-frontend
Access at: http://localhost:8080
Build from Project Root
docker build -t recipe-manager-frontend -f frontend/Dockerfile frontend/
Image Size Optimization
Strategies used:
- Multi-stage build: Node.js build artifacts are discarded, only static files shipped
- Alpine base images: Minimal OS footprint (~5MB base)
- npm ci: Uses package-lock.json for faster, deterministic installs
- .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:
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api';
Build with custom API URL:
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
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:
location /api/ {
proxy_pass http://backend:3000/api/;
}
location / {
proxy_pass http://frontend:80/;
}
Verification Checklist
- Dockerfile builds successfully
- Frontend npm build succeeds (proxy for Docker build)
- All referenced files exist (package.json, tsconfig.json, vite.config.ts, etc.)
- nginx.conf includes SPA routing, compression, security headers
- .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.jsonpaths 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/distpath is correct
Large image size
- Run
docker history recipe-manager-frontendto identify layers - Ensure multi-stage build is discarding node_modules
Future Improvements
- Build-time environment variables: Add
ARGinstructions for VITE_API_URL - nginx caching: Add
nginx.conftuning for production load - HTTPS support: Include SSL certificate mounting
- CDN integration: Separate static assets from HTML for CDN serving
- Health check script: Enhanced health check that verifies asset loading
See also: docker-backend.md, docker-compose configuration (pending)