From a4000680531f4bb87ae18df210779c0e4b940435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Sat, 15 Nov 2025 17:21:25 +0100 Subject: [PATCH] feat: add production Docker setup with multi-stage builds - Add production Dockerfiles for frontend and backend * Frontend: multi-stage build with nginx serving static files * Backend: multi-stage build with Prisma generation - Create production nginx configuration (nginx/conf.d.prod/) * Routes to frontend-prod:80 and backend-prod:3000 * Supports WebSocket connections for Socket.IO - Update docker-compose.yml to use production config * Add env_file support for backend-prod * Mount production nginx config directory - Add .env.production.example template for deployment --- backend/.env.production.example | 67 ++++++--------------------------- backend/Dockerfile.prod | 57 ++++++++++++++++++++++++++++ docker-compose.yml | 4 +- frontend/Dockerfile.prod | 36 ++++++++++++++++++ nginx/conf.d.prod/default.conf | 64 +++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 57 deletions(-) create mode 100644 backend/Dockerfile.prod create mode 100644 frontend/Dockerfile.prod create mode 100644 nginx/conf.d.prod/default.conf diff --git a/backend/.env.production.example b/backend/.env.production.example index 97ba90c..22485d5 100644 --- a/backend/.env.production.example +++ b/backend/.env.production.example @@ -1,69 +1,24 @@ -# Production Environment Configuration -# NEVER commit this file with real values! -# Use environment variables or secrets manager in production - # Server NODE_ENV=production PORT=3000 -# CORS - Your production domains -CORS_ORIGIN=https://spotlight.cam,https://www.spotlight.cam +# CORS +CORS_ORIGIN=http://localhost -# Database - Use managed database or strong credentials -# NEVER use default passwords in production! -DATABASE_URL=postgresql://prod_user:STRONG_PASSWORD_HERE@db:5432/spotlightcam_prod +# Database (production) +DATABASE_URL=postgresql://spotlightcam:spotlightcam123@db-prod:5432/spotlightcam?schema=public -# JWT - CRITICAL: Generate strong secrets -# Generate with: openssl rand -base64 64 -JWT_SECRET=CHANGE_THIS_TO_RANDOM_64_CHAR_STRING +# JWT (CHANGE THESE IN PRODUCTION!) +JWT_SECRET=production-secret-key-CHANGE-THIS-IN-REAL-PRODUCTION JWT_EXPIRES_IN=24h -# AWS SES - Production credentials -# BEST PRACTICE: Use IAM roles instead of access keys -AWS_REGION=us-east-1 -AWS_ACCESS_KEY_ID=AK......... -AWS_SECRET_ACCESS_KEY=change-it +# AWS SES (REPLACE WITH YOUR CREDENTIALS) +AWS_REGION=eu-central-1 +AWS_ACCESS_KEY_ID=your-aws-access-key-id +AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key SES_FROM_EMAIL=noreply@spotlight.cam SES_FROM_NAME=spotlight.cam # Email Settings -FRONTEND_URL=https://spotlight.cam +FRONTEND_URL=http://localhost VERIFICATION_TOKEN_EXPIRY=24h - -# Security Settings - Production (strict) -RATE_LIMIT_ENABLED=true -RATE_LIMIT_WINDOW_MS=900000 -RATE_LIMIT_MAX=100 -RATE_LIMIT_AUTH_MAX=5 -RATE_LIMIT_EMAIL_MAX=3 -ENABLE_CSRF=true -BODY_SIZE_LIMIT=10kb -LOG_LEVEL=warn - -# Password Policy - Enforced in production -PASSWORD_MIN_LENGTH=8 -PASSWORD_REQUIRE_UPPERCASE=true -PASSWORD_REQUIRE_LOWERCASE=true -PASSWORD_REQUIRE_NUMBER=true -PASSWORD_REQUIRE_SPECIAL=false - -# Account Lockout - Enabled in production -ENABLE_ACCOUNT_LOCKOUT=true -MAX_LOGIN_ATTEMPTS=5 -LOCKOUT_DURATION_MINUTES=15 - -# Database Connection Pool -DB_POOL_MIN=2 -DB_POOL_MAX=10 - -# Monitoring (optional) -SENTRY_DSN= -NEW_RELIC_LICENSE_KEY= - -# IMPORTANT SECURITY NOTES: -# 1. Generate JWT_SECRET with: openssl rand -base64 64 -# 2. Use AWS IAM roles instead of access keys when possible -# 3. Use environment variables or secrets manager (AWS Secrets Manager, HashiCorp Vault) -# 4. Never commit .env files to version control -# 5. Rotate all secrets regularly (every 90 days) -# 6. Use strong database passwords (20+ characters, mixed case, numbers, symbols) diff --git a/backend/Dockerfile.prod b/backend/Dockerfile.prod new file mode 100644 index 0000000..c885687 --- /dev/null +++ b/backend/Dockerfile.prod @@ -0,0 +1,57 @@ +# Multi-stage build for production + +# Stage 1: Build +FROM node:20-alpine AS builder + +# Install OpenSSL for Prisma +RUN apk add --no-cache openssl + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install all dependencies (including devDependencies for Prisma generation) +RUN npm ci + +# Copy application files +COPY . . + +# Generate Prisma Client +RUN npx prisma generate + +# Stage 2: Production +FROM node:20-alpine + +# Install OpenSSL for Prisma +RUN apk add --no-cache openssl + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install only production dependencies +RUN npm ci --only=production + +# Copy application files and generated Prisma Client from builder +COPY --from=builder /app/src ./src +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma +COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma + +# Copy entrypoint script +COPY docker-entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +# Set environment to production +ENV NODE_ENV=production + +# Expose port +EXPOSE 3000 + +# Set entrypoint +ENTRYPOINT ["docker-entrypoint.sh"] + +# Start the application +CMD ["node", "src/server.js"] diff --git a/docker-compose.yml b/docker-compose.yml index ac1b6f2..c77d50f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,7 +30,7 @@ services: - backend-prod volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/conf.d.prod:/etc/nginx/conf.d:ro - ./ssl:/etc/nginx/ssl:ro ports: - "80:80" @@ -122,6 +122,8 @@ services: args: - NODE_ENV=production container_name: spotlightcam-backend-prod + env_file: + - ./backend/.env.production expose: - "3000" environment: diff --git a/frontend/Dockerfile.prod b/frontend/Dockerfile.prod new file mode 100644 index 0000000..00a9e13 --- /dev/null +++ b/frontend/Dockerfile.prod @@ -0,0 +1,36 @@ +# Multi-stage build for production + +# Stage 1: Build +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production --ignore-scripts + +# Copy all dependencies for build (including devDependencies) +RUN npm install + +# Copy project files +COPY . . + +# Build the application +RUN npm run build + +# Stage 2: Production +FROM nginx:alpine + +# Copy built assets from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy nginx configuration (if needed) +# COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port +EXPOSE 80 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx/conf.d.prod/default.conf b/nginx/conf.d.prod/default.conf new file mode 100644 index 0000000..d3a56f0 --- /dev/null +++ b/nginx/conf.d.prod/default.conf @@ -0,0 +1,64 @@ +upstream frontend { + server frontend-prod:80; +} + +upstream backend { + server backend-prod:3000; +} + +server { + listen 80; + server_name localhost; + + client_max_body_size 500M; + + # Frontend - Static files served by nginx + location / { + proxy_pass http://frontend; + proxy_http_version 1.1; + + # Standard proxy headers + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # Backend API + location /api { + proxy_pass http://backend; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # WebSocket for Socket.IO + location /socket.io { + proxy_pass http://backend; + proxy_http_version 1.1; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Timeouts for WebSocket + proxy_connect_timeout 7d; + proxy_send_timeout 7d; + proxy_read_timeout 7d; + } +}