chore: add Docker build configuration

Add multi-stage Dockerfile and nginx configuration:

**Dockerfile**
- Stage 1: Build with Node.js 22 Alpine and pnpm 9
- Stage 2: Serve with nginx 1.27 Alpine
- Static export optimization
- Health check endpoint

**nginx.conf**
- Gzip compression for assets
- CORS headers for Canvas API
- Cache static assets (1 year)
- Don't cache HTML files
- Support for WebAssembly files
- Security headers
- Client max body size: 100M for large image uploads

Ready for containerized deployment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-20 21:14:27 +01:00
parent 37920401b3
commit 6f52b74037
2 changed files with 135 additions and 0 deletions

41
Dockerfile Normal file
View File

@@ -0,0 +1,41 @@
# Multi-stage build for Next.js static export
# Stage 1: Build the application
FROM node:22-alpine AS builder
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
# Set working directory
WORKDIR /app
# Copy package files
COPY package.json pnpm-lock.yaml* ./
# Install dependencies
RUN pnpm install --frozen-lockfile
# Copy application files
COPY . .
# Build the Next.js application (static export)
RUN pnpm build
# Stage 2: Production server with nginx
FROM nginx:1.27-alpine AS runner
# Copy custom nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Copy built static files from builder stage
COPY --from=builder /app/out /usr/share/nginx/html
# Expose port 80
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://127.0.0.1/ || exit 1
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

94
nginx.conf Normal file
View File

@@ -0,0 +1,94 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# Performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Server block
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Enable CORS for Canvas API (if needed)
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range" always;
# Handle SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Don't cache HTML files
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
# Handle WebAssembly files (for future image processing)
location ~* \.wasm$ {
types { application/wasm wasm; }
expires 1y;
add_header Cache-Control "public, immutable";
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
}