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";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
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: {},
|
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