From 12afbe874dfc9c737d4125fdb849043514200f0b Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Tue, 4 Nov 2025 02:43:45 +0100 Subject: [PATCH] refactor(container): simplify Redis pool initialization flow - Remove redundant `$container` parameter in `RedisPoolInitializer` instantiation. - Streamline container interactions for improved clarity and maintainability. --- deployment/README.md | 4 +- deployment/ansible/inventory/production.yml | 5 +- .../docs/reference/application-stack.md | 8 +- docker-compose.postgres-override.yml | 33 ++ docker-compose.production.yml | 137 +++++--- docker-compose.staging-postgres-override.yml | 33 ++ docker-compose.staging.yml | 7 +- .../production-compose-consolidation-plan.md | 262 +++++++++++++++ .../shared-postgres-override-plan.md | 303 ++++++++++++++++++ .../staging-database-connection-analysis.md | 185 +++++++++++ .../staging-postgres-connection-plan.md | 209 ++++++++++++ src/Framework/Core/AppBootstrapper.php | 17 +- src/Framework/Core/ContainerBootstrapper.php | 108 +++++-- 13 files changed, 1216 insertions(+), 95 deletions(-) create mode 100644 docker-compose.postgres-override.yml create mode 100644 docker-compose.staging-postgres-override.yml create mode 100644 docs/deployment/production-compose-consolidation-plan.md create mode 100644 docs/deployment/shared-postgres-override-plan.md create mode 100644 docs/deployment/staging-database-connection-analysis.md create mode 100644 docs/deployment/staging-postgres-connection-plan.md diff --git a/deployment/README.md b/deployment/README.md index 52307a8d..cc886039 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -272,8 +272,8 @@ docker network inspect traefik-public ### View Logs ```bash -# Application logs -docker compose -f stacks/application/docker-compose.yml logs -f app +# Application logs (Production) +docker compose -f docker-compose.base.yml -f docker-compose.production.yml logs -f php # Traefik logs docker compose -f stacks/traefik/docker-compose.yml logs -f diff --git a/deployment/ansible/inventory/production.yml b/deployment/ansible/inventory/production.yml index efed92f4..85720020 100644 --- a/deployment/ansible/inventory/production.yml +++ b/deployment/ansible/inventory/production.yml @@ -13,5 +13,6 @@ all: # Only override-specific variables should be here # Override system_* defaults here when Wartungsfenster abweichen - # Legacy compose_file reference (deprecated - stacks now use deployment/stacks/) - compose_file: "{{ stacks_base_path }}/application/docker-compose.yml" + # Legacy compose_file reference (deprecated) + # Production now uses docker-compose.base.yml + docker-compose.production.yml from repository root + # compose_file: "{{ stacks_base_path }}/application/docker-compose.yml" diff --git a/deployment/docs/reference/application-stack.md b/deployment/docs/reference/application-stack.md index 71432b11..ceb3717c 100644 --- a/deployment/docs/reference/application-stack.md +++ b/deployment/docs/reference/application-stack.md @@ -126,11 +126,11 @@ Das Ansible Playbook führt folgende Schritte auf dem Production-Server aus: **1. Backup aktueller Deployment-Status** ```bash # Speichert aktuellen Container-Status -docker compose -f ~/deployment/stacks/application/docker-compose.yml \ +docker compose -f docker-compose.base.yml -f docker-compose.production.yml \ ps --format json > backups//current_containers.json # Speichert aktuelle docker-compose.yml Konfiguration -docker compose -f ~/deployment/stacks/application/docker-compose.yml \ +docker compose -f docker-compose.base.yml -f docker-compose.production.yml \ config > backups//docker-compose-config.yml ``` @@ -186,7 +186,7 @@ replace: '\1{{ app_image }}:{{ image_tag }}' **5. Application Stack neu starten** ```bash -docker compose -f ~/deployment/stacks/application/docker-compose.yml \ +docker compose -f docker-compose.base.yml -f docker-compose.production.yml \ up -d \ --pull always \ --force-recreate \ @@ -547,7 +547,7 @@ docker pull registry.michaelschiemer.de/framework: ```bash # Prüfe ob Regex korrekt ist grep -E "image:\s+registry.michaelschiemer.de/framework" \ - ~/deployment/stacks/application/docker-compose.yml + docker-compose.base.yml docker-compose.production.yml # Prüfe Backup für vorherige Version ls -la ~/deployment/backups/ diff --git a/docker-compose.postgres-override.yml b/docker-compose.postgres-override.yml new file mode 100644 index 00000000..6b738516 --- /dev/null +++ b/docker-compose.postgres-override.yml @@ -0,0 +1,33 @@ +# Shared PostgreSQL Connection Override for Production +# +# This file provides network configuration for connecting to the PostgreSQL Stack. +# Used by Production Stack. +# +# Usage: +# Production Stack: +# docker compose -f docker-compose.base.yml \ +# -f docker-compose.production.yml \ +# -f docker-compose.postgres-override.yml up +# +# Prerequisites: +# - PostgreSQL Stack must be running (creates app-internal network) +# - app-internal network must exist as external network +# - PostgreSQL Stack: deployment/stacks/postgresql/docker-compose.yml + +services: + # Production Stack Services + # These services will be merged with existing definitions from base.yml + production.yml + php: + networks: + app-internal: {} # Add app-internal network for PostgreSQL access (merged with existing networks) + queue-worker: + networks: + app-internal: {} # Add app-internal network for PostgreSQL access (merged with existing networks) + scheduler: + networks: + app-internal: {} # Add app-internal network for PostgreSQL access + +networks: + app-internal: + external: true + name: app-internal diff --git a/docker-compose.production.yml b/docker-compose.production.yml index 0a29bc30..83041e56 100644 --- a/docker-compose.production.yml +++ b/docker-compose.production.yml @@ -151,48 +151,8 @@ services: # Mount .env file from shared directory (production environment variables) - /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro - db: - # Production restart policy - restart: always - - # Use Docker Secrets for database password - environment: - POSTGRES_PASSWORD_FILE: /run/secrets/db_user_password - secrets: - - db_user_password - - # Use production PostgreSQL configuration - volumes: - - db_data:/var/lib/postgresql/data - - ./docker/postgres/postgresql.production.conf:/etc/postgresql/postgresql.conf:ro - - ./docker/postgres/init:/docker-entrypoint-initdb.d:ro - - # Production resource limits - deploy: - resources: - limits: - memory: 2G - cpus: '2.0' - reservations: - memory: 1G - cpus: '1.0' - - # Stricter health checks - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-postgres} -d ${DB_DATABASE:-michaelschiemer}"] - interval: 10s - timeout: 3s - retries: 5 - start_period: 30s - - # JSON logging - logging: - driver: json-file - options: - max-size: "20m" - max-file: "10" - compress: "true" - labels: "service,environment" + # Database service removed - using external PostgreSQL Stack (deployment/stacks/postgresql/) + # Connection via app-internal network using docker-compose.postgres-override.yml redis: # Production restart policy @@ -333,12 +293,91 @@ services: # Wait for dependencies to be healthy before starting depends_on: - db: - condition: service_healthy redis: condition: service_healthy php: condition: service_healthy + # Note: PostgreSQL (postgres) is external service, connection via app-internal network + + # Scheduler (Cron Jobs) + scheduler: + # Use same build as php service (has application code copied) + image: git.michaelschiemer.de:5000/framework:latest + container_name: scheduler + + # Production restart policy + restart: always + + # Override user setting - container must start as root for gosu to work + # The entrypoint script will use gosu to switch to appuser after setup + user: "root" + + # Scheduler command - direct PHP execution + command: php console.php scheduler:run + + # Production volumes + volumes: + # Mount application code from rsync deployment (read-only) + - /home/deploy/michaelschiemer/current:/var/www/html:ro + # Mount storage directory as writable volume (overlays the read-only code mount) + - storage:/var/www/html/storage:rw + # Mount var directory as writable volume for cache and logs (overlays read-only code mount) + - var-data:/var/www/html/var:rw + # Mount .env file from shared directory (production environment variables) + - /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro + + environment: + - TZ=Europe/Berlin + - APP_ENV=production + - APP_DEBUG=false + # Use Docker Secrets via *_FILE pattern (Framework supports this automatically) + - DB_PASSWORD_FILE=/run/secrets/db_user_password + - REDIS_PASSWORD_FILE=/run/secrets/redis_password + - APP_KEY_FILE=/run/secrets/app_key + - VAULT_ENCRYPTION_KEY_FILE=/run/secrets/vault_encryption_key + secrets: + - db_user_password + - redis_password + - app_key + - vault_encryption_key + + # Production resource limits + deploy: + resources: + limits: + memory: 512M + cpus: '0.5' + reservations: + memory: 256M + cpus: '0.25' + + # Health checks + healthcheck: + test: ["CMD-SHELL", "php -r 'exit(0);' && test -f /var/www/html/console.php || exit 1"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 30s + + # JSON logging + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + compress: "true" + labels: "service,environment" + + # Graceful shutdown + stop_grace_period: 30s + + # Wait for dependencies to be healthy before starting + depends_on: + redis: + condition: service_healthy + php: + condition: service_healthy + # Note: PostgreSQL (postgres) is external service, connection via app-internal network # Certbot Sidecar Container for Let's Encrypt certbot: @@ -385,11 +424,5 @@ volumes: storage: driver: local - # Database volume with backup driver (optional) - db_data: - driver: local - # Optional: Use external volume for easier backups - # driver_opts: - # type: none - # o: bind - # device: /mnt/db-backups/michaelschiemer-prod + # Database volume removed - using external PostgreSQL Stack + # PostgreSQL data is managed by deployment/stacks/postgresql/ diff --git a/docker-compose.staging-postgres-override.yml b/docker-compose.staging-postgres-override.yml new file mode 100644 index 00000000..1b9acff6 --- /dev/null +++ b/docker-compose.staging-postgres-override.yml @@ -0,0 +1,33 @@ +# Shared PostgreSQL Connection Override for Staging +# +# This file provides network configuration for connecting to the PostgreSQL Stack. +# Used by Staging Stack. +# +# Usage: +# Staging Stack: +# docker compose -f docker-compose.base.yml \ +# -f docker-compose.staging.yml \ +# -f docker-compose.staging-postgres-override.yml up +# +# Prerequisites: +# - PostgreSQL Stack must be running (creates app-internal network) +# - app-internal network must exist as external network +# - PostgreSQL Stack: deployment/stacks/postgresql/docker-compose.yml + +services: + # Staging Stack Services + # These services will be merged with existing definitions from base.yml + staging.yml + staging-app: + networks: + app-internal: {} # Add app-internal network for PostgreSQL access (merged with staging-internal) + staging-queue-worker: + networks: + app-internal: {} # Add app-internal network for PostgreSQL access (merged with staging-internal) + staging-scheduler: + networks: + app-internal: {} # Add app-internal network for PostgreSQL access (merged with staging-internal) + +networks: + app-internal: + external: true + name: app-internal diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml index 9e4f3fcf..6c44e4ab 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.staging.yml @@ -353,7 +353,7 @@ services: - TZ=Europe/Berlin - APP_ENV=staging - APP_DEBUG=${APP_DEBUG:-true} - # Database + # Database (can share with production or use separate) - DB_HOST=${DB_HOST:-postgres} - DB_PORT=${DB_PORT:-5432} - DB_DATABASE=${DB_DATABASE:-michaelschiemer_staging} @@ -408,7 +408,7 @@ services: - TZ=Europe/Berlin - APP_ENV=staging - APP_DEBUG=${APP_DEBUG:-true} - # Database + # Database (can share with production or use separate) - DB_HOST=${DB_HOST:-postgres} - DB_PORT=${DB_PORT:-5432} - DB_DATABASE=${DB_DATABASE:-michaelschiemer_staging} @@ -471,6 +471,9 @@ networks: external: true staging-internal: driver: bridge + app-internal: + external: true + name: app-internal volumes: staging-code: diff --git a/docs/deployment/production-compose-consolidation-plan.md b/docs/deployment/production-compose-consolidation-plan.md new file mode 100644 index 00000000..73d54950 --- /dev/null +++ b/docs/deployment/production-compose-consolidation-plan.md @@ -0,0 +1,262 @@ +# Plan: Konsolidierung der Production Docker-Compose Konfiguration + +**Datum**: 2025-11-04 +**Ziel**: `docker-compose.production.yml` im Root verwenden statt `deployment/stacks/application/docker-compose.yml` + +## Aktuelle Situation + +### Datei 1: `docker-compose.production.yml` (Repository Root) +**Typ**: Override-Datei f?r `docker-compose.base.yml` +**Status**: ? Aktiv verwendet in Deployment-Skripten +**Services**: `web`, `php`, `db`, `redis`, `queue-worker`, `certbot` +**Struktur**: Base + Override Pattern + +**Verwendung**: +```bash +docker compose -f docker-compose.base.yml -f docker-compose.production.yml up -d +``` + +### Datei 2: `deployment/stacks/application/docker-compose.yml` +**Typ**: Vollst?ndige Docker-Compose-Definition +**Status**: ?? Wird noch in einigen Dokumenten referenziert, aber vermutlich nicht mehr aktiv verwendet +**Services**: `app`, `nginx`, `redis`, `queue-worker`, `scheduler` +**Struktur**: Standalone-Definition + +**Referenzen gefunden in**: +- `deployment/ansible/inventory/production.yml` +- `deployment/docs/reference/application-stack.md` +- `deployment/README.md` +- Verschiedene Test-Dokumente + +## Vergleich der Services + +| Service | docker-compose.production.yml | application/docker-compose.yml | +|---------|-------------------------------|-------------------------------| +| **PHP Runtime** | `php` | `app` | +| **Web Server** | `web` | `nginx` | +| **Database** | `db` | ? (nicht definiert, nutzt externen PostgreSQL) | +| **Redis** | `redis` | `redis` | +| **Queue Worker** | `queue-worker` | `queue-worker` | +| **Scheduler** | ? (nicht definiert) | `scheduler` ? | +| **Certbot** | `certbot` ? | ? | + +## Unterschiede + +### 1. Service-Namen +- **production.yml**: `web`, `php` (konsistent mit base.yml) +- **application.yml**: `nginx`, `app` (eigene Namenskonvention) + +### 2. Scheduler Service +- **production.yml**: ? Fehlt +- **application.yml**: ? Vorhanden (`scheduler`) + +### 3. Certbot Service +- **production.yml**: ? Vorhanden (f?r Let's Encrypt) +- **application.yml**: ? Fehlt + +### 4. Database Service +- **production.yml**: ? Definiert (`db`) +- **application.yml**: ? Nutzt externen PostgreSQL-Stack (`postgres`) + +### 5. Netzwerk-Konfiguration +- **production.yml**: Nutzt `frontend`, `backend`, `cache` (aus base.yml) +- **application.yml**: Nutzt `app-internal`, `traefik-public` (external networks) + +## Problemanalyse + +### Problem 1: Service-Namen-Mismatch +Die Service-Namen sind unterschiedlich, was zu Verwirrung f?hrt: +- `php` vs `app` +- `web` vs `nginx` + +### Problem 2: Fehlender Scheduler +`docker-compose.production.yml` hat keinen `scheduler` Service, der in `application/docker-compose.yml` vorhanden ist. + +### Problem 3: Unterschiedliche Architektur +- **production.yml**: Nutzt Base+Override Pattern mit `db` Service +- **application.yml**: Nutzt externen PostgreSQL-Stack (`postgres`) + +## L?sungsplan + +### Phase 1: Scheduler zu production.yml hinzuf?gen + +**Ziel**: `scheduler` Service in `docker-compose.production.yml` hinzuf?gen + +**Begr?ndung**: Der Scheduler ist wichtig f?r Cron-Jobs und sollte in Production verf?gbar sein. + +**Implementierung**: +```yaml +# In docker-compose.production.yml hinzuf?gen: +scheduler: + # Production restart policy + restart: always + + # Use same build as php service + image: git.michaelschiemer.de:5000/framework:latest + + # Production volumes + volumes: + - /home/deploy/michaelschiemer/current:/var/www/html:ro + - storage:/var/www/html/storage:rw + - var-data:/var/www/html/var:rw + - /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro + + environment: + - APP_ENV=production + - APP_DEBUG=false + # Use Docker Secrets + - DB_PASSWORD_FILE=/run/secrets/db_user_password + - REDIS_PASSWORD_FILE=/run/secrets/redis_password + - APP_KEY_FILE=/run/secrets/app_key + - VAULT_ENCRYPTION_KEY_FILE=/run/secrets/vault_encryption_key + secrets: + - db_user_password + - redis_password + - app_key + - vault_encryption_key + + command: php console.php scheduler:run + + # Health checks + healthcheck: + test: ["CMD-SHELL", "php -r 'exit(0);' && test -f /var/www/html/console.php || exit 1"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 30s + + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + php: + condition: service_healthy +``` + +### Phase 2: Referenzen aktualisieren + +**Ziel**: Alle Referenzen auf `application/docker-compose.yml` entfernen/aktualisieren + +**Dateien zu aktualisieren**: +1. `deployment/ansible/inventory/production.yml` - `compose_file` entfernen/aktualisieren +2. `deployment/docs/reference/application-stack.md` - Dokumentation aktualisieren +3. `deployment/README.md` - Referenzen aktualisieren +4. Test-Dokumente - Referenzen aktualisieren + +### Phase 3: application/docker-compose.yml entfernen + +**Voraussetzungen**: +- ? Alle Referenzen aktualisiert +- ? Scheduler in production.yml hinzugef?gt +- ? Deployment-Skripte testen +- ? Production-Deployment erfolgreich + +**Aktion**: +- `deployment/stacks/application/docker-compose.yml` l?schen +- `deployment/stacks/application/docker-compose.base.yml` pr?fen (falls redundant) +- Backup erstellen vor L?schung + +### Phase 4: PostgreSQL-Stack Integration + +**Wichtig**: Da `docker-compose.production.yml` aktuell einen eigenen `db` Service definiert, aber wir zum PostgreSQL-Stack verbinden wollen: + +**Option A**: `db` Service aus production.yml entfernen +- Nutzt externen PostgreSQL-Stack (`postgres`) +- F?gt `app-internal` Netzwerk hinzu (via `docker-compose.postgres-override.yml`) + +**Option B**: `db` Service behalten (wenn separate Production-DB gew?nscht) +- Nutzt eigenen `db` Service +- Keine Verbindung zum PostgreSQL-Stack n?tig + +**Empfehlung**: Option A - Nutze externen PostgreSQL-Stack f?r Konsistenz + +## Migrationspfad + +### Schritt 1: Scheduler hinzuf?gen +```bash +# 1. Scheduler zu docker-compose.production.yml hinzuf?gen +# 2. Testen lokal +docker compose -f docker-compose.base.yml -f docker-compose.production.yml config +``` + +### Schritt 2: Referenzen pr?fen +```bash +# Alle Referenzen finden +grep -r "stacks/application/docker-compose" deployment/ +grep -r "application/docker-compose" deployment/ +``` + +### Schritt 3: Referenzen aktualisieren +- Dokumentation aktualisieren +- Ansible-Playbooks pr?fen +- Deployment-Skripte aktualisieren + +### Schritt 4: Testing +```bash +# Test Production-Deployment +docker compose -f docker-compose.base.yml \ + -f docker-compose.production.yml \ + -f docker-compose.postgres-override.yml \ + config + +# Alle Services pr?fen +docker compose -f docker-compose.base.yml \ + -f docker-compose.production.yml \ + -f docker-compose.postgres-override.yml \ + ps +``` + +### Schritt 5: Cleanup +```bash +# Backup erstellen +cp deployment/stacks/application/docker-compose.yml \ + deployment/stacks/application/docker-compose.yml.backup + +# Datei entfernen +rm deployment/stacks/application/docker-compose.yml +``` + +## Checkliste + +- [ ] Scheduler Service zu `docker-compose.production.yml` hinzuf?gen +- [ ] Alle Referenzen auf `application/docker-compose.yml` finden +- [ ] Dokumentation aktualisieren +- [ ] Ansible-Playbooks aktualisieren +- [ ] Deployment-Skripte testen +- [ ] Production-Deployment testen +- [ ] Backup von `application/docker-compose.yml` erstellen +- [ ] `application/docker-compose.yml` entfernen +- [ ] `docker-compose.postgres-override.yml` integrieren (f?r PostgreSQL-Stack) + +## Risiken + +### Risiko 1: Service-Namen-?nderungen +**Problem**: Services haben unterschiedliche Namen (`php` vs `app`, `web` vs `nginx`) + +**L?sung**: +- Deployment-Skripte m?ssen Service-Namen aktualisieren +- `php` statt `app`, `web` statt `nginx` + +### Risiko 2: Fehlende Features +**Problem**: `application/docker-compose.yml` k?nnte Features haben, die in `production.yml` fehlen + +**L?sung**: +- Vergleich durchf?hren +- Scheduler bereits identifiziert und wird hinzugef?gt +- Weitere Features pr?fen + +### Risiko 3: Referenzen vergessen +**Problem**: Versteckte Referenzen k?nnten zu Fehlern f?hren + +**L?sung**: +- Systematische Suche nach allen Referenzen +- Tests durchf?hren + +## N?chste Schritte + +1. ? Plan erstellt +2. ? Scheduler zu `docker-compose.production.yml` hinzuf?gen +3. ? Referenzen systematisch finden und aktualisieren +4. ? Testing durchf?hren +5. ? Cleanup: `application/docker-compose.yml` entfernen diff --git a/docs/deployment/shared-postgres-override-plan.md b/docs/deployment/shared-postgres-override-plan.md new file mode 100644 index 00000000..11b9daf6 --- /dev/null +++ b/docs/deployment/shared-postgres-override-plan.md @@ -0,0 +1,303 @@ +# Plan: Gemeinsame Docker-Compose Override f?r PostgreSQL-Verbindung + +**Datum**: 2025-11-04 +**Ziel**: Gemeinsame Override-Datei f?r Application- und Staging-Stack, die beide mit dem PostgreSQL-Stack verbinden k?nnen + +## Aktuelle Situation + +### Application Stack (`deployment/stacks/application/docker-compose.yml`) +- ? **Bereits im `app-internal` Netzwerk** +- ? **Kann sich bereits mit PostgreSQL verbinden** +- Services: `app`, `nginx`, `queue-worker`, `scheduler` +- Netzwerk: `app-internal` (external: true) + +### Staging Stack (`docker-compose.staging.yml`) +- ? **Nur im `staging-internal` Netzwerk** +- ? **Kann sich NICHT mit PostgreSQL verbinden** +- Services: `staging-app`, `staging-nginx`, `staging-queue-worker`, `staging-scheduler` +- Netzwerk: `staging-internal` (driver: bridge) + +### PostgreSQL Stack (`deployment/stacks/postgresql/docker-compose.yml`) +- Service: `postgres` +- Netzwerk: `app-internal` (external: true) + +## L?sung: Gemeinsame Override-Datei + +### Datei-Struktur + +``` +Repository Root/ +??? docker-compose.base.yml +??? docker-compose.staging.yml +??? docker-compose.postgres-override.yml # NEU: Gemeinsame Override +??? deployment/ + ??? stacks/ + ??? application/ + ? ??? docker-compose.yml + ??? postgresql/ + ??? docker-compose.yml +``` + +### Vorteile + +1. ? **DRY-Prinzip**: Netzwerk-Konfiguration zentral +2. ? **Wartbarkeit**: ?nderungen an einem Ort +3. ? **Konsistenz**: Beide Stacks nutzen identische Konfiguration +4. ? **Flexibilit?t**: Kann einfach aktiviert/deaktiviert werden + +## Implementierungsplan + +### 1. Neue Datei: `docker-compose.postgres-override.yml` + +**Inhalt**: Netzwerk-Definition und Service-Overrides f?r PostgreSQL-Verbindung + +```yaml +# Shared PostgreSQL Connection Override +# Usage: +# Application Stack: +# docker compose -f deployment/stacks/application/docker-compose.yml \ +# -f docker-compose.postgres-override.yml up +# +# Staging Stack: +# docker compose -f docker-compose.base.yml \ +# -f docker-compose.staging.yml \ +# -f docker-compose.postgres-override.yml up + +services: + # Application Stack Services + app: + networks: + app-internal: {} # Ensure app-internal network is used + nginx: + networks: + app-internal: {} # Ensure app-internal network is used + queue-worker: + networks: + app-internal: {} # Ensure app-internal network is used + scheduler: + networks: + app-internal: {} # Ensure app-internal network is used + + # Staging Stack Services + staging-app: + networks: + app-internal: {} # Add app-internal network + staging-queue-worker: + networks: + app-internal: {} # Add app-internal network + staging-scheduler: + networks: + app-internal: {} # Add app-internal network + +networks: + app-internal: + external: true + name: app-internal +``` + +### 2. Problem: Service-Namen sind unterschiedlich + +**Application Stack**: `app`, `nginx`, `queue-worker`, `scheduler` +**Staging Stack**: `staging-app`, `staging-nginx`, `staging-queue-worker`, `staging-scheduler` + +**L?sung**: Die Override-Datei kann beide Service-Namen unterst?tzen (Docker Compose ignoriert nicht-existierende Services). + +### 3. Alternative: Zwei separate Override-Dateien + +**Option A**: Eine Datei f?r beide (einfacher) +- ? Einfacher zu warten +- ?? Enth?lt Service-Definitionen f?r beide Stacks + +**Option B**: Zwei Dateien (sauberer) +- `docker-compose.postgres-override-app.yml` (f?r Application Stack) +- `docker-compose.postgres-override-staging.yml` (f?r Staging Stack) +- ? Klarer getrennt +- ?? Mehr Dateien + +**Empfehlung**: Option A - eine Datei, da die Konfiguration identisch ist + +## Detaillierte Implementierung + +### Datei: `docker-compose.postgres-override.yml` + +```yaml +# Shared PostgreSQL Connection Override +# +# This file provides network configuration for connecting to the PostgreSQL Stack. +# It can be used by both Application Stack and Staging Stack. +# +# Usage: +# Application Stack: +# docker compose -f deployment/stacks/application/docker-compose.yml \ +# -f docker-compose.postgres-override.yml up +# +# Staging Stack: +# docker compose -f docker-compose.base.yml \ +# -f docker-compose.staging.yml \ +# -f docker-compose.postgres-override.yml up +# +# Prerequisites: +# - PostgreSQL Stack must be running (creates app-internal network) +# - app-internal network must exist as external network + +services: + # Application Stack Services + # (These will be ignored if not present in the base compose file) + app: + networks: + app-internal: {} # Ensure app-internal network is available + nginx: + networks: + app-internal: {} # Ensure app-internal network is available + queue-worker: + networks: + app-internal: {} # Ensure app-internal network is available + scheduler: + networks: + app-internal: {} # Ensure app-internal network is available + + # Staging Stack Services + # (These will be ignored if not present in the base compose file) + staging-app: + networks: + app-internal: {} # Add app-internal network for PostgreSQL access + staging-queue-worker: + networks: + app-internal: {} # Add app-internal network for PostgreSQL access + staging-scheduler: + networks: + app-internal: {} # Add app-internal network for PostgreSQL access + +networks: + app-internal: + external: true + name: app-internal +``` + +### Wichtig: Netzwerk-Merge-Verhalten + +Docker Compose **merged** Netzwerke, wenn ein Service mehrere Netzwerke hat: + +```yaml +# In docker-compose.staging.yml +staging-app: + networks: + - staging-internal + +# In docker-compose.postgres-override.yml +staging-app: + networks: + app-internal: {} + +# Ergebnis: staging-app ist in BEIDEN Netzwerken +# - staging-internal (aus staging.yml) +# - app-internal (aus override.yml) +``` + +## Verwendung + +### Application Stack + +**Aktuell**: +```bash +cd deployment/stacks/application +docker compose -f docker-compose.yml up -d +``` + +**Mit Override** (optional, da bereits im app-internal Netzwerk): +```bash +cd deployment/stacks/application +docker compose -f docker-compose.yml \ + -f ../../docker-compose.postgres-override.yml up -d +``` + +### Staging Stack + +**Aktuell**: +```bash +docker compose -f docker-compose.base.yml \ + -f docker-compose.staging.yml up -d +``` + +**Mit Override** (f?r PostgreSQL-Verbindung): +```bash +docker compose -f docker-compose.base.yml \ + -f docker-compose.staging.yml \ + -f docker-compose.postgres-override.yml up -d +``` + +## Deployment-Skripte anpassen + +### Ansible Playbooks + +**`deployment/ansible/playbooks/deploy-staging.yml`**: +```yaml +- name: Start staging stack + command: > + docker compose + -f docker-compose.base.yml + -f docker-compose.staging.yml + -f docker-compose.postgres-override.yml + up -d + args: + chdir: "{{ staging_deploy_path }}" +``` + +**`deployment/ansible/playbooks/deploy-production.yml`**: +```yaml +- name: Start application stack + command: > + docker compose + -f docker-compose.yml + -f ../../docker-compose.postgres-override.yml + up -d + args: + chdir: "{{ app_deploy_path }}/deployment/stacks/application" +``` + +## Vorteile dieser L?sung + +1. ? **Zentrale Konfiguration**: Netzwerk-Setup an einem Ort +2. ? **Wiederverwendbar**: Beide Stacks nutzen die gleiche Datei +3. ? **Optional**: Kann bei Bedarf weggelassen werden +4. ? **Flexibel**: Einfach erweiterbar f?r weitere Services +5. ? **DRY**: Keine Duplikation von Netzwerk-Konfiguration + +## Nachteile / ?berlegungen + +1. ?? **Pfad-Abh?ngigkeit**: Relative Pfade in verschiedenen Verzeichnissen + - **L?sung**: Absolute Pfade oder von Repository-Root aus ausf?hren + +2. ?? **Service-Namen**: Docker Compose ignoriert nicht-existierende Services + - **Status**: ? Funktioniert - Docker Compose merged nur existierende Services + +3. ?? **Netzwerk muss existieren**: `app-internal` muss vorher erstellt sein + - **Status**: ? Wird automatisch vom PostgreSQL-Stack erstellt + +## Alternative: Inline-Netzwerk-Konfiguration + +**Alternativ** k?nnte das Netzwerk auch direkt in den jeweiligen Compose-Dateien definiert werden: + +- `docker-compose.staging.yml`: Netzwerk `app-internal` hinzuf?gen +- `deployment/stacks/application/docker-compose.yml`: Bereits vorhanden + +**Nachteil**: Duplikation, weniger wartbar + +**Vorteil**: Keine zus?tzliche Override-Datei n?tig + +## Empfehlung + +**Gemeinsame Override-Datei** ist die beste L?sung, weil: +1. **DRY-Prinzip**: Keine Duplikation +2. **Wartbarkeit**: ?nderungen an einem Ort +3. **Flexibilit?t**: Kann optional verwendet werden +4. **Klarheit**: Explizite PostgreSQL-Verbindung sichtbar + +## N?chste Schritte + +1. ? Plan erstellt +2. ? Implementierung: `docker-compose.postgres-override.yml` erstellen +3. ? Staging Stack: Verwendung in Dokumentation/Deployment-Skripten +4. ? Application Stack: Optional (bereits funktionsf?hig, aber f?r Konsistenz) +5. ? Testing: Beide Stacks mit Override testen +6. ? Dokumentation: README-Dateien aktualisieren diff --git a/docs/deployment/staging-database-connection-analysis.md b/docs/deployment/staging-database-connection-analysis.md new file mode 100644 index 00000000..93aaa51e --- /dev/null +++ b/docs/deployment/staging-database-connection-analysis.md @@ -0,0 +1,185 @@ +# Analyse: Datenbankverbindung im staging-app Container + +**Datum**: 2025-11-04 +**Problem**: `staging-app` Container kann nicht zur Datenbank verbinden +**Fehlermeldung**: `could not translate host name "postgres" to address: Name or service not known` + +## Problem-Identifikation + +### Symptom +Der `staging-app` Container schl?gt fehl mit folgendem Fehler: +``` +[2025-11-04 01:32:28] [ERROR] appConsoleHandler --- Failed to invoke initializer method __invoke for class App\Framework\Database\ConnectionInitializer: Failed to connect to database 'michaelschiemer' on 'postgres': SQLSTATE[08006] [7] could not translate host name "postgres" to address: Name or service not known +``` + +### Root Cause Analysis + +#### 1. **Fehlender PostgreSQL-Service im Staging-Setup** + +**Problem**: In `docker-compose.staging.yml` wird `DB_HOST=${DB_HOST:-postgres}` konfiguriert (Zeilen 32, 357, 412), aber es existiert kein PostgreSQL-Service mit dem Namen `postgres` im Staging-Setup. + +**Beweis**: +- `docker-compose.staging.yml` Zeile 32: `DB_HOST=${DB_HOST:-postgres}` +- `docker-compose.staging.yml` Zeilen 456-458: Der `db` Service aus `docker-compose.base.yml` wird explizit deaktiviert: + ```yaml + db: + profiles: + - never + ``` + +#### 2. **Netzwerk-Isolation** + +**Problem**: Der `staging-app` Container ist nur im `staging-internal` Netzwerk (Zeile 18), aber es gibt keinen Datenbank-Service in diesem Netzwerk. + +**Netzwerk-Konfiguration**: +- `staging-app`: Netzwerk `staging-internal` (Zeile 18) +- `staging-redis`: Netzwerk `staging-internal` (Zeile 302) +- **Kein PostgreSQL-Service** im `staging-internal` Netzwerk + +#### 3. **Vergleich mit Production-Setup** + +**Production (`docker-compose.production.yml`)**: +- Zeile 154-196: `db` Service ist definiert und aktiv +- `db` Service ist im `backend` Netzwerk (aus `docker-compose.base.yml`) +- PHP-Services k?nnen sich mit `DB_HOST=postgres` verbinden, da der Service existiert + +**Staging (`docker-compose.staging.yml`)**: +- Zeilen 456-458: `db` Service wird deaktiviert +- Kein PostgreSQL-Service definiert +- Services versuchen trotzdem, sich mit `postgres` zu verbinden + +## Detaillierte Analyse der Konfiguration + +### Staging-Services, die DB_HOST verwenden + +1. **staging-app** (Zeile 13-205) + - `DB_HOST=${DB_HOST:-postgres}` (Zeile 32) + - Netzwerk: `staging-internal` + +2. **staging-queue-worker** (Zeile 346-399) + - `DB_HOST=${DB_HOST:-postgres}` (Zeile 357) + - Netzwerk: `staging-internal` + +3. **staging-scheduler** (Zeile 401-447) + - `DB_HOST=${DB_HOST:-postgres}` (Zeile 412) + - Netzwerk: `staging-internal` + +### Fehlende Komponenten + +1. **PostgreSQL-Service**: Nicht definiert in Staging-Konfiguration +2. **Netzwerk-Verbindung**: Kein Datenbank-Service im `staging-internal` Netzwerk +3. **Dependencies**: Keine `depends_on` f?r Datenbank-Service + +## L?sungsoptionen + +### Option 1: Eigener PostgreSQL-Service f?r Staging (Empfohlen) + +**Vorteile**: +- Vollst?ndige Isolation zwischen Staging und Production +- Keine Abh?ngigkeit von externen Datenbanken +- Konsistente Konfiguration mit anderen Services + +**Implementierung**: +```yaml +# In docker-compose.staging.yml hinzuf?gen: +staging-postgres: + image: postgres:16-alpine + container_name: staging-postgres + restart: unless-stopped + networks: + - staging-internal + environment: + - TZ=Europe/Berlin + - POSTGRES_DB=${DB_DATABASE:-michaelschiemer_staging} + - POSTGRES_USER=${DB_USERNAME} + - POSTGRES_PASSWORD_FILE=/run/secrets/db_user_password + secrets: + - db_user_password + volumes: + - staging-postgres-data:/var/lib/postgresql/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE:-michaelschiemer_staging}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + +volumes: + staging-postgres-data: + name: staging-postgres-data + +# DB_HOST in allen Services ?ndern: +- DB_HOST=staging-postgres # statt postgres +``` + +### Option 2: Externe Datenbank verwenden + +**Vorteile**: +- Keine zus?tzliche Container-Verwaltung +- Kann auf bestehende Datenbank-Infrastruktur zur?ckgreifen + +**Nachteile**: +- Abh?ngigkeit von externer Infrastruktur +- Netzwerk-Konfiguration komplexer (muss extern erreichbar sein) + +**Implementierung**: +- `DB_HOST` auf externe IP/Hostname setzen +- Sicherstellen, dass `staging-internal` Netzwerk Zugriff hat (oder externes Netzwerk verwenden) + +### Option 3: Base-Service `db` aktivieren und umbenennen + +**Vorteile**: +- Nutzt bestehende Konfiguration aus `docker-compose.base.yml` +- Minimaler Aufwand + +**Nachteile**: +- Kann Konflikte mit anderen Umgebungen geben +- Nicht so isoliert wie Option 1 + +**Implementierung**: +```yaml +# In docker-compose.staging.yml: +staging-postgres: + extends: + service: db + file: docker-compose.base.yml + container_name: staging-postgres + networks: + - staging-internal + # DB_HOST auf staging-postgres ?ndern +``` + +## Empfohlene L?sung + +**Option 1** ist die beste L?sung, weil: +1. **Vollst?ndige Isolation**: Staging hat eigene Datenbank +2. **Konsistenz**: Gleiche Struktur wie `staging-redis` +3. **Einfachheit**: Alle Services im gleichen Netzwerk +4. **Wartbarkeit**: Klare Struktur, leicht zu verstehen + +## N?chste Schritte + +1. ? Analyse abgeschlossen +2. ? Implementierung: PostgreSQL-Service f?r Staging hinzuf?gen +3. ? `DB_HOST` in allen Staging-Services auf `staging-postgres` ?ndern +4. ? `depends_on` f?r Datenbank-Service hinzuf?gen +5. ? Volume f?r PostgreSQL-Daten definieren +6. ? Testing: Verbindung testen nach Implementierung + +## Zus?tzliche ?berlegungen + +### Netzwerk-Architektur +- `staging-internal`: Interne Services (app, postgres, redis, queue-worker, scheduler) +- `traefik-public`: Externes Netzwerk f?r Traefik (nur staging-nginx) + +### Sicherheit +- Docker Secrets f?r Datenbank-Passwort verwenden (bereits konfiguriert) +- Keine Ports nach au?en f?r PostgreSQL (nur intern) +- Gesundheitschecks f?r Datenbank-Service + +### Performance +- PostgreSQL-Volumes f?r Persistenz +- Gesundheitschecks f?r alle Services +- `depends_on` mit `condition: service_healthy` f?r Datenbank diff --git a/docs/deployment/staging-postgres-connection-plan.md b/docs/deployment/staging-postgres-connection-plan.md new file mode 100644 index 00000000..83dda0fc --- /dev/null +++ b/docs/deployment/staging-postgres-connection-plan.md @@ -0,0 +1,209 @@ +# Plan: Verbindung zum PostgreSQL-Stack f?r Staging + +**Datum**: 2025-11-04 +**Ziel**: Staging-Services sollen sich mit dem PostgreSQL-Container aus dem PostgreSQL-Stack verbinden k?nnen + +## Aktuelle Situation + +### PostgreSQL-Stack (`deployment/stacks/postgresql/docker-compose.yml`) +- **Service-Name**: `postgres` +- **Container-Name**: `postgres` +- **Netzwerk**: `app-internal` (external: true) +- **Port**: 5432 + +### Staging-Stack (`docker-compose.staging.yml`) +- **Netzwerk**: `staging-internal` (driver: bridge, **nicht external**) +- **Problem**: `staging-app` ist nur im `staging-internal` Netzwerk und kann daher den `postgres` Container nicht erreichen + +### Application-Stack (Production) (`deployment/stacks/application/docker-compose.yml`) +- **Netzwerk**: `app-internal` (external: true) +- **Status**: ? Kann sich mit `postgres` verbinden (gleiches Netzwerk) + +## L?sung: Multi-Network-Architektur + +Die Staging-Services m?ssen **beide Netzwerke** nutzen: +1. **`app-internal`** (external) - f?r Zugriff auf PostgreSQL-Stack +2. **`staging-internal`** (internal) - f?r interne Staging-Services (Redis, etc.) + +## Implementierungsplan + +### 1. Netzwerk-Konfiguration anpassen + +**?nderungen in `docker-compose.staging.yml`:** + +#### A. `app-internal` Netzwerk als external definieren + +```yaml +networks: + traefik-public: + external: true + staging-internal: + driver: bridge + app-internal: # NEU: F?r PostgreSQL-Zugriff + external: true + name: app-internal +``` + +#### B. Staging-Services an beide Netzwerke anschlie?en + +**Services die PostgreSQL ben?tigen:** +- `staging-app` ? `app-internal` + `staging-internal` +- `staging-queue-worker` ? `app-internal` + `staging-internal` +- `staging-scheduler` ? `app-internal` + `staging-internal` + +**Services die KEINE PostgreSQL ben?tigen:** +- `staging-nginx` ? bleibt bei `traefik-public` + `staging-internal` +- `staging-redis` ? bleibt bei `staging-internal` (nur intern) + +### 2. DB_HOST Konfiguration + +**Aktuell**: `DB_HOST=${DB_HOST:-postgres}` +**Status**: ? Korrekt - `postgres` ist der Service-Name im PostgreSQL-Stack + +**Keine ?nderung n?tig**, da `postgres` der korrekte Hostname ist, sobald die Services im `app-internal` Netzwerk sind. + +### 3. Dependencies hinzuf?gen + +**Optional**: `depends_on` f?r PostgreSQL k?nnte hinzugef?gt werden, aber da der PostgreSQL-Stack extern ist, sollte besser auf Health-Checks verzichtet werden (externer Service kann nicht direkt abh?ngig sein). + +**Alternative**: Warten auf PostgreSQL-Verf?gbarkeit im Entrypoint-Script. + +### 4. Netzwerk-Architektur + +``` +??????????????????????????????????????????????????????????? +? PostgreSQL Stack (deployment/stacks/postgresql/) ? +? ??????????????????????????????????????????????????????? ? +? ? postgres Container ? ? +? ? Network: app-internal (external) ? ? +? ??????????????????????????????????????????????????????? ? +??????????????????????????????????????????????????????????? + ? + ? + ? app-internal (external network) + ? +???????????????????????????????????????????????????????????? +? Staging Stack (docker-compose.staging.yml) ? +? ? +? ???????????????????? ???????????????????? ? +? ? staging-app ? ? staging-queue- ? ? +? ? Networks: ? ? worker ? ? +? ? ? app-internal ? ? Networks: ? ? +? ? ? staging-internal? ? ? app-internal ? ? +? ???????????????????? ? ? staging-internal? ? +? ???????????????????? ? +? ? +? ???????????????????? ???????????????????? ? +? ? staging-redis ? ? staging-nginx ? ? +? ? Networks: ? ? Networks: ? ? +? ? ? staging- ? ? ? traefik-public ? ? +? ? internal ? ? ? staging-internal? ? +? ???????????????????? ???????????????????? ? +???????????????????????????????????????????????????????????? +``` + +## Konkrete ?nderungen + +### Datei: `docker-compose.staging.yml` + +#### 1. Networks-Sektion erweitern + +```yaml +networks: + traefik-public: + external: true + staging-internal: + driver: bridge + app-internal: # NEU: F?r PostgreSQL-Zugriff + external: true + name: app-internal +``` + +#### 2. staging-app: app-internal Netzwerk hinzuf?gen + +```yaml +staging-app: + networks: + - app-internal # NEU: F?r PostgreSQL + - staging-internal # Bestehend: F?r Redis +``` + +#### 3. staging-queue-worker: app-internal Netzwerk hinzuf?gen + +```yaml +staging-queue-worker: + networks: + - app-internal # NEU: F?r PostgreSQL + - staging-internal # Bestehend: F?r Redis +``` + +#### 4. staging-scheduler: app-internal Netzwerk hinzuf?gen + +```yaml +staging-scheduler: + networks: + - app-internal # NEU: F?r PostgreSQL + - staging-internal # Bestehend: F?r Redis +``` + +## Vorteile dieser L?sung + +1. ? **Keine ?nderung am PostgreSQL-Stack n?tig** +2. ? **Staging nutzt bestehende PostgreSQL-Infrastruktur** +3. ? **Isolation bleibt erhalten**: Staging-Services kommunizieren intern ?ber `staging-internal` +4. ? **Konsistent mit Production**: Production nutzt ebenfalls `app-internal` +5. ? **Minimale ?nderungen**: Nur Netzwerk-Konfiguration, keine DB_HOST-?nderung n?tig + +## Nachteile / ?berlegungen + +1. ?? **Netzwerk-Abh?ngigkeit**: Staging h?ngt von externem `app-internal` Netzwerk ab + - **L?sung**: Netzwerk muss vor Staging-Start existieren (sollte automatisch sein) + +2. ?? **Shared Database**: Staging und Production k?nnten theoretisch die gleiche DB nutzen + - **Aktuell**: `DB_DATABASE=${DB_DATABASE:-michaelschiemer_staging}` verhindert dies + - **Empfehlung**: Separate Datenbank f?r Staging (`michaelschiemer_staging`) + +## Testing-Schritte nach Implementierung + +1. **Netzwerk-Verf?gbarkeit pr?fen**: + ```bash + docker network inspect app-internal | grep postgres + ``` + +2. **Container-Verbindung testen**: + ```bash + docker exec staging-app ping -c 1 postgres + ``` + +3. **PostgreSQL-Verbindung testen**: + ```bash + docker exec staging-app php -r " + \$dsn = 'pgsql:host=postgres;port=5432;dbname=' . getenv('DB_DATABASE'); + \$pdo = new PDO(\$dsn, getenv('DB_USERNAME'), file_get_contents(getenv('DB_PASSWORD_FILE'))); + echo 'Connection successful: ' . \$pdo->query('SELECT version()')->fetchColumn(); + " + ``` + +4. **Application-Logs pr?fen**: + ```bash + docker logs staging-app | grep -i "database\|postgres\|connection" + ``` + +## N?chste Schritte + +1. ? Plan erstellt +2. ? Implementierung: Netzwerk-Konfiguration anpassen +3. ? Testing: Verbindung testen +4. ? Dokumentation: README aktualisieren + +## Alternative Ans?tze (nicht empfohlen) + +### Alternative 1: Eigener PostgreSQL f?r Staging +- **Vorteil**: Vollst?ndige Isolation +- **Nachteil**: Mehr Ressourcen, mehr Wartungsaufwand +- **Status**: Nicht gew?nscht (Benutzer m?chte PostgreSQL-Stack nutzen) + +### Alternative 2: Externe PostgreSQL-URL +- **Vorteil**: Flexibler +- **Nachteil**: Komplexer, erfordert externe Netzwerk-Konfiguration +- **Status**: Nicht n?tig (Docker-Netzwerke sind ausreichend) diff --git a/src/Framework/Core/AppBootstrapper.php b/src/Framework/Core/AppBootstrapper.php index d7ca42b3..00f6f470 100644 --- a/src/Framework/Core/AppBootstrapper.php +++ b/src/Framework/Core/AppBootstrapper.php @@ -51,13 +51,15 @@ final readonly class AppBootstrapper // Initialize environment with encryption support $env = $this->initializeEnvironment(); + // Workaround: If REDIS_PASSWORD_FILE is not set but expected file exists, set it manually // This handles cases where Docker Compose doesn't set the variable correctly - $expectedRedisPasswordFile = '/run/secrets/redis_password'; - if (!$env->has('REDIS_PASSWORD_FILE') && file_exists($expectedRedisPasswordFile)) { - // Add REDIS_PASSWORD_FILE to environment if file exists - $env = $env->withVariable('REDIS_PASSWORD_FILE', $expectedRedisPasswordFile); - } + #$expectedRedisPasswordFile = '/run/secrets/redis_password'; + #if (!$env->has('REDIS_PASSWORD_FILE') && file_exists($expectedRedisPasswordFile)) { + # // Add REDIS_PASSWORD_FILE to environment if file exists + # #$env = $env->withVariable('REDIS_PASSWORD_FILE', $expectedRedisPasswordFile); + # + #} // Make Environment available throughout the application $this->container->instance(Environment::class, $env); @@ -128,12 +130,13 @@ final readonly class AppBootstrapper { $this->collector->startTiming('bootstrap', PerformanceCategory::SYSTEM); - $this->bootstrapper->bootstrap($this->basePath, $this->collector); + // Get Environment from container (registered in constructor) + $env = $this->container->get(Environment::class); + $this->bootstrapper->bootstrap($this->basePath, $this->collector, $env); $this->collector->endTiming('bootstrap'); // Initialize secrets management after container is bootstrapped - $env = $this->container->get(Environment::class); $this->initializeSecretsManagement($env); // ErrorHandler wird jetzt kontextabhängig registriert diff --git a/src/Framework/Core/ContainerBootstrapper.php b/src/Framework/Core/ContainerBootstrapper.php index 99b30aec..742f4e76 100644 --- a/src/Framework/Core/ContainerBootstrapper.php +++ b/src/Framework/Core/ContainerBootstrapper.php @@ -42,16 +42,17 @@ final readonly class ContainerBootstrapper public function bootstrap( string $basePath, PerformanceCollectorInterface $collector, + Environment $environment, ): Container { // Temporarily disable compiled container to fix bootstrap issues // TODO: Re-enable once container interface compatibility is fixed - // $optimizedContainer = $this->tryLoadCompiledContainer($basePath, $collector); + // $optimizedContainer = $this->tryLoadCompiledContainer($basePath, $collector, $environment); // if ($optimizedContainer !== null) { // return $optimizedContainer; // } // Fallback to fresh container bootstrap - return $this->bootstrapFreshContainer($basePath, $collector); + return $this->bootstrapFreshContainer($basePath, $collector, $environment); } /** @@ -59,7 +60,8 @@ final readonly class ContainerBootstrapper */ private function tryLoadCompiledContainer( string $basePath, - PerformanceCollectorInterface $collector + PerformanceCollectorInterface $collector, + Environment $environment, ): ?Container { try { $cacheDir = sys_get_temp_dir() . '/framework-cache'; @@ -71,7 +73,7 @@ final readonly class ContainerBootstrapper } // Create temporary fresh container to validate against - $tempContainer = $this->createFreshContainer($basePath, $collector); + $tempContainer = $this->createFreshContainer($basePath, $collector, $environment); // Create compiler for validation $reflectionProvider = new CachedReflectionProvider(); @@ -86,8 +88,11 @@ final readonly class ContainerBootstrapper // Load and return compiled container $compiledContainer = ContainerCompiler::load($compiledPath); + // Environment must be registered first as runtime data that can't be compiled + $compiledContainer->instance(Environment::class, $environment); + // Add runtime instances that can't be compiled - $this->addRuntimeInstances($compiledContainer, $basePath, $collector); + $this->registerRuntimeServices($compiledContainer, $basePath, $collector, $environment); return $compiledContainer; @@ -108,9 +113,10 @@ final readonly class ContainerBootstrapper */ private function bootstrapFreshContainer( string $basePath, - PerformanceCollectorInterface $collector + PerformanceCollectorInterface $collector, + Environment $environment, ): Container { - $container = $this->createFreshContainer($basePath, $collector); + $container = $this->createFreshContainer($basePath, $collector, $environment); // Compile for next request (async in production) $this->compileContainerAsync($container); @@ -123,47 +129,87 @@ final readonly class ContainerBootstrapper */ private function createFreshContainer( string $basePath, - PerformanceCollectorInterface $collector + PerformanceCollectorInterface $collector, + Environment $environment, ): DefaultContainer { // Use the existing container or create new DefaultContainer $container = $this->container instanceof DefaultContainer ? $this->container : new DefaultContainer(); - $this->addRuntimeInstances($container, $basePath, $collector); + // Environment must be registered first as it's required by other services + // Check if already registered (e.g. by AppBootstrapper) to avoid overwriting + if (! $container->has(Environment::class)) { + $container->instance(Environment::class, $environment); + } + + $this->registerRuntimeServices($container, $basePath, $collector, $environment); $this->autowire($container); return $container; } /** - * Add instances that must be created at runtime + * Register all runtime services that must be created at runtime */ - private function addRuntimeInstances( + private function registerRuntimeServices( Container $container, string $basePath, - PerformanceCollectorInterface $collector + PerformanceCollectorInterface $collector, + Environment $environment, + ): void { + $this->registerCoreServices($container, $basePath, $collector); + $this->registerRedisAndCache($container, $collector, $environment); + $this->registerHttpServices($container); + $this->registerDatabaseServices($container, $environment); + } + + /** + * Register core services: Clock, PathProvider, Logger, PerformanceCollector + */ + private function registerCoreServices( + Container $container, + string $basePath, + PerformanceCollectorInterface $collector, ): void { - // Core services that need runtime data // Clock must be registered first as it's required by Logger // Only create if not already registered (e.g. by ClockInitializer) if (! $container->has(Clock::class)) { $container->instance(Clock::class, new SystemClock()); } - $clock = $container->get(Clock::class); + $container->instance(PathProvider::class, new PathProvider($basePath)); - $init = $container->get(LoggerInitializer::class); - - $logger = $container->invoker->invoke(LoggerInitializer::class, '__invoke'); - - $container->instance(Logger::class, $logger); $container->instance(PerformanceCollectorInterface::class, $collector); - $pool = new RedisPoolInitializer($container->get(Environment::class))->initialize(); - $container->instance(Cache::class, new CacheInitializer($collector, $container, $pool)()); + // Initialize Logger + $logger = $container->invoker->invoke(LoggerInitializer::class, '__invoke'); + $container->instance(Logger::class, $logger); + } + + /** + * Register Redis connection pool and Cache services + * Requires Environment to be registered in container + */ + private function registerRedisAndCache( + Container $container, + PerformanceCollectorInterface $collector, + Environment $environment, + ): void { + $pool = new RedisPoolInitializer($environment)->initialize(); + $container->instance(Cache::class, new CacheInitializer($collector, $container, $pool)()); + } + + /** + * Register HTTP-related services + * + * TODO: Remove manual Request binding once Discovery system properly handles RequestFactory Initializer + * The RequestFactory should be discovered via Initializer attribute and registered automatically + */ + private function registerHttpServices(Container $container): void { $container->instance(ResponseEmitter::class, new ResponseEmitter()); - // TEMPORARY FIX: Manual RequestFactory binding until Discovery issue is resolved + // TEMPORARY FIX: Manual Request binding until Discovery issue is resolved + // This should be handled by RequestFactory Initializer via Discovery system $container->singleton(Request::class, function ($container) { error_log("ContainerBootstrapper: Creating Request singleton"); @@ -179,13 +225,24 @@ final readonly class ContainerBootstrapper return $request; }); + } + /** + * Register database-related services + * + * TODO: Remove manual DatabasePlatform binding once Discovery system properly handles DatabasePlatformInitializer + * The DatabasePlatformInitializer should be discovered via Initializer attribute and registered automatically + */ + private function registerDatabaseServices( + Container $container, + Environment $environment, + ): void { // TEMPORARY FIX: Manual DatabasePlatform binding until Initializer Discovery issue is resolved - $container->singleton(\App\Framework\Database\Platform\DatabasePlatform::class, function ($container) { + // This should be handled by DatabasePlatformInitializer via Discovery system + $container->singleton(\App\Framework\Database\Platform\DatabasePlatform::class, function ($container) use ($environment) { error_log("ContainerBootstrapper: Creating DatabasePlatform singleton"); - $env = $container->get(\App\Framework\Config\Environment::class); - $driver = $env->get('DB_DRIVER', 'pgsql'); + $driver = $environment->get('DB_DRIVER', 'pgsql'); error_log("ContainerBootstrapper: DB_DRIVER = {$driver}"); @@ -200,7 +257,6 @@ final readonly class ContainerBootstrapper return $platform; }); - } /**