# LeoCRM Deployment ## Architektur LeoCRM läuft als **einzelner docker-compose Stack** in Coolify. Alle 4 Container (PostgreSQL, Redis, API, Worker) werden aus der `docker-compose.yaml` im Git-Repo gestartet und teilen sich ein Docker-Netzwerk. ``` ┌─────────────────────────────────────────────┐ │ Coolify Application (docker-compose) │ │ ┌──────────┐ ┌──────────┐ │ │ │ postgres │ │ redis │ │ │ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ ┌────┴─────┐ ┌────┴─────┐ │ │ │ crm_app │ │crm_worker│ │ │ │ (API) │ │ (ARQ) │ │ │ └──────────┘ └──────────┘ │ └─────────────────────────────────────────────┘ ``` Keine separaten Coolify Services für DB/Redis/Worker. Alles in einem Stack. ## Quick Start ### Redeploy (bestehende Anwendung) ```bash # Umgebungsvariablen setzen export COOLIFY_API_TOKEN="dein-token" export APP_DOMAIN="https://crm.media-on.de" export COOLIFY_APP_UUID="xf7smknlger3hvkrsb910tui" # optional, wird via APP_NAME gesucht # Redeploy via Coolify API python scripts/deploy.py # Verifikation nur python scripts/deploy.py --verify-only ``` Das Script macht automatisch: 1. Coolify Application auflösen (via UUID oder Name) 2. Deploy via `/api/v1/deploy` triggern 3. Auf Deployment-Erfolg warten 4. HTTP Health-Check verifizieren 5. Login-Test (optional, wenn LOGIN_EMAIL/LOGIN_PASSWORD gesetzt) 6. Alembic-Migration-Head prüfen (via SSH) 7. RLS-Tabellen zählen (via SSH) ### Initial Deployment (neue Anwendung) ```bash # Alle Umgebungsvariablen setzen export COOLIFY_API_TOKEN="dein-token" export APP_DOMAIN="https://crm.media-on.de" export APP_NAME="leocrm" # Coolify Application Name export DB_PASSWORD="..." export REDIS_PASSWORD="..." export SECRET_KEY="..." export COOLIFY_PROJECT_UUID="..." export COOLIFY_SERVER_UUID="..." export COOLIFY_PRIVATE_KEY_UUID="..." export COOLIFY_ENVIRONMENT="production" # optional, default: production # Initial deployment python scripts/deploy.py --initial ``` Das Script macht automatisch: 1. Coolify Application via `private-deploy-key` erstellen 2. Build Pack auf `dockercompose` setzen (liest docker-compose.yaml aus Git) 3. Environment-Variablen setzen (Secrets, Domain, Admin-Credentials) 4. Erster Deploy (ohne Domain — Coolify muss docker-compose.yaml lesen) 5. `docker_compose_domains` setzen + Redeploy (mit Traefik-Labels) 6. Verifikation (HTTP, Login, Alembic, RLS) ### Option B: Docker Compose (lokal / ohne Coolify) ```bash # .env.docker erstellen cp .env.docker.example .env.docker $EDITOR .env.docker # SECRET_KEY, DB_PASSWORD, REDIS_PASSWORD etc. ausfüllen # Starten (alle 4 Container: Postgres, Redis, App, Worker) docker compose --env-file .env.docker up --build -d # Health check curl http://localhost:8000/api/v1/health # Stoppen docker compose down ``` **Container:** - `postgres` — PostgreSQL 16 mit pgvector - `redis` — Redis 7 - `crm_app` — FastAPI API Server - `crm_worker` — ARQ Background Worker Alle mit persistenten Volumes. Kein Datenverlust bei Redeploy. ## Voraussetzungen - Python 3.12+ - `httpx` Python package - Docker & Docker Compose (für Option B) - Coolify v4+ (für Option A) - SSH-Zugang zum Server (für Verifikation, Option A) ## Umgebungsvariablen Siehe `.env.example` für alle Variablen. Wichtigste: | Variable | Pflicht | Default | Beschreibung | |---|---|---|---| | `COOLIFY_API_TOKEN` | Ja | — | Coolify API Token | | `APP_DOMAIN` | Ja | — | App-Domain (z.B. https://crm.media-on.de) | | `COOLIFY_APP_UUID` | Nein | — | Application UUID (auto-resolved via APP_NAME) | | `APP_NAME` | Nein | abgeleitet aus APP_DOMAIN | Coolify Application Name | | `SSH_KEY` | Nein | `/a0/usr/workdir/.ssh/coolify-01-root` | SSH Key für Verifikation | | `SERVER_IP` | Nein | `46.225.91.159` | Server IP für SSH | | `LOGIN_EMAIL` | Nein | — | Login-Test Email (optional) | | `LOGIN_PASSWORD` | Nein | — | Login-Test Passwort (optional) | ### Nur für `--initial`: | Variable | Pflicht | Beschreibung | |---|---|---| | `DB_PASSWORD` | Ja | PostgreSQL Passwort | | `REDIS_PASSWORD` | Ja | Redis Passwort | | `SECRET_KEY` | Ja | Application Secret Key (min. 32 Zeichen) | | `COOLIFY_PROJECT_UUID` | Ja | Coolify Project UUID | | `COOLIFY_SERVER_UUID` | Ja | Coolify Server UUID | | `COOLIFY_PRIVATE_KEY_UUID` | Ja | Coolify Private Deploy Key UUID | | `COOLIFY_ENVIRONMENT` | Nein | Coolify Environment (default: production) | ## S3 Storage (optional) Die App unterstützt S3-kompatiblen Storage. Setze: ```bash STORAGE_BACKEND=s3 S3_ENDPOINT=https://s3.example.com S3_BUCKET=leocrm S3_ACCESS_KEY=... S3_SECRET_KEY=... ``` ## Mehrere Instanzen Mehrere LeoCRM-Instanzen auf demselben Coolify-Server: ```bash # Test-Instanz APP_NAME=leocrm-test APP_DOMAIN=https://crm-test.media-on.de python scripts/deploy.py --initial # Produktions-Instanz APP_NAME=leocrm APP_DOMAIN=https://crm.media-on.de python scripts/deploy.py --initial ``` Jede Instanz hat eigene DB, Redis, Container und Domain. ## Troubleshooting **Container nicht healthy:** ```bash docker logs --tail 50 ``` **Migration fehlgeschlagen:** ```bash docker exec alembic upgrade head ``` **503 Fehler (Traefik):** - Domain ohne `:443` in `docker_compose_domains` setzen - Service-Namen mit Unterstrichen in docker-compose.yaml (crm_app, nicht crm-app) - `docker-compose.yaml` (nicht `.yml`) als Dateiname ## Backup & Restore ### Backup (PostgreSQL) ```bash # Full DB backup (run on the host or via docker exec) docker exec pg_dump -U crm_user -Fc crm_db > backup_$(date +%Y%m%d_%H%M%S).dump ``` ### Backup (Redis — Sessions/Queues) ```bash docker exec redis-cli -a "$REDIS_PASSWORD" SAVE docker cp :/data/dump.rdb redis_backup_$(date +%Y%m%d).rdb ``` ### Restore (PostgreSQL) ```bash # Stop app containers docker compose stop crm_app crm_worker # Restore DB docker exec -i pg_restore -U crm_user -d crm_db --clean < backup_20260726.dump # Restart app docker compose start crm_app crm_worker ``` ### Automatisierte Backups (Cron) ```bash # /etc/cron.d/leocrm-backup 0 2 * * * root docker exec pg_dump -U crm_user -Fc crm_db > /backups/leocrm_$(date +\%Y\%m\%d).dump 0 3 * * * root find /backups -name 'leocrm_*.dump' -mtime +30 -delete ``` **Empfehlung:** Tägliche DB-Backups, 30 Tage Aufbewahrung. Storage-Backup wöchentlich.