From 6f52b740377463753f5459879f3cea38376d4721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Thu, 20 Nov 2025 21:14:27 +0100 Subject: [PATCH] chore: add Docker build configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Dockerfile | 41 ++++++++++++++++++++++++ nginx.conf | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 Dockerfile create mode 100644 nginx.conf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a0bffd0 --- /dev/null +++ b/Dockerfile @@ -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;"] diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..27ef251 --- /dev/null +++ b/nginx.conf @@ -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; + } + } +}