# 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)_
