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:
272
docs/AZURE-DEPLOYMENT-SUMMARY.md
Normal file
272
docs/AZURE-DEPLOYMENT-SUMMARY.md
Normal 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
|
||||
142
docs/AZURE-QUICK-REFERENCE.md
Normal file
142
docs/AZURE-QUICK-REFERENCE.md
Normal 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
|
||||
464
docs/DATABASE-RECOMMENDATION.md
Normal file
464
docs/DATABASE-RECOMMENDATION.md
Normal 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
|
||||
435
docs/GITEA-DOCKER-REGISTRY.md
Normal file
435
docs/GITEA-DOCKER-REGISTRY.md
Normal 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)
|
||||
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)
|
||||
1036
docs/zira-classificatie-tool-specificatie.md
Normal file
1036
docs/zira-classificatie-tool-specificatie.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user