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:
2026-01-14 00:38:40 +01:00
parent ca21b9538d
commit a7f8301196
73 changed files with 12878 additions and 2003 deletions

View File

@@ -0,0 +1,272 @@
# Azure Deployment - Infrastructure Samenvatting
## Applicatie Overzicht
**Zuyderland CMDB GUI** - Web applicatie voor classificatie en beheer van applicatiecomponenten in Jira Assets.
### Technologie Stack
- **Backend**: Node.js 20 (Express, TypeScript)
- **Frontend**: React 18 (Vite, TypeScript)
- **Database**: SQLite (cache layer, ~20MB, geen backup nodig - sync vanuit Jira)
- **Containerization**: Docker
- **Authentication**: Jira OAuth 2.0 of Personal Access Token
- **Gebruikers**: Max. 20 collega's
---
## Infrastructure Vereisten
### 1. Compute Resources
**Aanbevolen: Azure App Service (Basic Tier)**
- **App Service Plan**: B1 (1 vCPU, 1.75GB RAM) - **voldoende voor 20 gebruikers**
- 2 Web Apps: Backend + Frontend (deel dezelfde App Service Plan)
- **Kosten**: ~€15-25/maand
- **Voordelen**: Eenvoudig, managed service, voldoende voor kleine teams
**Alternatief: Azure Container Instances (ACI) - Als je containers prefereert**
- 2 containers: Backend + Frontend
- Backend: 1 vCPU, 2GB RAM
- Frontend: 0.5 vCPU, 1GB RAM
- **Kosten**: ~€30-50/maand
- **Nadeel**: Minder managed features dan App Service
### 2. Database & Storage
**Optie A: PostgreSQL (Aanbevolen) ⭐**
- **Azure Database for PostgreSQL**: Flexible Server Basic tier (B1ms)
- **Database**: ~20MB (huidige grootte, ruimte voor groei)
- **Kosten**: ~€20-30/maand
- **Voordelen**: Identieke dev/prod stack, betere concurrency, connection pooling
**Optie B: SQLite (Huidige situatie)**
- **SQLite Database**: ~20MB (in Azure Storage)
- **Azure Storage Account**: Standard LRS (Hot tier)
- **Kosten**: ~€1-3/maand
- **Nadelen**: Beperkte concurrency, geen connection pooling
**Logs**: ~500MB-1GB/maand (Application Insights)
### 3. Networking
**Vereisten:**
- **HTTPS**: SSL/TLS certificaat (Let's Encrypt of Azure App Service Certificate)
- **DNS**: Subdomain (bijv. `cmdb.zuyderland.nl`)
- **Firewall**: Inbound poorten 80/443, outbound naar Jira API
- **Load Balancer**: Azure Application Gateway (optioneel, voor HA)
**Network Security:**
- Private endpoints (optioneel, voor extra security)
- Network Security Groups (NSG)
- Azure Firewall (optioneel)
### 4. Secrets Management
**Azure Key Vault** voor:
- `JIRA_OAUTH_CLIENT_SECRET`
- `SESSION_SECRET`
- `ANTHROPIC_API_KEY`
- `JIRA_PAT` (indien gebruikt)
**Kosten**: ~€1-5/maand
### 5. Monitoring & Logging
**Azure Monitor:**
- Application Insights (Basic tier - gratis tot 5GB/maand)
- Log Analytics Workspace (Pay-as-you-go)
- Alerts voor health checks, errors
**Kosten**: ~€0-20/maand (met Basic tier vaak gratis voor kleine apps)
### 6. Backup & Disaster Recovery
**Geen backup vereist** - Data wordt gesynchroniseerd vanuit Jira Assets, dus backup is niet nodig.
De SQLite database is een cache layer die opnieuw opgebouwd kan worden via sync.
---
## Deployment Architectuur
### Aanbevolen: Azure App Service (Basic Tier)
**Eenvoudige setup voor kleine teams (20 gebruikers):**
```
┌─────────────────────────────────────┐
│ Azure App Service (B1 Plan) │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Frontend │ │ Backend │ │
│ │ Web App │ │ Web App │ │
│ └──────────┘ └────┬─────┘ │
└─────────────────────────┼──────────┘
┌─────────────┴─────────────┐
│ │
┌───────▼──────┐ ┌────────────▼────┐
│ Azure Storage│ │ Azure Key Vault │
│ (SQLite DB) │ │ (Secrets) │
└──────────────┘ └─────────────────┘
┌───────▼──────┐
│ Application │
│ Insights │
│ (Basic/FREE) │
└──────────────┘
```
**Opmerking**: Application Gateway is niet nodig voor 20 gebruikers - App Service heeft ingebouwde SSL en load balancing.
---
## Security Overwegingen
### 1. Authentication
- **Jira OAuth 2.0**: Gebruikers authenticeren via Jira
- **Session Management**: Sessions in-memory (overweeg Azure Redis Cache voor productie)
### 2. Network Security
- **HTTPS Only**: Alle verkeer via HTTPS
- **CORS**: Alleen toegestaan vanuit geconfigureerde frontend URL
- **Rate Limiting**: 100 requests/minuut per IP (configureerbaar)
### 3. Data Security
- **Secrets**: Alle secrets in Azure Key Vault
- **Database**: SQLite database in Azure Storage (encrypted at rest)
- **In Transit**: TLS 1.2+ voor alle communicatie
### 4. Compliance
- **Logging**: Alle API calls gelogd (geen PII)
- **Audit Trail**: Wijzigingen aan applicaties gelogd
- **Data Residency**: Data blijft in Azure West Europe (of gewenste regio)
---
## Externe Dependencies
### 1. Jira Assets API
- **Endpoint**: `https://jira.zuyderland.nl`
- **Authentication**: OAuth 2.0 of Personal Access Token
- **Rate Limits**: Respecteer Jira API rate limits
- **Network**: Outbound HTTPS naar Jira (poort 443)
### 2. AI API (Optioneel)
- **Anthropic Claude API**: Voor AI classificatie features
- **Network**: Outbound HTTPS naar `api.anthropic.com`
---
## Deployment Stappen
### 1. Azure Resources Aanmaken
```bash
# Resource Group
az group create --name rg-cmdb-gui --location westeurope
# App Service Plan (Basic B1 - voldoende voor 20 gebruikers)
az appservice plan create --name plan-cmdb-gui --resource-group rg-cmdb-gui --sku B1
# Web Apps (delen dezelfde plan - kostenbesparend)
az webapp create --name cmdb-backend --resource-group rg-cmdb-gui --plan plan-cmdb-gui
az webapp create --name cmdb-frontend --resource-group rg-cmdb-gui --plan plan-cmdb-gui
# Key Vault
az keyvault create --name kv-cmdb-gui --resource-group rg-cmdb-gui --location westeurope
# Storage Account (voor SQLite database - alleen bij SQLite optie)
az storage account create --name stcmdbgui --resource-group rg-cmdb-gui --location westeurope --sku Standard_LRS
```
**Met PostgreSQL (Aanbevolen):**
```bash
# PostgreSQL Database (Flexible Server)
az postgres flexible-server create \
--resource-group rg-cmdb-gui \
--name psql-cmdb-gui \
--location westeurope \
--admin-user cmdbadmin \
--admin-password <secure-password-from-key-vault> \
--sku-name Standard_B1ms \
--tier Burstable \
--storage-size 32 \
--version 15
# Database aanmaken
az postgres flexible-server db create \
--resource-group rg-cmdb-gui \
--server-name psql-cmdb-gui \
--database-name cmdb
```
### 2. Configuration
- Environment variabelen via App Service Configuration
- Secrets via Key Vault references
- SSL certificaat via App Service Certificate of Let's Encrypt
### 3. CI/CD
- **Azure DevOps Pipelines** of **GitHub Actions**
- Automatische deployment bij push naar main branch
- Deployment slots voor zero-downtime updates
---
## Kosten Schatting (Maandelijks)
**Voor 20 gebruikers - Basic Setup:**
**Met SQLite (huidige setup):**
| Component | Schatting |
|-----------|-----------|
| App Service Plan (B1) | €15-25 |
| Storage Account | €1-3 |
| Key Vault | €1-2 |
| Application Insights (Basic) | €0-5 |
| **Totaal** | **€17-35/maand** |
**Met PostgreSQL (aanbevolen):**
| Component | Schatting |
|-----------|-----------|
| App Service Plan (B1) | €15-25 |
| PostgreSQL Database (B1ms) | €20-30 |
| Key Vault | €1-2 |
| Application Insights (Basic) | €0-5 |
| **Totaal** | **€36-62/maand** |
*Inclusief: SSL certificaat (gratis via App Service), basis monitoring*
**Opmerking**: Met Basic tier en gratis Application Insights kan dit zelfs onder €20/maand blijven.
**Backup**: Niet nodig - data wordt gesynchroniseerd vanuit Jira Assets.
---
## Vragen voor Infrastructure Team
1. **DNS & Domain**: Kunnen we een subdomain krijgen? (bijv. `cmdb.zuyderland.nl`)
2. **SSL Certificaat**: Azure App Service Certificate of Let's Encrypt via certbot?
3. **Network**: Moeten we via VPN/ExpressRoute of direct internet toegang?
4. **Firewall Rules**: Welke outbound toegang is nodig? (Jira API, Anthropic API)
5. **Monitoring**: Gebruiken we bestaande Azure Monitor setup of aparte workspace?
6. **Backup**: Niet nodig - SQLite database is cache layer, data wordt gesynchroniseerd vanuit Jira Assets
7. **Disaster Recovery**: Data kan opnieuw gesynchroniseerd worden vanuit Jira (geen backup vereist)
8. **Compliance**: Zijn er specifieke compliance requirements? (ISO 27001, NEN 7510)
9. **Scaling**: Niet nodig - max. 20 gebruikers, Basic tier is voldoende
10. **Maintenance Windows**: Wanneer kunnen we updates deployen?
---
## Next Steps
1. **Kick-off Meeting**: Bespreken architectuur en requirements
2. **Proof of Concept**: Deploy naar Azure App Service (test environment)
3. **Security Review**: Security team review van configuratie
4. **Load Testing**: Testen onder verwachte load
5. **Production Deployment**: Go-live met monitoring
---
## Contact & Documentatie
- **Application Code**: [Git Repository]
- **Deployment Guide**: `PRODUCTION-DEPLOYMENT.md`
- **API Documentation**: `/api/config` endpoint

View File

@@ -0,0 +1,142 @@
# Azure Deployment - Quick Reference
## 🎯 In één oogopslag
**Applicatie**: Zuyderland CMDB GUI (Node.js + React web app)
**Doel**: Hosten in Azure App Service
**Gebruikers**: Max. 20 collega's
**Geschatte kosten**: €18-39/maand (Basic tier)
**Complexiteit**: Laag (eenvoudige web app deployment)
---
## 📦 Wat hebben we nodig?
### Core Services
-**Azure App Service Plan (B1)**: Gedeeld tussen Backend + Frontend
-**Azure Key Vault**: Voor secrets (OAuth, API keys)
-**Database**: PostgreSQL (aanbevolen) of SQLite (huidige)
- PostgreSQL: Azure Database for PostgreSQL (B1ms) - €20-30/maand
- SQLite: Azure Storage - €1-3/maand
-**Application Insights (Basic)**: Monitoring & logging (gratis tot 5GB/maand)
### Networking
-**HTTPS**: SSL certificaat (App Service Certificate of Let's Encrypt)
-**DNS**: Subdomain (bijv. `cmdb.zuyderland.nl`)
-**Outbound**: Toegang naar `jira.zuyderland.nl` (HTTPS)
### Security
-**OAuth 2.0**: Authenticatie via Jira
-**Secrets**: Alles in Key Vault
-**HTTPS Only**: Geen HTTP toegang
---
## 💰 Kosten Breakdown
**Optie 1: Met SQLite (huidige setup)**
| Item | Maandelijks |
|------|-------------|
| App Service Plan (B1) | €15-25 |
| Application Insights (Basic) | €0-5 |
| Storage + Key Vault | €2-5 |
| **Totaal** | **€17-35** |
**Optie 2: Met PostgreSQL (aanbevolen voor identieke dev/prod)**
| Item | Maandelijks |
|------|-------------|
| App Service Plan (B1) | €15-25 |
| PostgreSQL Database (B1ms) | €20-30 |
| Application Insights (Basic) | €0-5 |
| Key Vault | €1-2 |
| **Totaal** | **€36-62** |
*Zie `DATABASE-RECOMMENDATION.md` voor volledige vergelijking*
*Backup niet nodig - data sync vanuit Jira Assets*
---
## ⚙️ Technische Details
**Backend:**
- Node.js 20, Express API
- Poort: 3001 (intern)
- Health check: `/health` endpoint
- Database: SQLite (~20MB - huidige grootte)
- **Resources**: 1 vCPU, 1.75GB RAM (B1 tier - voldoende)
- **Backup**: Niet nodig - data sync vanuit Jira Assets
**Frontend:**
- React SPA
- Static files via App Service
- API calls naar backend via `/api/*`
**Dependencies:**
- Jira Assets API (outbound HTTPS)
- Anthropic API (optioneel, voor AI features)
---
## 🚀 Deployment Opties
### Optie 1: Azure App Service Basic (Aanbevolen) ⭐
- **Pro**: Eenvoudig, managed service, goedkoop, voldoende voor 20 gebruikers
- **Con**: Geen auto-scaling (niet nodig), minder flexibel dan containers
- **Tijd**: 1 dag setup
- **Kosten**: €18-39/maand
### Optie 2: Azure Container Instances (ACI)
- **Pro**: Snelle setup, container-based
- **Con**: Duurder dan App Service, minder managed features
- **Tijd**: 1 dag setup
- **Kosten**: €30-50/maand
**Niet aanbevolen voor 20 gebruikers** - App Service is goedkoper en eenvoudiger.
---
## ❓ Vragen voor Jullie
1. **DNS**: Kunnen we `cmdb.zuyderland.nl` krijgen?
2. **SSL**: App Service Certificate of Let's Encrypt?
3. **Network**: Direct internet of via VPN/ExpressRoute?
4. **Monitoring**: Nieuwe workspace of bestaande?
5. **Backup**: Niet nodig - data wordt gesynchroniseerd vanuit Jira Assets
6. **Compliance**: Specifieke requirements? (NEN 7510, ISO 27001)
---
## 📋 Checklist voor Go-Live
- [ ] Resource Group aangemaakt
- [ ] App Service Plan geconfigureerd
- [ ] 2x Web Apps aangemaakt (backend + frontend)
- [ ] Key Vault aangemaakt met secrets
- [ ] Storage Account voor database
- [ ] SSL certificaat geconfigureerd
- [ ] DNS record aangemaakt
- [ ] Application Insights geconfigureerd
- [ ] Health checks getest
- [ ] Monitoring alerts ingesteld
---
## 📝 Belangrijke Notities
**Schaalbaarheid**: Deze setup is geoptimaliseerd voor **max. 20 gebruikers**.
- Basic B1 tier (1 vCPU, 1.75GB RAM) is ruim voldoende
- Geen auto-scaling nodig
- Geen load balancer nodig
- Eenvoudige, kosteneffectieve oplossing
**Als het aantal gebruikers groeit** (>50 gebruikers), overweeg dan:
- Upgrade naar B2 tier (€50-75/maand)
- Of Standard S1 tier voor betere performance
---
## 📞 Contact
Voor vragen over de applicatie zelf, zie:
- `PRODUCTION-DEPLOYMENT.md` - Volledige deployment guide
- `AZURE-DEPLOYMENT-SUMMARY.md` - Uitgebreide Azure specifieke info

View File

@@ -0,0 +1,464 @@
# Database Engine Aanbeveling - Azure Productie
## Huidige Situatie
De applicatie gebruikt momenteel **SQLite** via `better-sqlite3`:
- **cmdb-cache.db**: ~20MB - CMDB object cache
- **classifications.db**: Classification history
## Aanbeveling: PostgreSQL
> **Belangrijk**: Azure Database for MariaDB wordt afgeschaft (retirement september 2025).
> De keuze is daarom tussen **PostgreSQL** en **MySQL** (niet MariaDB).
### PostgreSQL vs MySQL Vergelijking
| Feature | PostgreSQL | MySQL (Azure) |
|---------|------------|---------------|
| **Azure Support** | ✅ Flexible Server | ✅ Flexible Server |
| **Kosten (B1ms)** | ~€20-30/maand | ~€20-30/maand |
| **JSON Support** | ✅ JSONB (superieur) | ✅ JSON (basis) |
| **Performance** | ✅ Uitstekend | ✅ Goed |
| **Concurrency** | ✅ MVCC (zeer goed) | ✅ Goed |
| **SQL Standards** | ✅ Zeer compliant | ⚠️ Eigen dialect |
| **Full-Text Search** | ✅ Ingebouwd | ⚠️ Basis |
| **Community** | ✅ Groot & actief | ✅ Groot & actief |
| **Development Tools** | ✅ Uitstekend | ✅ Goed |
**Voor jouw use case (JSON data, 20 gebruikers):**
**PostgreSQL heeft voordeel:**
- **JSONB**: Betere JSON performance en querying (gebruikt in huidige schema)
- **MVCC**: Betere concurrency voor 20 gelijktijdige gebruikers
- **SQL Standards**: Makkelijker migreren van SQLite
- **Full-Text Search**: Ingebouwd (handig voor toekomstige zoekfuncties)
**MySQL is ook goed:**
- Vergelijkbare kosten
- Goede performance
- Veel gebruikt (bekende technologie)
**Conclusie**: Beide zijn goede keuzes. PostgreSQL heeft lichte voordelen voor JSON-heavy workloads en betere SQLite compatibiliteit.
### Waarom PostgreSQL?
**Identieke Dev/Prod Stack**
- Lokaal: PostgreSQL via Docker (gratis)
- Azure: Azure Database for PostgreSQL Flexible Server
- Zelfde database engine, zelfde SQL syntax
**Azure Integratie**
- Native ondersteuning in Azure
- Managed service (geen server management)
- Betaalbaar: Basic tier ~€20-30/maand voor 20 gebruikers
**Performance**
- Betere concurrency dan SQLite (20 gebruikers)
- Connection pooling (nodig voor web apps)
- Betere query performance bij groei
**Features**
- JSON support (gebruikt in huidige schema)
- Transactions
- Foreign keys
- Full-text search (toekomstig)
**Development Experience**
- Docker setup identiek aan productie
- Migraties mogelijk (Prisma, Knex, etc.)
- Betere tooling
### Alternatief: SQLite Blijven
**Voordelen:**
- ✅ Geen migratie nodig
- ✅ Werkt in Azure App Service (file storage)
- ✅ Gratis
- ✅ Eenvoudig
**Nadelen:**
- ❌ Beperkte concurrency (kan problemen geven met 20 gebruikers)
- ❌ Geen connection pooling
- ❌ File-based (moeilijker backup/restore)
- ❌ Beperkte query performance bij groei
**Conclusie**: SQLite kan werken voor 20 gebruikers, maar PostgreSQL is beter voor productie.
---
## Migratie naar PostgreSQL
### Stappenplan
#### 1. Database Abstraction Layer
Maak een database abstraction layer zodat we kunnen wisselen tussen SQLite (dev) en PostgreSQL (prod):
```typescript
// backend/src/services/database/interface.ts
export interface DatabaseAdapter {
query(sql: string, params?: any[]): Promise<any[]>;
execute(sql: string, params?: any[]): Promise<void>;
transaction<T>(callback: (db: DatabaseAdapter) => Promise<T>): Promise<T>;
close(): Promise<void>;
}
```
#### 2. PostgreSQL Adapter
```typescript
// backend/src/services/database/postgresAdapter.ts
import { Pool } from 'pg';
export class PostgresAdapter implements DatabaseAdapter {
private pool: Pool;
constructor(connectionString: string) {
this.pool = new Pool({ connectionString });
}
async query(sql: string, params?: any[]): Promise<any[]> {
const result = await this.pool.query(sql, params);
return result.rows;
}
// ... implement other methods
}
```
#### 3. SQLite Adapter (voor backward compatibility)
```typescript
// backend/src/services/database/sqliteAdapter.ts
import Database from 'better-sqlite3';
export class SqliteAdapter implements DatabaseAdapter {
private db: Database.Database;
constructor(dbPath: string) {
this.db = new Database(dbPath);
}
async query(sql: string, params?: any[]): Promise<any[]> {
const stmt = this.db.prepare(sql);
return stmt.all(...(params || [])) as any[];
}
// ... implement other methods
}
```
#### 4. Schema Migratie
**SQLite → PostgreSQL verschillen:**
- `INTEGER PRIMARY KEY AUTOINCREMENT``SERIAL PRIMARY KEY`
- `TEXT``TEXT` of `VARCHAR`
- `JSON``JSONB` (beter in PostgreSQL)
- `ON CONFLICT``ON CONFLICT` (werkt in beide)
**SQLite → MySQL verschillen:**
- `INTEGER PRIMARY KEY AUTOINCREMENT``INT AUTO_INCREMENT PRIMARY KEY`
- `TEXT``TEXT` of `VARCHAR(255)`
- `JSON``JSON` (basis support)
- `ON CONFLICT``ON DUPLICATE KEY UPDATE` (andere syntax)
---
## Azure Setup
### Optie 1: Azure Database for PostgreSQL Flexible Server
**Basic Tier (aanbevolen voor 20 gebruikers):**
- **Burstable B1ms**: 1 vCore, 2GB RAM
- **Storage**: 32GB (meer dan genoeg voor 20MB database)
- **Kosten**: ~€20-30/maand
- **Backup**: 7 dagen retention (gratis)
**Configuratie:**
```bash
# Azure CLI
az postgres flexible-server create \
--resource-group rg-cmdb-gui \
--name psql-cmdb-gui \
--location westeurope \
--admin-user cmdbadmin \
--admin-password <secure-password> \
--sku-name Standard_B1ms \
--tier Burstable \
--storage-size 32 \
--version 15
```
**Connection String:**
```
postgresql://cmdbadmin:<password>@psql-cmdb-gui.postgres.database.azure.com:5432/cmdb?sslmode=require
```
### Optie 2: Azure Database for MySQL Flexible Server
**Basic Tier (aanbevolen voor 20 gebruikers):**
- **Burstable B1ms**: 1 vCore, 2GB RAM
- **Storage**: 32GB (meer dan genoeg voor 20MB database)
- **Kosten**: ~€20-30/maand
- **Backup**: 7 dagen retention (gratis)
**Configuratie:**
```bash
# Azure CLI
az mysql flexible-server create \
--resource-group rg-cmdb-gui \
--name mysql-cmdb-gui \
--location westeurope \
--admin-user cmdbadmin \
--admin-password <secure-password> \
--sku-name Standard_B1ms \
--tier Burstable \
--storage-size 32 \
--version 8.0.21
```
**Connection String:**
```
mysql://cmdbadmin:<password>@mysql-cmdb-gui.mysql.database.azure.com:3306/cmdb?ssl-mode=REQUIRED
```
**MySQL vs PostgreSQL voor jouw schema:**
- **JSON**: MySQL heeft JSON type, maar PostgreSQL JSONB is sneller voor queries
- **AUTOINCREMENT**: MySQL gebruikt `AUTO_INCREMENT`, PostgreSQL gebruikt `SERIAL` (beide werken)
- **ON CONFLICT**: MySQL gebruikt `ON DUPLICATE KEY UPDATE`, PostgreSQL gebruikt `ON CONFLICT` (beide werken)
---
## Local Development Setup
### Optie 1: Docker Compose voor PostgreSQL
Voeg toe aan `docker-compose.yml`:
```yaml
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: cmdb
POSTGRES_USER: cmdb
POSTGRES_PASSWORD: cmdb-dev
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U cmdb"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
```
### Optie 2: Docker Compose voor MySQL
Voeg toe aan `docker-compose.yml`:
```yaml
services:
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: cmdb
MYSQL_USER: cmdb
MYSQL_PASSWORD: cmdb-dev
MYSQL_ROOT_PASSWORD: root-dev
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
volumes:
mysql_data:
```
**Environment variabelen (lokaal - PostgreSQL):**
```bash
# .env.local
DATABASE_URL=postgresql://cmdb:cmdb-dev@localhost:5432/cmdb
# of
DATABASE_TYPE=postgres
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=cmdb
DATABASE_USER=cmdb
DATABASE_PASSWORD=cmdb-dev
```
**Environment variabelen (Azure):**
```bash
# Azure App Service Configuration
DATABASE_TYPE=postgres
DATABASE_HOST=psql-cmdb-gui.postgres.database.azure.com
DATABASE_PORT=5432
DATABASE_NAME=cmdb
DATABASE_USER=cmdbadmin
DATABASE_PASSWORD=<from-key-vault>
DATABASE_SSL=true
```
**Environment variabelen (lokaal - MySQL):**
```bash
# .env.local
DATABASE_URL=mysql://cmdb:cmdb-dev@localhost:3306/cmdb
# of
DATABASE_TYPE=mysql
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_NAME=cmdb
DATABASE_USER=cmdb
DATABASE_PASSWORD=cmdb-dev
```
**Environment variabelen (Azure - MySQL):**
```bash
# Azure App Service Configuration
DATABASE_TYPE=mysql
DATABASE_HOST=mysql-cmdb-gui.mysql.database.azure.com
DATABASE_PORT=3306
DATABASE_NAME=cmdb
DATABASE_USER=cmdbadmin
DATABASE_PASSWORD=<from-key-vault>
DATABASE_SSL=true
```
---
## Kosten Vergelijking
| Optie | Lokaal | Azure (maandelijks) | Totaal |
|-------|--------|---------------------|--------|
| **PostgreSQL** | Gratis (Docker) | €20-30 | €20-30 |
| **MySQL** | Gratis (Docker) | €20-30 | €20-30 |
| **SQLite** | Gratis | €0 (in App Service) | €0 |
| **Azure SQL** | SQL Server Express | €50-100 | €50-100 |
**Aanbeveling**: PostgreSQL - beste balans tussen kosten, performance en identieke stack.
MySQL is ook een goede keuze met vergelijkbare kosten en performance.
---
## Migratie Script
### SQLite naar PostgreSQL Converter
```typescript
// scripts/migrate-sqlite-to-postgres.ts
import Database from 'better-sqlite3';
import { Pool } from 'pg';
async function migrate() {
// Connect to SQLite
const sqlite = new Database('./data/cmdb-cache.db');
// Connect to PostgreSQL
const pg = new Pool({
connectionString: process.env.DATABASE_URL
});
// Migrate cached_objects
const objects = sqlite.prepare('SELECT * FROM cached_objects').all();
for (const obj of objects) {
await pg.query(
`INSERT INTO cached_objects (id, object_key, object_type, label, data, jira_updated_at, jira_created_at, cached_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (id) DO NOTHING`,
[obj.id, obj.object_key, obj.object_type, obj.label, obj.data, obj.jira_updated_at, obj.jira_created_at, obj.cached_at]
);
}
// Migrate relations, metadata, etc.
// ...
sqlite.close();
await pg.end();
}
```
---
## Implementatie Plan
### Fase 1: Database Abstraction (1-2 dagen)
1. Maak `DatabaseAdapter` interface
2. Implementeer `PostgresAdapter` en `SqliteAdapter`
3. Update `CacheStore` en `DatabaseService` om adapter te gebruiken
### Fase 2: Local PostgreSQL Setup (0.5 dag)
1. Voeg PostgreSQL toe aan docker-compose.yml
2. Test lokaal met PostgreSQL
3. Update environment configuratie
### Fase 3: Schema Migratie (1 dag)
1. Converteer SQLite schema naar PostgreSQL
2. Maak migratie script
3. Test migratie lokaal
### Fase 4: Azure Setup (1 dag)
1. Maak Azure Database for PostgreSQL
2. Configureer connection string in Key Vault
3. Test connectiviteit
### Fase 5: Productie Migratie (0.5 dag)
1. Migreer data van SQLite naar PostgreSQL
2. Update App Service configuratie
3. Test in productie
**Totaal**: ~3-4 dagen werk
---
## Aanbeveling
### 🏆 PostgreSQL (Aanbevolen)
**Gebruik PostgreSQL** voor:
- ✅ Identieke dev/prod stack
- ✅ Betere JSON performance (JSONB)
- ✅ Betere concurrency (MVCC)
- ✅ Azure native ondersteuning
- ✅ Toekomstbestendig
- ✅ Betaalbaar (~€20-30/maand)
- ✅ Makkelijker migratie van SQLite (SQL syntax)
### ✅ MySQL (Ook goed)
**MySQL is ook een goede keuze** als:
- Je team meer ervaring heeft met MySQL
- Je voorkeur geeft aan MySQL ecosystem
- Vergelijkbare kosten en performance acceptabel zijn
- Je JSON queries niet te complex zijn
**Nadelen t.o.v. PostgreSQL:**
- ⚠️ Minder geavanceerde JSON support (geen JSONB)
- ⚠️ Minder SQL standards compliant
- ⚠️ Iets andere syntax voor conflicten
### 💰 SQLite (Minimale kosten)
**SQLite blijft optie** als:
- Je snel moet deployen zonder migratie
- Kosten kritiek zijn (€0 vs €20-30)
- Je accepteert beperkingen (concurrency, performance)
**Conclusie**: Voor jouw use case (JSON data, 20 gebruikers) is **PostgreSQL de beste keuze**, maar MySQL is ook prima. Beide zijn veel beter dan SQLite voor productie.
---
## Next Steps
1. **Beslissing**: PostgreSQL of SQLite blijven?
2. **Als PostgreSQL**: Start met Fase 1 (Database Abstraction)
3. **Als SQLite**: Documenteer beperkingen en overweeg toekomstige migratie

View File

@@ -0,0 +1,435 @@
# Gitea Docker Container Registry - Deployment Guide
Deze guide beschrijft hoe je Gitea gebruikt als Docker Container Registry voor het deployen van de Zuyderland CMDB GUI applicatie in productie.
## 📋 Inhoudsopgave
1. [Gitea Container Registry Setup](#gitea-container-registry-setup)
2. [Build & Push Images](#build--push-images)
3. [Docker Compose Configuration](#docker-compose-configuration)
4. [Deployment Workflow](#deployment-workflow)
5. [Automation Scripts](#automation-scripts)
---
## 🔧 Gitea Container Registry Setup
### 1. Enable Container Registry in Gitea
In je Gitea configuratie (`app.ini`), zorg dat de Container Registry enabled is:
```ini
[registry]
ENABLED = true
```
Of via de Gitea UI: **Settings****Application****Container Registry** → Enable
### 2. Registry URL Format
Gitea Container Registry gebruikt het volgende formaat:
```
<gitea-host>/<username>/<repository-name>
```
Bijvoorbeeld:
- Gitea URL: `https://git.zuyderland.nl`
- Repository: `icmt/cmdb-gui`
- Registry URL: `git.zuyderland.nl/icmt/cmdb-gui`
---
## 🐳 Build & Push Images
### 1. Login to Gitea Registry
```bash
# Login met Gitea credentials
docker login git.zuyderland.nl
# Username: <your-gitea-username>
# Password: <your-gitea-password> (of Personal Access Token)
```
### 2. Build Images
```bash
# Build backend image
docker build -t git.zuyderland.nl/icmt/cmdb-gui/backend:latest -f backend/Dockerfile.prod ./backend
# Build frontend image
docker build -t git.zuyderland.nl/icmt/cmdb-gui/frontend:latest -f frontend/Dockerfile.prod ./frontend
```
### 3. Push Images
```bash
# Push backend image
docker push git.zuyderland.nl/icmt/cmdb-gui/backend:latest
# Push frontend image
docker push git.zuyderland.nl/icmt/cmdb-gui/frontend:latest
```
### 4. Tagging for Versions
Voor versioned releases:
```bash
VERSION="1.0.0"
# Tag and push backend
docker tag git.zuyderland.nl/icmt/cmdb-gui/backend:latest \
git.zuyderland.nl/icmt/cmdb-gui/backend:v${VERSION}
docker push git.zuyderland.nl/icmt/cmdb-gui/backend:v${VERSION}
# Tag and push frontend
docker tag git.zuyderland.nl/icmt/cmdb-gui/frontend:latest \
git.zuyderland.nl/icmt/cmdb-gui/frontend:v${VERSION}
docker push git.zuyderland.nl/icmt/cmdb-gui/frontend:v${VERSION}
```
---
## 🚀 Docker Compose Configuration
### Production Docker Compose met Gitea Registry
Maak `docker-compose.prod.registry.yml`:
```yaml
version: '3.8'
services:
backend:
image: git.zuyderland.nl/icmt/cmdb-gui/backend:latest
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:
image: git.zuyderland.nl/icmt/cmdb-gui/frontend:latest
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
```
### Using Specific Versions
Voor productie deployments, gebruik specifieke versies in plaats van `latest`:
```yaml
backend:
image: git.zuyderland.nl/icmt/cmdb-gui/backend:v1.0.0
frontend:
image: git.zuyderland.nl/icmt/cmdb-gui/frontend:v1.0.0
```
---
## 📦 Deployment Workflow
### 1. Build & Push Script
Maak `scripts/build-and-push.sh`:
```bash
#!/bin/bash
set -e
# Configuration
GITEA_HOST="git.zuyderland.nl"
REPO_PATH="icmt/cmdb-gui"
VERSION="${1:-latest}"
echo "🔨 Building Docker images..."
echo "Registry: ${GITEA_HOST}/${REPO_PATH}"
echo "Version: ${VERSION}"
# Build backend
echo "📦 Building backend..."
docker build -t ${GITEA_HOST}/${REPO_PATH}/backend:${VERSION} \
-f backend/Dockerfile.prod ./backend
# Build frontend
echo "📦 Building frontend..."
docker build -t ${GITEA_HOST}/${REPO_PATH}/frontend:${VERSION} \
-f frontend/Dockerfile.prod ./frontend
# Push images
echo "📤 Pushing images to registry..."
docker push ${GITEA_HOST}/${REPO_PATH}/backend:${VERSION}
docker push ${GITEA_HOST}/${REPO_PATH}/frontend:${VERSION}
echo "✅ Build and push complete!"
echo ""
echo "To deploy, run:"
echo " docker-compose -f docker-compose.prod.registry.yml pull"
echo " docker-compose -f docker-compose.prod.registry.yml up -d"
```
### 2. Deployment Script
Maak `scripts/deploy.sh`:
```bash
#!/bin/bash
set -e
VERSION="${1:-latest}"
COMPOSE_FILE="docker-compose.prod.registry.yml"
echo "🚀 Deploying version: ${VERSION}"
# Update image tags in compose file (if using version tags)
if [ "$VERSION" != "latest" ]; then
sed -i.bak "s|:latest|:v${VERSION}|g" ${COMPOSE_FILE}
fi
# Pull latest images
echo "📥 Pulling images..."
docker-compose -f ${COMPOSE_FILE} pull
# Deploy
echo "🚀 Starting services..."
docker-compose -f ${COMPOSE_FILE} up -d
# Cleanup old images (optional)
echo "🧹 Cleaning up..."
docker image prune -f
echo "✅ Deployment complete!"
echo ""
echo "Check status:"
echo " docker-compose -f ${COMPOSE_FILE} ps"
echo ""
echo "View logs:"
echo " docker-compose -f ${COMPOSE_FILE} logs -f"
```
### 3. CI/CD Integration (Gitea Actions)
Maak `.gitea/workflows/docker-build.yml`:
```yaml
name: Build and Push Docker Images
on:
push:
tags:
- 'v*'
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Gitea Container Registry
uses: docker/login-action@v2
with:
registry: git.zuyderland.nl
username: ${{ secrets.GITEA_USERNAME }}
password: ${{ secrets.GITEA_PASSWORD }}
- name: Determine version
id: version
run: |
if [[ ${{ github.ref }} == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
else
VERSION=latest
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Build and push backend
uses: docker/build-push-action@v4
with:
context: ./backend
file: ./backend/Dockerfile.prod
push: true
tags: |
git.zuyderland.nl/icmt/cmdb-gui/backend:${{ steps.version.outputs.version }}
git.zuyderland.nl/icmt/cmdb-gui/backend:latest
- name: Build and push frontend
uses: docker/build-push-action@v4
with:
context: ./frontend
file: ./frontend/Dockerfile.prod
push: true
tags: |
git.zuyderland.nl/icmt/cmdb-gui/frontend:${{ steps.version.outputs.version }}
git.zuyderland.nl/icmt/cmdb-gui/frontend:latest
```
---
## 🔐 Authentication
### Personal Access Token (Aanbevolen)
Voor CI/CD en automatisering, gebruik een Personal Access Token:
1. Gitea UI → **Settings****Applications****Generate New Token**
2. Scopes: `read:repository`, `write:repository`
3. Gebruik token als password bij `docker login`:
```bash
echo $GITEA_TOKEN | docker login git.zuyderland.nl -u <username> --password-stdin
```
### Environment Variables
Voor scripts, gebruik environment variables:
```bash
export GITEA_REGISTRY="git.zuyderland.nl"
export GITEA_USERNAME="your-username"
export GITEA_PASSWORD="your-token"
export REPO_PATH="icmt/cmdb-gui"
```
---
## 📝 Usage Examples
### Build and Push
```bash
# Build and push latest
./scripts/build-and-push.sh
# Build and push specific version
./scripts/build-and-push.sh 1.0.0
```
### Deploy
```bash
# Deploy latest
./scripts/deploy.sh
# Deploy specific version
./scripts/deploy.sh 1.0.0
```
### Manual Deployment
```bash
# Login
docker login git.zuyderland.nl
# Pull images
docker-compose -f docker-compose.prod.registry.yml pull
# Deploy
docker-compose -f docker-compose.prod.registry.yml up -d
# Check status
docker-compose -f docker-compose.prod.registry.yml ps
# View logs
docker-compose -f docker-compose.prod.registry.yml logs -f
```
---
## 🔍 Troubleshooting
### Authentication Issues
```bash
# Check login status
cat ~/.docker/config.json
# Re-login
docker logout git.zuyderland.nl
docker login git.zuyderland.nl
```
### Registry Not Found
- Controleer dat Container Registry enabled is in Gitea
- Verifieer de registry URL format: `<host>/<username>/<repo>`
- Check Gitea logs voor errors
### Image Pull Errors
```bash
# Check if image exists in registry (via Gitea UI)
# Verify network connectivity
curl -I https://git.zuyderland.nl
# Check Docker daemon logs
journalctl -u docker.service
```
---
## 🎯 Best Practices
1. **Use Version Tags**: Gebruik specifieke versies (`v1.0.0`) voor productie, `latest` voor development
2. **Security**: Gebruik Personal Access Tokens in plaats van passwords
3. **CI/CD**: Automatiseer build/push via Gitea Actions
4. **Image Scanning**: Overweeg image vulnerability scanning (Trivy, Clair)
5. **Registry Cleanup**: Regelmatig oude images verwijderen om ruimte te besparen
---
## 📚 Additional Resources
- [Gitea Container Registry Documentation](https://docs.gitea.io/en-us/usage/packages/container/)
- [Docker Registry Authentication](https://docs.docker.com/engine/reference/commandline/login/)
- [Docker Compose Production Guide](./PRODUCTION-DEPLOYMENT.md)

View 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)

File diff suppressed because it is too large Load Diff