- Restore blue PageHeader on Dashboard (/app-components) - Update homepage (/) with subtle header design without blue bar - Add uniform PageHeader styling to application edit page - Fix Rapporten link on homepage to point to /reports overview - Improve header descriptions spacing for better readability
760 lines
18 KiB
Markdown
760 lines
18 KiB
Markdown
# Productie Deployment Guide
|
|
|
|
Deze guide beschrijft hoe je de CMDB Insight 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)
|