Add Docker deployment setup with health check endpoint
- Add multi-stage Dockerfile with standalone Next.js output - Add docker-compose.yml with Caddy reverse proxy - Add nginx configs for local and SSL setups - Add Caddyfile for automatic HTTPS - Add docker-entrypoint.sh for runtime config - Add .env.example and .dockerignore - Add comprehensive Docker README - Add /api/health endpoint for container health checks - Configure next.config.ts for standalone output
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+5
-1
@@ -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: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user