diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 0000000..031c9d2 --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1,96 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Build outputs +.next +out +dist +build +*.tsbuildinfo + +# Environment files (these are passed via docker-compose) +.env +.env.local +.env.*.local +.env.dev +.env.test +.env.production + +# Version control +.git +.gitignore +.gitattributes + +# IDE and editor files +.vscode +.idea +*.swp +*.swo +*~ +.DS_Store +Thumbs.db + +# Testing +coverage +.nyc_output +*.test.ts +*.test.tsx +*.spec.ts +*.spec.tsx +tests +test +__tests__ + +# Documentation (not needed in production image) +*.md +!README.md +LICENSE +CHANGELOG.md +CONTRIBUTING.md + +# Docker files (avoid recursive copy) +docker +docker-compose*.yml +Dockerfile* +.dockerignore + +# CI/CD +.github +.gitlab-ci.yml +.travis.yml +azure-pipelines.yml +Jenkinsfile + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Misc +.cache +temp +tmp +.tmp +uploads/* +!uploads/.gitkeep + +# Large binary files +*.mp4 +*.mov +*.avi +*.mkv +*.pdf +*.zip +*.tar.gz +*.rar diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..d54b873 --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,54 @@ +# =========================================== +# Synapsis Docker Environment Configuration +# =========================================== +# Copy this file to .env and configure your values + +# =========================================== +# Required Settings +# =========================================== + +# Domain Configuration +# Replace with your actual domain +DOMAIN=your-domain.com + +# Database Credentials +# Change these to secure values! +DB_USER=synapsis +DB_PASSWORD=your-secure-password-here +DB_NAME=synapsis + +# Authentication Secret +# Generate with: openssl rand -hex 32 +AUTH_SECRET=your-secret-key-here-minimum-32-characters + +# Admin Users +# Comma-separated list of email addresses with admin access +ADMIN_EMAILS=admin@your-domain.com + +# =========================================== +# S3-Compatible Storage (Required for uploads) +# =========================================== +# You can use AWS S3, MinIO, Wasabi, Backblaze B2, etc. + +STORAGE_ENDPOINT=https://s3.your-provider.com +STORAGE_REGION=us-east-1 +STORAGE_BUCKET=synapsis-uploads +STORAGE_ACCESS_KEY=your-access-key +STORAGE_SECRET_KEY=your-secret-key +STORAGE_PUBLIC_BASE_URL=https://cdn.your-domain.com + +# =========================================== +# Optional Settings +# =========================================== + +# Maximum bots per user (default: 5) +# BOT_MAX_PER_USER=5 + +# Redis configuration (if using external Redis) +# REDIS_URL=redis://redis:6379 + +# =========================================== +# Docker Compose Settings +# =========================================== + +# COMPOSE_PROJECT_NAME=synapsis diff --git a/docker/Caddyfile b/docker/Caddyfile new file mode 100644 index 0000000..aba0d41 --- /dev/null +++ b/docker/Caddyfile @@ -0,0 +1,40 @@ +# Caddyfile for Synapsis +# Automatic HTTPS via Let's Encrypt + +{$DOMAIN} { + # Reverse proxy to Synapsis app + reverse_proxy app:3000 + + # Enable compression + encode gzip + + # Security headers + header { + # Enable HSTS + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + # Prevent clickjacking + X-Frame-Options "SAMEORIGIN" + # Prevent MIME type sniffing + X-Content-Type-Options "nosniff" + # XSS protection + X-XSS-Protection "1; mode=block" + # Referrer policy + Referrer-Policy "strict-origin-when-cross-origin" + } + + # Logging + log { + output file /data/access.log + format json + } + + # Increase max body size for uploads (20MB) + request_body { + max_size 20MB + } +} + +# Health check endpoint for Docker +:8080 { + respond /health "healthy" 200 +} diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..dc4d6d0 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,90 @@ +# Synapsis - Multi-stage Production Dockerfile +# This Dockerfile creates a production-ready image that: +# - Keeps source files immutable (prevents accidental modifications) +# - Optimizes build size with multi-stage approach +# - Runs as non-root user for security + +# ============================================ +# Stage 1: Dependencies +# ============================================ +FROM node:22-alpine AS deps +RUN apk add --no-cache libc6-compat openssl + +WORKDIR /app + +# Copy package files for efficient layer caching +COPY package.json package-lock.json* ./ + +# Install production dependencies only (using ci for reproducible builds) +RUN npm ci --only=production --ignore-scripts && npm cache clean --force + +# ============================================ +# Stage 2: Builder +# ============================================ +FROM node:22-alpine AS builder +RUN apk add --no-cache libc6-compat openssl + +WORKDIR /app + +# Copy dependencies from deps stage +COPY --from=deps /app/node_modules ./node_modules + +# Copy all source files +COPY . . + +# Set environment variables for build +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Build the Next.js application +RUN npm run build + +# ============================================ +# Stage 3: Production Runner +# ============================================ +FROM node:22-alpine AS runner +RUN apk add --no-cache libc6-compat openssl + +WORKDIR /app + +# Create non-root user for security +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs + +# Set production environment +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Copy public assets +COPY --from=builder /app/public ./public + +# Copy Next.js build output +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# Copy drizzle migrations for runtime database updates +COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle +COPY --from=builder --chown=nextjs:nodejs /app/drizzle.config.ts ./ +COPY --from=builder --chown=nextjs:nodejs /app/package.json ./ + +# Copy entrypoint script +COPY --from=builder --chown=nextjs:nodejs /app/docker/docker-entrypoint.sh ./ +RUN chmod +x ./docker-entrypoint.sh + +# Switch to non-root user +USER nextjs + +# Expose the application port +EXPOSE 3000 + +# Health check to ensure container is running properly +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))" + +# Set the entrypoint +ENTRYPOINT ["./docker-entrypoint.sh"] + +# Default command (can be overridden) +CMD ["node", "server.js"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..51255e9 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,325 @@ +# Synapsis Docker Production Deployment + +One-command Docker deployment for Synapsis that solves the "user modifies file and breaks git sync" problem. + +## 🚀 Quick Start + +```bash +cd /var/www/Synapsis/docker + +# 1. Copy and edit environment variables +cp .env.example .env +nano .env # Edit all required values + +# 2. Start the stack +docker-compose up -d + +# 3. Check logs +docker-compose logs -f +``` + +Your Synapsis instance will be available at `http://localhost` (or your configured domain). + +--- + +## 📋 Server Requirements + +| Resource | Minimum | Recommended | +|----------|---------|-------------| +| RAM | 2 GB | 4 GB | +| CPU | 2 cores | 4 cores | +| Disk | 20 GB SSD | 50 GB SSD | +| OS | Ubuntu 22.04/24.04, Debian 12, or compatible | + +### Required Software +- Docker 24.0+ +- Docker Compose 2.20+ + +### Installation (Ubuntu/Debian) +```bash +# Install Docker +curl -fsSL https://get.docker.com | sh +sudo usermod -aG docker $USER +newgrp docker + +# Verify +docker --version +docker-compose --version +``` + +--- + +## 🔧 Configuration + +### 1. Environment Variables + +Copy `.env.example` to `.env` and configure: + +```bash +cd /var/www/Synapsis/docker +cp .env.example .env +``` + +**Required settings:** +- `DOMAIN` - Your domain name (e.g., `synapsis.example.com`) +- `DB_PASSWORD` - Strong database password +- `AUTH_SECRET` - Generate with `openssl rand -hex 32` +- `ADMIN_EMAILS` - Admin user email(s) +- `STORAGE_*` - S3-compatible storage credentials + +### 2. SSL/HTTPS Setup + +#### Option A: Let's Encrypt (Recommended) + +1. Start with HTTP first: +```bash +docker-compose up -d +``` + +2. Obtain SSL certificates: +```bash +docker run -it --rm \ + -v ./certbot_data:/etc/letsencrypt \ + -v ./certbot_www:/var/www/certbot \ + -p 80:80 \ + certbot/certbot certonly \ + --standalone \ + -d your-domain.com +``` + +3. Enable SSL configuration: +```bash +cd nginx/conf.d +mv default.conf default.conf.http +mv default.conf.ssl default.conf +# Edit default.conf and replace ${DOMAIN} with your actual domain +docker-compose restart nginx +``` + +4. Uncomment the certbot service in docker-compose.yml for auto-renewal. + +#### Option B: Custom SSL Certificates + +Place your certificates in `./certbot_data/live/your-domain.com/`: +- `fullchain.pem` +- `privkey.pem` + +--- + +## 🔄 Updates + +### Standard Update Process + +```bash +cd /var/www/Synapsis/docker + +# 1. Pull latest changes +git pull origin main + +# 2. Rebuild and restart +docker-compose down +docker-compose pull # If using pre-built images +docker-compose up -d --build + +# 3. Run migrations (if needed) +docker-compose exec app npx drizzle-kit push + +# 4. Verify +docker-compose ps +docker-compose logs -f app +``` + +### One-Command Update Script + +Create `update.sh`: +```bash +#!/bin/bash +cd /var/www/Synapsis/docker +git pull origin main +docker-compose down +docker-compose up -d --build +docker-compose exec -T app npx drizzle-kit push || true +echo "✅ Update complete!" +``` + +--- + +## 📁 Directory Structure + +``` +/var/www/Synapsis/docker/ +├── docker-compose.yml # Main orchestration file +├── Dockerfile # Multi-stage build +├── docker-entrypoint.sh # Startup script +├── .env # Your environment variables +├── .env.example # Template +├── .dockerignore # Build exclusions +├── nginx/ +│ ├── nginx.conf # Main Nginx config +│ └── conf.d/ +│ ├── default.conf # HTTP config +│ └── default.conf.ssl # HTTPS config +└── README.md # This file +``` + +--- + +## 🛠️ Management Commands + +### View Logs +```bash +docker-compose logs -f app # Application logs +docker-compose logs -f nginx # Nginx logs +docker-compose logs -f postgres # Database logs +docker-compose logs -f # All logs +``` + +### Database Operations +```bash +# Access database shell +docker-compose exec postgres psql -U synapsis -d synapsis + +# Backup database +docker-compose exec postgres pg_dump -U synapsis synapsis > backup.sql + +# Restore database +docker-compose exec -T postgres psql -U synapsis -d synapsis < backup.sql + +# Run migrations manually +docker-compose exec app npx drizzle-kit push +``` + +### Container Management +```bash +# Restart services +docker-compose restart app +docker-compose restart nginx + +# Stop everything +docker-compose down + +# Stop and remove volumes (⚠️ destroys data!) +docker-compose down -v + +# View running containers +docker-compose ps + +# Enter container shell +docker-compose exec app sh +docker-compose exec postgres sh +``` + +--- + +## 🔒 Security Features + +This Docker setup includes: + +- **Immutable source code** - Application runs from image, not bind-mounted files +- **Non-root execution** - App runs as unprivileged user +- **Network isolation** - Services communicate via internal Docker network +- **Rate limiting** - Nginx protects against brute force attacks +- **Security headers** - X-Frame-Options, X-Content-Type-Options, etc. +- **Resource limits** - Memory constraints on all containers +- **Health checks** - Automatic container health monitoring + +--- + +## 🔍 Troubleshooting + +### Container won't start +```bash +# Check for configuration errors +docker-compose config + +# View detailed logs +docker-compose logs app --tail=100 +``` + +### Database connection failed +```bash +# Check database is healthy +docker-compose ps + +# Verify environment variables +docker-compose exec app env | grep DATABASE +``` + +### SSL certificate issues +```bash +# Test SSL configuration +docker-compose exec nginx nginx -t + +# Check certificate files +ls -la certbot_data/live/ +``` + +### Port already in use +```bash +# Find process using port 80/443 +sudo netstat -tlnp | grep :80 + +# Change ports in docker-compose.yml if needed +``` + +--- + +## 📊 Monitoring + +### Health Checks +- App: `http://your-domain/api/health` +- Nginx: `http://your-domain/nginx-health` + +### Resource Usage +```bash +# Container stats +docker stats + +# Disk usage +docker system df +``` + +--- + +## 💾 Backup Strategy + +### Automated Backup Script + +Create `/var/www/Synapsis/docker/backup.sh`: +```bash +#!/bin/bash +BACKUP_DIR="/var/backups/synapsis" +DATE=$(date +%Y%m%d_%H%M%S) +mkdir -p $BACKUP_DIR + +# Database backup +docker-compose exec -T postgres pg_dump -U synapsis synapsis > "$BACKUP_DIR/db_$DATE.sql" + +# Uploads backup +tar -czf "$BACKUP_DIR/uploads_$DATE.tar.gz" -C /var/lib/docker/volumes/synapsis_uploads_data/_data . + +# Keep only last 7 days +find $BACKUP_DIR -type f -mtime +7 -delete + +echo "✅ Backup complete: $DATE" +``` + +Add to crontab: +```bash +0 2 * * * /var/www/Synapsis/docker/backup.sh >> /var/log/synapsis-backup.log 2>&1 +``` + +--- + +## 📞 Support + +For issues or questions: +1. Check logs: `docker-compose logs -f` +2. Review configuration: `docker-compose config` +3. Consult the main Synapsis documentation + +--- + +## 📝 License + +This Docker configuration follows the same license as Synapsis (Apache 2.0). diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..7ef630b --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,139 @@ +# Synapsis - Docker Compose +# One-command deployment: docker-compose up -d +# +# Services: +# - Synapsis Next.js application +# - PostgreSQL database (persistent storage) +# - Caddy reverse proxy with automatic SSL +# +# All state is stored in Docker volumes - source code is immutable in the image + +version: "3.8" + +services: + # ============================================ + # PostgreSQL Database + # ============================================ + postgres: + image: postgres:16-alpine + container_name: synapsis-db + restart: unless-stopped + environment: + POSTGRES_USER: ${DB_USER:-synapsis} + POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme} + POSTGRES_DB: ${DB_NAME:-synapsis} + PGDATA: /var/lib/postgresql/data/pgdata + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - synapsis-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-synapsis} -d ${DB_NAME:-synapsis}"] + interval: 10s + timeout: 5s + retries: 5 + deploy: + resources: + limits: + memory: 1G + reservations: + memory: 256M + + # ============================================ + # Synapsis Application + # ============================================ + app: + build: + context: .. + dockerfile: docker/Dockerfile + image: synapsis:latest + container_name: synapsis-app + restart: unless-stopped + environment: + # Database connection + DATABASE_URL: postgresql://${DB_USER:-synapsis}:${DB_PASSWORD:-changeme}@postgres:5432/${DB_NAME:-synapsis} + + # Authentication + AUTH_SECRET: ${AUTH_SECRET} + + # Domain configuration + NEXT_PUBLIC_NODE_DOMAIN: ${DOMAIN:-localhost} + + # Admin emails + ADMIN_EMAILS: ${ADMIN_EMAILS} + + # S3 Storage configuration + STORAGE_ENDPOINT: ${STORAGE_ENDPOINT} + STORAGE_REGION: ${STORAGE_REGION:-us-east-1} + STORAGE_BUCKET: ${STORAGE_BUCKET} + STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY} + STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY} + STORAGE_PUBLIC_BASE_URL: ${STORAGE_PUBLIC_BASE_URL} + + # Optional settings + BOT_MAX_PER_USER: ${BOT_MAX_PER_USER:-5} + + # Node environment + NODE_ENV: production + volumes: + # Uploads directory (if using local file storage) + - uploads_data:/app/uploads + networks: + - synapsis-network + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + deploy: + resources: + limits: + memory: 1G + reservations: + memory: 256M + + # ============================================ + # Caddy Reverse Proxy (Automatic SSL) + # ============================================ + caddy: + image: caddy:2-alpine + container_name: synapsis-caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + networks: + - synapsis-network + depends_on: + app: + condition: service_healthy + environment: + - DOMAIN=${DOMAIN:-localhost} + +# ============================================ +# Persistent Volumes +# ============================================ +volumes: + postgres_data: + driver: local + uploads_data: + driver: local + caddy_data: + driver: local + caddy_config: + driver: local + +# ============================================ +# Networks +# ============================================ +networks: + synapsis-network: + driver: bridge diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100644 index 0000000..2dbf3ef --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,101 @@ +#!/bin/sh +# Synapsis Docker Entrypoint Script +# Handles database migrations, seeding, and application startup + +set -e + +echo "========================================" +echo " Synapsis - Starting Application" +echo "========================================" + +# Function to wait for database +wait_for_db() { + echo "⏳ Waiting for PostgreSQL..." + + # Extract connection details from DATABASE_URL + # Format: postgresql://user:password@host:port/database + DB_HOST=$(echo "$DATABASE_URL" | sed -n 's/.*@\([^:]*\):.*/\1/p') + DB_PORT=$(echo "$DATABASE_URL" | sed -n 's/.*:\([0-9]*\)\/.*/\1/p') + + # Default values + DB_HOST=${DB_HOST:-postgres} + DB_PORT=${DB_PORT:-5432} + + max_retries=30 + retry_count=0 + + while ! nc -z "$DB_HOST" "$DB_PORT"; do + retry_count=$((retry_count + 1)) + if [ $retry_count -ge $max_retries ]; then + echo "❌ Failed to connect to database after $max_retries attempts" + exit 1 + fi + echo " Attempt $retry_count/$max_retries - waiting for $DB_HOST:$DB_PORT..." + sleep 2 + done + + echo "✅ Database is ready!" +} + +# Function to run database migrations +run_migrations() { + echo "" + echo "🔄 Running database migrations..." + + # Check if drizzle-kit is available (in dev dependencies) + if command -v drizzle-kit >/dev/null 2>&1; then + echo " Using drizzle-kit for migrations..." + drizzle-kit push --force || { + echo "⚠️ Migration push failed - database may already be up to date" + } + else + echo " ⚠️ drizzle-kit not found in production image" + echo " Migrations should be run manually during deployment:" + echo " docker-compose exec app npx drizzle-kit push" + fi + + echo "✅ Migration check complete" +} + +# Function to check/create initial admin user +check_admin_setup() { + echo "" + echo "👤 Checking admin setup..." + + # This is a placeholder - you can add logic here to ensure + # at least one admin user exists on first run + # For now, we rely on the application to handle this + + echo "✅ Admin check complete (handled by application)" +} + +# Install netcat for database connectivity check if not present +if ! command -v nc >/dev/null 2>&1; then + echo "📦 Installing netcat for database checks..." + # Note: In Alpine, nc is typically included in busybox + # If not, we'll proceed anyway and let the app handle connection + echo " (Skipping - using application-level retry logic)" +fi + +# Wait for database to be ready +wait_for_db + +# Run migrations +run_migrations + +# Check admin setup +check_admin_setup + +# Display startup info +echo "" +echo "========================================" +echo " 🚀 Starting Synapsis Server" +echo "========================================" +echo " Environment: $NODE_ENV" +echo " Port: $PORT" +echo " Database: Connected" +echo "========================================" +echo "" + +# Execute the main command (passed as arguments) +exec "$@" diff --git a/docker/nginx/conf.d/default.conf b/docker/nginx/conf.d/default.conf new file mode 100644 index 0000000..85cbbd8 --- /dev/null +++ b/docker/nginx/conf.d/default.conf @@ -0,0 +1,63 @@ +# HTTP to HTTPS redirect (run this first without SSL) +# After SSL certificates are obtained, use the SSL version below + +server { + listen 80; + server_name _; # Accept any server name (change to your domain for production) + + # Health check endpoint for Docker + location /nginx-health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Proxy all requests to the Next.js app + location / { + proxy_pass http://app:3000; + 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; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + proxy_read_timeout 86400; + } + + # Static files caching + location /_next/static { + proxy_pass http://app:3000; + proxy_cache_valid 200 365d; + add_header Cache-Control "public, immutable"; + } + + # API rate limiting + location /api/ { + limit_req zone=api burst=20 nodelay; + limit_conn addr 10; + + proxy_pass http://app:3000; + 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; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Auth endpoints - stricter rate limiting + location /api/auth/ { + limit_req zone=auth burst=5 nodelay; + limit_conn addr 5; + + proxy_pass http://app:3000; + 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; + } +} diff --git a/docker/nginx/conf.d/default.conf.ssl b/docker/nginx/conf.d/default.conf.ssl new file mode 100644 index 0000000..25ebb96 --- /dev/null +++ b/docker/nginx/conf.d/default.conf.ssl @@ -0,0 +1,91 @@ +# SSL Configuration (use this after obtaining certificates) +# Rename to default.conf after SSL setup + +server { + listen 80; + server_name ${DOMAIN}; + + # Certbot challenge + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + # Redirect all HTTP to HTTPS + location / { + return 301 https://$server_name$request_uri; + } +} + +server { + listen 443 ssl http2; + server_name ${DOMAIN}; + + # SSL certificates (mounted from certbot) + ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem; + + # SSL configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + # HSTS (enable after confirming HTTPS works) + # add_header Strict-Transport-Security "max-age=63072000" always; + + # Health check + location /nginx-health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Proxy all requests to the Next.js app + location / { + proxy_pass http://app:3000; + 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; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + proxy_read_timeout 86400; + } + + # Static files caching + location /_next/static { + proxy_pass http://app:3000; + proxy_cache_valid 200 365d; + add_header Cache-Control "public, immutable"; + } + + # API rate limiting + location /api/ { + limit_req zone=api burst=20 nodelay; + limit_conn addr 10; + + proxy_pass http://app:3000; + 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; + } + + # Auth endpoints - stricter rate limiting + location /api/auth/ { + limit_req zone=auth burst=5 nodelay; + limit_conn addr 5; + + proxy_pass http://app:3000; + 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; + } +} diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100644 index 0000000..1f7764d --- /dev/null +++ b/docker/nginx/nginx.conf @@ -0,0 +1,70 @@ +# Nginx configuration for Synapsis +# This configuration includes: +# - Gzip compression +# - Security headers +# - Rate limiting +# - Proper handling of Next.js static files + +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging format + 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 50M; + + # 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; + + # Rate limiting zones + limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m; + limit_conn_zone $binary_remote_addr zone=addr:10m; + + # 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 "strict-origin-when-cross-origin" always; + + # Include server configurations + include /etc/nginx/conf.d/*.conf; +} diff --git a/next.config.ts b/next.config.ts index c64487c..f85eeaa 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,11 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - // Empty turbopack config to silence the warning + // Enable standalone output for Docker deployment + // This creates a minimal server.js that doesn't require full node_modules + output: 'standalone', + + // Turbopack configuration turbopack: {}, }; diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts new file mode 100644 index 0000000..88cf603 --- /dev/null +++ b/src/app/api/health/route.ts @@ -0,0 +1,16 @@ +import { NextResponse } from 'next/server'; + +/** + * Health check endpoint for Docker and monitoring + * Returns 200 OK when the application is running properly + */ +export async function GET() { + return NextResponse.json( + { + status: 'healthy', + timestamp: new Date().toISOString(), + service: 'synapsis', + }, + { status: 200 } + ); +}