Add database adapter system, production deployment configs, and new dashboard components
- Add PostgreSQL and SQLite database adapters with factory pattern - Add migration script for SQLite to PostgreSQL - Add production Dockerfiles and docker-compose configs - Add deployment documentation and scripts - Add BIA sync dashboard and matching service - Add data completeness configuration and components - Add new dashboard components (BusinessImportanceComparison, ComplexityDynamics, etc.) - Update various services and routes - Remove deprecated management-parameters.json and taxonomy files
This commit is contained in:
759
docs/PRODUCTION-DEPLOYMENT.md
Normal file
759
docs/PRODUCTION-DEPLOYMENT.md
Normal file
@@ -0,0 +1,759 @@
|
||||
# Productie Deployment Guide
|
||||
|
||||
Deze guide beschrijft hoe je de Zuyderland CMDB GUI applicatie veilig en betrouwbaar in productie kunt draaien.
|
||||
|
||||
## 📋 Inhoudsopgave
|
||||
|
||||
1. [Security Best Practices](#security-best-practices)
|
||||
2. [Production Docker Setup](#production-docker-setup)
|
||||
3. [Environment Configuratie](#environment-configuratie)
|
||||
4. [Reverse Proxy (Nginx)](#reverse-proxy-nginx)
|
||||
5. [SSL/TLS Certificaten](#ssltls-certificaten)
|
||||
6. [Database & Cache](#database--cache)
|
||||
7. [Session Management](#session-management)
|
||||
8. [Monitoring & Logging](#monitoring--logging)
|
||||
9. [Backup Strategie](#backup-strategie)
|
||||
10. [Deployment Checklist](#deployment-checklist)
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Best Practices
|
||||
|
||||
### 1. Environment Variabelen
|
||||
|
||||
**ALTIJD** gebruik een `.env` bestand dat NIET in git staat:
|
||||
|
||||
```bash
|
||||
# .env (niet committen!)
|
||||
JIRA_HOST=https://jira.zuyderland.nl
|
||||
JIRA_SCHEMA_ID=your-schema-id
|
||||
JIRA_AUTH_METHOD=oauth # of 'pat'
|
||||
JIRA_OAUTH_CLIENT_ID=your-client-id
|
||||
JIRA_OAUTH_CLIENT_SECRET=your-client-secret
|
||||
JIRA_OAUTH_CALLBACK_URL=https://cmdb.zuyderland.nl/api/auth/callback
|
||||
SESSION_SECRET=<generate-random-64-char-string>
|
||||
ANTHROPIC_API_KEY=your-key
|
||||
NODE_ENV=production
|
||||
PORT=3001
|
||||
FRONTEND_URL=https://cmdb.zuyderland.nl
|
||||
JIRA_API_BATCH_SIZE=15
|
||||
```
|
||||
|
||||
**Genereer een veilige SESSION_SECRET:**
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
### 2. Secrets Management
|
||||
|
||||
Gebruik een secrets management systeem:
|
||||
- **Kubernetes**: Secrets
|
||||
- **Docker Swarm**: Docker Secrets
|
||||
- **Cloud**: AWS Secrets Manager, Azure Key Vault, Google Secret Manager
|
||||
- **On-premise**: HashiCorp Vault
|
||||
|
||||
### 3. Network Security
|
||||
|
||||
- **Firewall**: Alleen poorten 80/443 open voor reverse proxy
|
||||
- **Internal Network**: Backend alleen bereikbaar via reverse proxy
|
||||
- **VPN**: Overweeg VPN voor interne toegang
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Production Docker Setup
|
||||
|
||||
### Backend Production Dockerfile
|
||||
|
||||
Maak `backend/Dockerfile.prod`:
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production && npm cache clean --force
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
# Build TypeScript
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install only production dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production && npm cache clean --force
|
||||
|
||||
# Copy built files
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/src/generated ./src/generated
|
||||
|
||||
# Create data directory with proper permissions
|
||||
RUN mkdir -p /app/data && chown -R node:node /app/data
|
||||
|
||||
# Switch to non-root user
|
||||
USER node
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3001
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||
CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||||
|
||||
# Start production server
|
||||
CMD ["node", "dist/index.js"]
|
||||
```
|
||||
|
||||
### Frontend Production Dockerfile
|
||||
|
||||
Maak `frontend/Dockerfile.prod`:
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Copy source and build
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Production stage with nginx
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy built files
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy nginx config
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
### Frontend Nginx Config
|
||||
|
||||
Maak `frontend/nginx.conf`:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
|
||||
|
||||
# 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;
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# API proxy
|
||||
location /api {
|
||||
proxy_pass http://backend:3001;
|
||||
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 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
|
||||
# SPA routing
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Production Docker Compose
|
||||
|
||||
Maak `docker-compose.prod.yml`:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.prod
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3001
|
||||
env_file:
|
||||
- .env.production
|
||||
volumes:
|
||||
- backend_data:/app/data
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile.prod
|
||||
depends_on:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./nginx/ssl:/etc/nginx/ssl:ro
|
||||
- nginx_cache:/var/cache/nginx
|
||||
depends_on:
|
||||
- frontend
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal
|
||||
|
||||
volumes:
|
||||
backend_data:
|
||||
nginx_cache:
|
||||
|
||||
networks:
|
||||
internal:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Reverse Proxy (Nginx)
|
||||
|
||||
### Main Nginx Config
|
||||
|
||||
Maak `nginx/nginx.conf`:
|
||||
|
||||
```nginx
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
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;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 10M;
|
||||
|
||||
# Gzip
|
||||
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;
|
||||
|
||||
# Rate limiting
|
||||
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/m;
|
||||
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=200r/m;
|
||||
|
||||
# SSL Configuration
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# Upstream backend
|
||||
upstream backend {
|
||||
server backend:3001;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
# Upstream frontend
|
||||
upstream frontend {
|
||||
server frontend:80;
|
||||
}
|
||||
|
||||
# HTTP to HTTPS redirect
|
||||
server {
|
||||
listen 80;
|
||||
server_name cmdb.zuyderland.nl;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS server
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name cmdb.zuyderland.nl;
|
||||
|
||||
# SSL certificates
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
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;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://jira.zuyderland.nl;" always;
|
||||
|
||||
# API routes with rate limiting
|
||||
location /api {
|
||||
limit_req zone=api_limit burst=20 nodelay;
|
||||
|
||||
proxy_pass http://backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# Timeouts
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
limit_req zone=general_limit burst=50 nodelay;
|
||||
|
||||
proxy_pass http://frontend;
|
||||
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;
|
||||
}
|
||||
|
||||
# Health check (no rate limit)
|
||||
location /health {
|
||||
access_log off;
|
||||
proxy_pass http://backend/health;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 SSL/TLS Certificaten
|
||||
|
||||
### Let's Encrypt (Gratis)
|
||||
|
||||
```bash
|
||||
# Install certbot
|
||||
sudo apt-get update
|
||||
sudo apt-get install certbot
|
||||
|
||||
# Generate certificate
|
||||
sudo certbot certonly --standalone -d cmdb.zuyderland.nl
|
||||
|
||||
# Certificates worden opgeslagen in:
|
||||
# /etc/letsencrypt/live/cmdb.zuyderland.nl/fullchain.pem
|
||||
# /etc/letsencrypt/live/cmdb.zuyderland.nl/privkey.pem
|
||||
|
||||
# Auto-renewal
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
### Certificaten kopiëren naar Docker volume:
|
||||
|
||||
```bash
|
||||
# Maak directory
|
||||
mkdir -p nginx/ssl
|
||||
|
||||
# Kopieer certificaten (of gebruik bind mount)
|
||||
cp /etc/letsencrypt/live/cmdb.zuyderland.nl/fullchain.pem nginx/ssl/
|
||||
cp /etc/letsencrypt/live/cmdb.zuyderland.nl/privkey.pem nginx/ssl/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💾 Database & Cache
|
||||
|
||||
### SQLite Cache Database
|
||||
|
||||
De cache database wordt opgeslagen in `/app/data/cmdb-cache.db`.
|
||||
|
||||
**Backup strategie:**
|
||||
```bash
|
||||
# Dagelijkse backup script
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/backups/cmdb"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
docker exec cmdb-gui_backend_1 sqlite3 /app/data/cmdb-cache.db ".backup '$BACKUP_DIR/cmdb-cache-$DATE.db'"
|
||||
# Bewaar laatste 30 dagen
|
||||
find $BACKUP_DIR -name "cmdb-cache-*.db" -mtime +30 -delete
|
||||
```
|
||||
|
||||
**Volume backup:**
|
||||
```bash
|
||||
# Backup hele volume
|
||||
docker run --rm -v cmdb-gui_backend_data:/data -v $(pwd)/backups:/backup \
|
||||
alpine tar czf /backup/backend-data-$(date +%Y%m%d).tar.gz /data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Session Management
|
||||
|
||||
### Huidige Implementatie
|
||||
|
||||
De applicatie gebruikt momenteel in-memory session storage. Voor productie is dit **NIET geschikt**.
|
||||
|
||||
### Aanbeveling: Redis voor Sessions
|
||||
|
||||
1. **Voeg Redis toe aan docker-compose.prod.yml:**
|
||||
|
||||
```yaml
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
```
|
||||
|
||||
2. **Update authService.ts om Redis te gebruiken:**
|
||||
|
||||
```typescript
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const redis = new Redis({
|
||||
host: process.env.REDIS_HOST || 'redis',
|
||||
port: parseInt(process.env.REDIS_PORT || '6379'),
|
||||
password: process.env.REDIS_PASSWORD,
|
||||
});
|
||||
|
||||
// Vervang sessionStore met Redis calls
|
||||
async function setSession(sessionId: string, session: UserSession): Promise<void> {
|
||||
const ttl = Math.floor((session.expiresAt - Date.now()) / 1000);
|
||||
await redis.setex(`session:${sessionId}`, ttl, JSON.stringify(session));
|
||||
}
|
||||
|
||||
async function getSession(sessionId: string): Promise<UserSession | null> {
|
||||
const data = await redis.get(`session:${sessionId}`);
|
||||
return data ? JSON.parse(data) : null;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring & Logging
|
||||
|
||||
### 1. Logging
|
||||
|
||||
De applicatie gebruikt Winston. Configureer log rotation:
|
||||
|
||||
```typescript
|
||||
// backend/src/services/logger.ts
|
||||
import winston from 'winston';
|
||||
import DailyRotateFile from 'winston-daily-rotate-file';
|
||||
|
||||
export const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: [
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/application-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
}),
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/error-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
level: 'error',
|
||||
maxSize: '20m',
|
||||
maxFiles: '30d',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.add(new winston.transports.Console({
|
||||
format: winston.format.simple()
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Health Checks
|
||||
|
||||
De applicatie heeft al een `/health` endpoint. Monitor dit:
|
||||
|
||||
```bash
|
||||
# Monitoring script
|
||||
#!/bin/bash
|
||||
HEALTH_URL="https://cmdb.zuyderland.nl/health"
|
||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_URL)
|
||||
|
||||
if [ $STATUS -ne 200 ]; then
|
||||
# Alert via email/Slack/etc
|
||||
echo "Health check failed: $STATUS"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 3. Application Monitoring
|
||||
|
||||
Overweeg:
|
||||
- **Prometheus + Grafana**: Metrics en dashboards
|
||||
- **Sentry**: Error tracking
|
||||
- **New Relic / Datadog**: APM (Application Performance Monitoring)
|
||||
|
||||
---
|
||||
|
||||
## 💾 Backup Strategie
|
||||
|
||||
### 1. Database Backups
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup.sh - Run dagelijks via cron
|
||||
|
||||
BACKUP_DIR="/backups/cmdb"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
# Backup SQLite database
|
||||
docker exec cmdb-gui_backend_1 sqlite3 /app/data/cmdb-cache.db \
|
||||
".backup '$BACKUP_DIR/cmdb-cache-$DATE.db'"
|
||||
|
||||
# Compress
|
||||
gzip "$BACKUP_DIR/cmdb-cache-$DATE.db"
|
||||
|
||||
# Upload naar remote storage (optioneel)
|
||||
# aws s3 cp "$BACKUP_DIR/cmdb-cache-$DATE.db.gz" s3://your-backup-bucket/
|
||||
|
||||
# Cleanup oude backups (bewaar 30 dagen)
|
||||
find $BACKUP_DIR -name "*.db.gz" -mtime +30 -delete
|
||||
```
|
||||
|
||||
### 2. Configuration Backups
|
||||
|
||||
Backup `.env.production` en andere configuratie bestanden naar een veilige locatie.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Deployment Checklist
|
||||
|
||||
### Pre-Deployment
|
||||
|
||||
- [ ] Alle environment variabelen geconfigureerd
|
||||
- [ ] SESSION_SECRET gegenereerd (64+ karakters)
|
||||
- [ ] SSL certificaten geconfigureerd
|
||||
- [ ] Firewall regels ingesteld
|
||||
- [ ] Reverse proxy geconfigureerd
|
||||
- [ ] Health checks getest
|
||||
- [ ] Backup strategie geïmplementeerd
|
||||
|
||||
### Security
|
||||
|
||||
- [ ] Alle secrets in environment variabelen (niet in code)
|
||||
- [ ] HTTPS geforceerd (HTTP → HTTPS redirect)
|
||||
- [ ] Security headers geconfigureerd (HSTS, CSP, etc.)
|
||||
- [ ] Rate limiting geactiveerd
|
||||
- [ ] CORS correct geconfigureerd
|
||||
- [ ] Session cookies secure en httpOnly
|
||||
- [ ] OAuth callback URL correct geconfigureerd
|
||||
|
||||
### Performance
|
||||
|
||||
- [ ] Production builds getest
|
||||
- [ ] Docker images geoptimaliseerd (multi-stage builds)
|
||||
- [ ] Caching geconfigureerd (nginx, browser)
|
||||
- [ ] Gzip compression geactiveerd
|
||||
- [ ] Database indexes gecontroleerd
|
||||
|
||||
### Monitoring
|
||||
|
||||
- [ ] Logging geconfigureerd met rotation
|
||||
- [ ] Health checks geïmplementeerd
|
||||
- [ ] Monitoring alerts ingesteld
|
||||
- [ ] Error tracking geconfigureerd (optioneel)
|
||||
|
||||
### Operations
|
||||
|
||||
- [ ] Backup scripts getest
|
||||
- [ ] Restore procedure gedocumenteerd
|
||||
- [ ] Rollback procedure gedocumenteerd
|
||||
- [ ] Deployment procedure gedocumenteerd
|
||||
- [ ] Incident response plan
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Commands
|
||||
|
||||
### Build en Start
|
||||
|
||||
```bash
|
||||
# Build production images
|
||||
docker-compose -f docker-compose.prod.yml build
|
||||
|
||||
# Start services
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# Check logs
|
||||
docker-compose -f docker-compose.prod.yml logs -f
|
||||
|
||||
# Check health
|
||||
curl https://cmdb.zuyderland.nl/health
|
||||
```
|
||||
|
||||
### Updates
|
||||
|
||||
```bash
|
||||
# Pull nieuwe code
|
||||
git pull
|
||||
|
||||
# Rebuild en restart
|
||||
docker-compose -f docker-compose.prod.yml up -d --build
|
||||
|
||||
# Zero-downtime deployment (met meerdere instances)
|
||||
# Gebruik rolling updates of blue-green deployment
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Aanvullende Aanbevelingen
|
||||
|
||||
### 1. Container Orchestration
|
||||
|
||||
Voor grotere deployments, overweeg:
|
||||
- **Kubernetes**: Voor schaalbaarheid en high availability
|
||||
- **Docker Swarm**: Simpelere alternatief voor Kubernetes
|
||||
|
||||
### 2. Load Balancing
|
||||
|
||||
Voor high availability, gebruik meerdere backend instances met load balancer.
|
||||
|
||||
### 3. CDN
|
||||
|
||||
Overweeg een CDN (CloudFlare, AWS CloudFront) voor statische assets.
|
||||
|
||||
### 4. Database Scaling
|
||||
|
||||
Voor grote datasets, overweeg:
|
||||
- PostgreSQL in plaats van SQLite
|
||||
- Database replicatie voor read scaling
|
||||
- Connection pooling
|
||||
|
||||
### 5. Caching Layer
|
||||
|
||||
Overweeg Redis voor:
|
||||
- Session storage
|
||||
- API response caching
|
||||
- Query result caching
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support & Troubleshooting
|
||||
|
||||
### Veelvoorkomende Issues
|
||||
|
||||
1. **Sessions verlopen te snel**: Check SESSION_SECRET en Redis configuratie
|
||||
2. **CORS errors**: Check FRONTEND_URL en CORS configuratie
|
||||
3. **Database locks**: SQLite is niet geschikt voor hoge concurrency
|
||||
4. **Memory issues**: Monitor container memory usage
|
||||
|
||||
### Logs Bekijken
|
||||
|
||||
```bash
|
||||
# Backend logs
|
||||
docker-compose -f docker-compose.prod.yml logs backend
|
||||
|
||||
# Frontend logs
|
||||
docker-compose -f docker-compose.prod.yml logs frontend
|
||||
|
||||
# Nginx logs
|
||||
docker-compose -f docker-compose.prod.yml logs nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Referenties
|
||||
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)
|
||||
- [Docker Security Best Practices](https://docs.docker.com/engine/security/)
|
||||
- [Nginx Security Guide](https://nginx.org/en/docs/http/configuring_https_servers.html)
|
||||
Reference in New Issue
Block a user