recipe-manager/docs/docker-frontend.md

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:

  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)

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 clickjacking
  • X-Content-Type-Options: nosniff - Prevent MIME sniffing
  • X-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:

  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:

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.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)