recipe-manager/docs/docker-frontend.md

226 lines
5.5 KiB
Markdown

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