# SaaS: Instanz pro Kunde (Provisioning)

Dieses Dokument beschreibt den **Referenz-Deploy** unter [`deploy/`](../deploy/): Docker-Image, Compose-Stack (MariaDB + App + Traefik) und das Skript [`deploy/provision.sh`](../deploy/provision.sh).

## Überblick

1. **Image bauen** (`deploy/Dockerfile`): Symfony mit Apache, Docroot `public/`, Healthcheck `GET /health.php`.
2. **Stack** (`deploy/docker-compose.yml`): MariaDB 10.11, TerraFlow-App, Traefik (HTTPS, Let’s Encrypt).
3. **Nach dem Start**: `provision.sh` führt Migrationen, `assets:install`, `importmap:install` und `terraflow:tenant:bootstrap` aus (idempotent wo möglich).

## Voraussetzungen

- Docker mit Compose Plugin (`docker compose`) oder `docker-compose`
- Server mit öffentlicher IP, Ports **80** und **443** erreichbar (für Let’s Encrypt TLS-ALPN)
- **DNS**: Hostname aus `TRAEFIK_HOST` zeigt per A/AAAA-Record auf diesen Server **bevor** Traefik das Zertifikat anfordert

## Schnellstart

```bash
cd deploy
cp .env.example .env
# .env bearbeiten: TRAEFIK_HOST, ACME_EMAIL, Passwörter, DATABASE_URL (Host „database“), Bootstrap-Admin
./provision.sh
```

### Wichtige Umgebungsvariablen (`deploy/.env`)

| Variable | Bedeutung |
|----------|-----------|
| `TRAEFIK_HOST` | Öffentlicher Hostname (z. B. `cms.kunde.de`) |
| `ACME_EMAIL` | E-Mail für Let’s Encrypt (Traefik) |
| `MYSQL_*` / `DATABASE_URL` | `DATABASE_URL` muss zum MariaDB-Service passen: Host **`database`**, Port **3306**, `serverVersion=10.11.0-MariaDB` |
| `APP_SECRET` | `openssl rand -hex 32` |
| `MAILER_DSN` | Erforderlich für „Passwort vergessen“ (z. B. SMTP-DSN wie `smtp://user:pass@mail.example:587`) |
| `TERRAFLOW_BOOTSTRAP_*` | Erster Admin nach leerer Datenbank |

### Erster Admin (Bootstrap)

Der Befehl `php bin/console terraflow:tenant:bootstrap` legt einen Admin an, **wenn noch kein User mit dem gewählten Benutzernamen existiert** (idempotent).

- Umgebung: `TERRAFLOW_BOOTSTRAP_ADMIN_USERNAME`, `TERRAFLOW_BOOTSTRAP_ADMIN_EMAIL`, `TERRAFLOW_BOOTSTRAP_ADMIN_PASSWORD`
- Alternativ: `--username`, `--email`, `--password`

Der interaktive Befehl `terraflow:create:admin` bleibt für manuelle Installationen bestehen.

## Reverse Proxy und Symfony

In Produktion setzt [`config/packages/prod/framework.yaml`](../config/packages/prod/framework.yaml) `trusted_proxies` (private Netze + `REMOTE_ADDR`) und `trusted_headers`, damit HTTPS und Host hinter Traefik korrekt erkannt werden. Bei exotischen Setups kannst du diese Datei anpassen oder eigene `config/packages/prod/*.yaml` ergänzen.

## Image lokal bauen

Vom **Projektroot**:

```bash
docker build -f deploy/Dockerfile -t terraflow-cms:local .
```

Build-Zeit: [`deploy/.env.docker-build`](../deploy/.env.docker-build) stellt minimale Variablen für Composer/Runtime-Dateien bereit; **echte Secrets** kommen zur Laufzeit aus Container-Env bzw. `deploy/.env`.

## Backups (Kurzcheckliste)

- **Datenbank**: Dump der MariaDB-Volume-Daten oder `mysqldump` regelmäßig.
- **Dateien**: Volume `terraflow_uploads` (unter anderem `public/uploads`) und ggf. `terraflow_var` (Cache/Logs nach Bedarf).

## Updates

1. Neues Image bauen und taggen (gleiche Version wie Release).
2. `docker compose pull` / neu bauen, dann `docker compose up -d`.
3. Im App-Container: `php bin/console doctrine:migrations:migrate --no-interaction --env=prod`.
4. Optional: `php bin/console cache:clear --env=prod` (oder Warmup nach Bedarf).

## Alternative: Caddy statt Traefik

Der Stack ist bewusst modular: Du kannst Traefik durch **Caddy** ersetzen (automatisches HTTPS) und nur `app` + `database` aus der Compose-Datei übernehmen. Dann Ports/Labels anpassen und dieselben Umgebungsvariablen für die App verwenden.

## Troubleshooting

- **Zertifikat / ACME**: `deploy/traefik/acme.json` muss für den Container beschreibbar sein; erste Anforderung schlägt fehl, wenn Port 443 blockiert ist oder DNS noch nicht stimmt.
- **502 / falsches Schema**: `trusted_proxies` / `trusted_headers` in `config/packages/prod/framework.yaml` prüfen.
- **Doctrine**: `DATABASE_URL` muss `serverVersion` zur verwendeten MariaDB-Version enthalten.
