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
This commit is contained in:
@@ -1,69 +1,24 @@
|
|||||||
# Production Environment Configuration
|
|
||||||
# NEVER commit this file with real values!
|
|
||||||
# Use environment variables or secrets manager in production
|
|
||||||
|
|
||||||
# Server
|
# Server
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
# CORS - Your production domains
|
# CORS
|
||||||
CORS_ORIGIN=https://spotlight.cam,https://www.spotlight.cam
|
CORS_ORIGIN=http://localhost
|
||||||
|
|
||||||
# Database - Use managed database or strong credentials
|
# Database (production)
|
||||||
# NEVER use default passwords in production!
|
DATABASE_URL=postgresql://spotlightcam:spotlightcam123@db-prod:5432/spotlightcam?schema=public
|
||||||
DATABASE_URL=postgresql://prod_user:STRONG_PASSWORD_HERE@db:5432/spotlightcam_prod
|
|
||||||
|
|
||||||
# JWT - CRITICAL: Generate strong secrets
|
# JWT (CHANGE THESE IN PRODUCTION!)
|
||||||
# Generate with: openssl rand -base64 64
|
JWT_SECRET=production-secret-key-CHANGE-THIS-IN-REAL-PRODUCTION
|
||||||
JWT_SECRET=CHANGE_THIS_TO_RANDOM_64_CHAR_STRING
|
|
||||||
JWT_EXPIRES_IN=24h
|
JWT_EXPIRES_IN=24h
|
||||||
|
|
||||||
# AWS SES - Production credentials
|
# AWS SES (REPLACE WITH YOUR CREDENTIALS)
|
||||||
# BEST PRACTICE: Use IAM roles instead of access keys
|
AWS_REGION=eu-central-1
|
||||||
AWS_REGION=us-east-1
|
AWS_ACCESS_KEY_ID=your-aws-access-key-id
|
||||||
AWS_ACCESS_KEY_ID=AK.........
|
AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key
|
||||||
AWS_SECRET_ACCESS_KEY=change-it
|
|
||||||
SES_FROM_EMAIL=noreply@spotlight.cam
|
SES_FROM_EMAIL=noreply@spotlight.cam
|
||||||
SES_FROM_NAME=spotlight.cam
|
SES_FROM_NAME=spotlight.cam
|
||||||
|
|
||||||
# Email Settings
|
# Email Settings
|
||||||
FRONTEND_URL=https://spotlight.cam
|
FRONTEND_URL=http://localhost
|
||||||
VERIFICATION_TOKEN_EXPIRY=24h
|
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)
|
|
||||||
|
|||||||
57
backend/Dockerfile.prod
Normal file
57
backend/Dockerfile.prod
Normal file
@@ -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"]
|
||||||
@@ -30,7 +30,7 @@ services:
|
|||||||
- backend-prod
|
- backend-prod
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
- ./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
|
- ./ssl:/etc/nginx/ssl:ro
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
@@ -122,6 +122,8 @@ services:
|
|||||||
args:
|
args:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
container_name: spotlightcam-backend-prod
|
container_name: spotlightcam-backend-prod
|
||||||
|
env_file:
|
||||||
|
- ./backend/.env.production
|
||||||
expose:
|
expose:
|
||||||
- "3000"
|
- "3000"
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
36
frontend/Dockerfile.prod
Normal file
36
frontend/Dockerfile.prod
Normal file
@@ -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;"]
|
||||||
64
nginx/conf.d.prod/default.conf
Normal file
64
nginx/conf.d.prod/default.conf
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user