Skip to content

Deployment mit Coolify ​

Alternative zum CapRover-Deployment: Die ganze Plattform läuft als eine Coolify-Anwendung aus dem Git-Repository. Coolify baut alle Images auf dem Server aus docker-compose.coolify.yml, stellt über seinen Traefik-Proxy die Let's-Encrypt-Zertifikate aus und verwaltet die Variablen. Eine Registry und eine CI-Pipeline sind nicht nötig.

Was hinter den einzelnen Diensten steckt (Datenbankrollen, zwei Redis-Instanzen, Mercure, Worker, Scheduler, Backups), steht ausführlich in der Deployment-Doku. Diese Seite beschreibt nur, was bei Coolify anders ist.

Überblick ​

Dienst (Compose)Domain (Beispiel)Container-PortInhalt
dashboardhttps://app.pollyou.org8080Vue-SPA (Caddy), leitet /api intern an die API weiter
apihttps://api.pollyou.org8080Symfony/FrankenPHP, migriert beim Start
embedhttps://embed.pollyou.org8080Embed-Skript (CDN)
mercurehttps://rt.pollyou.org80Live-Ergebnisse (Server-Sent Events)
sitehttps://pollyou.org8080Landing Page (optional, ohne Domain läuft sie nur leer mit)
docshttps://docs.pollyou.org8080Doku mit Impressum, Datenschutzerklärung, AGB (/legal/)
worker––Messenger-Worker (Mails, Webhooks, Auswertung, Exporte)
scheduler––Geplante Aufgaben, genau ein Container
database––PostgreSQL 17 mit Rollen-Init (deploy/coolify)
redis-queue––Redis für die Messenger-Queue (AOF, persistent)
redis-cache––Redis für Cache und Rate-Limiter (flüchtig)
gotenberg––PDF-Berichte (nur intern)
backup––Tägliches pg_dump in einen S3-Speicher

Unterschiede zu CapRover:

  • Alle Dienste liegen in einem eigenen Docker-Netz und erreichen sich über den Dienstnamen (api:8080, database:5432); es gibt keine srv-captain--…-Namen.
  • Das Export-Verzeichnis (/var/lib/pollyou/exports) ist ein gemeinsames Docker-Volume von API, Worker und Scheduler, ohne Host-Pfad.
  • Secrets und Passwörter erzeugt Coolify beim ersten Laden der Compose-Datei (SERVICE_PASSWORD_*, SERVICE_HEX_64_*, SERVICE_BASE64_64_*) und behält sie über alle Deployments.
  • PostgreSQL- und Redis-Konfiguration stecken in kleinen Images (deploy/coolify/*.Dockerfile), damit keine Dateien aus dem Repository eingehängt werden müssen.

1. Server ​

  • Coolify v4 auf dem Server installiert (Installationsanleitung), der Proxy (Traefik) läuft: Servers → localhost → Proxy zeigt „Running“.
  • Größe: mindestens 4 vCPU und 8 GB RAM, für Produktion besser 8 vCPU und 16 GB RAM (z. B. Hetzner CPX41). Die Images werden auf dem Server gebaut, der erste Build braucht kurzzeitig 3–4 GB RAM.
  • Firewall: eingehend 22, 80 und 443. Port 8000 (Coolify-Oberfläche) nur, solange Coolify selbst noch keine Domain mit HTTPS hat.

2. DNS ​

A-Records (und ggf. AAAA) auf die IP des Servers, z. B. für pollyou.org:

NameZweck
appDashboard
apiAPI
embedEmbed-Skript
rtMercure (Live-Ergebnisse)
@Landing Page (optional)
wwwWeiterleitung (optional)

Die Zertifikate stellt Traefik beim ersten Aufruf über Let's Encrypt aus (HTTP-01). Die Records müssen also vor dem ersten Deployment auf den Server zeigen.

3. Zugriff auf das Repository ​

Das Repository liegt privat auf Bitbucket, Coolify braucht deshalb einen Deploy-Key:

  1. Coolify: Keys & Tokens → Private Keys → + Add und „Generate new ED25519 SSH Key“, Name z. B. bitbucket-pollyou.
  2. Den angezeigten öffentlichen Schlüssel in Bitbucket eintragen: Repository settings → Access keys → „Add key“ (nur Lesezugriff).

4. Anwendung anlegen ​

  1. Coolify: Projects → + Add (z. B. „PollYou“), Umgebung production.
  2. + New → Private Repository (with Deploy Key), den Schlüssel aus Schritt 3 wählen.
  3. Repository-URL git@bitbucket.org:<workspace>/pollyou.git, Branch main.
  4. Build Pack: Docker Compose, Base Directory /, Docker Compose Location /docker-compose.coolify.yml, dann „Continue“.
  5. Coolify liest die Datei ein und zeigt alle Dienste. Unter Environment Variables erscheinen die Pflichtvariablen (rot markiert) und die bereits erzeugten Secrets.

5. Domains zuweisen ​

In der Anwendung unter General → Domains je Dienst die Domain eintragen, mit dem Container-Port, sofern er nicht 80 ist:

DienstEintrag
dashboardhttps://app.pollyou.org:8080
apihttps://api.pollyou.org:8080
embedhttps://embed.pollyou.org:8080
mercurehttps://rt.pollyou.org
sitehttps://pollyou.org:8080 (optional)
docshttps://docs.pollyou.org:8080

Alle anderen Dienste bekommen keine Domain. Der Port legt nur fest, wohin Traefik intern weiterleitet; nach außen bleibt es bei 443.

6. Umgebungsvariablen ​

Unter Environment Variables setzen (Pflicht):

VariableBeispiel
PUBLIC_DASHBOARD_URLhttps://app.pollyou.org
PUBLIC_API_URLhttps://api.pollyou.org
PUBLIC_EMBED_URLhttps://embed.pollyou.org
PUBLIC_REALTIME_URLhttps://rt.pollyou.org
CORS_ALLOW_ORIGIN^https://app\.pollyou\.de$ (Regex, Punkte mit \ maskieren)
MAILER_DSNSMTP eines EU-Anbieters, z. B. smtp://user:pass@smtp.example.eu:587 (Sonderzeichen URL-kodieren)
MAILER_FROMPollYou <noreply@pollyou.org> (Domain mit SPF, DKIM und DMARC)

Die PUBLIC_*_URL müssen genau zu den Domains aus Schritt 5 passen (ohne Port und ohne / am Ende). Sie landen in E-Mail-Links, in der Content-Security-Policy des Dashboards, im Embed-Code und in der Landing Page.

Optional (leer = aus):

VariableWofür
PUBLIC_SITE_URL, PUBLIC_DOCS_URLLanding Page: eigene Adresse und Link zur Doku (leer = Vorgaben aus site.config.ts, Doku https://docs.pollyou.org); PUBLIC_DOCS_URL gilt auch für die Doku-Links im Dashboard
SITE_DEMO_SURVEY_IDLive-Demo-Umfrage auf der Landing Page
SENTRY_DSN, VITE_SENTRY_DSNFehler-Tracking für Backend bzw. Dashboard (EU-Region)
SENTRY_ENVIRONMENTStandard production
APP_RELEASERelease-Name für Sentry, z. B. pollyou@2026-10-14
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETGuthabenkauf mit Stripe; Webhook-Endpunkt https://api.<domain>/api/billing/stripe/webhook; leer = Kauf aus
BILLING_AUTOMATIC_TAXStripe Tax im Checkout (Standard 1)
OAUTH_GOOGLE_*, OAUTH_GITHUB_*OAuth-Login, Redirect-URI über PUBLIC_DASHBOARD_URL
ANTHROPIC_API_KEYKI-Auswertung von Freitext
DEEPL_API_KEYVorübersetzen im Übersetzungseditor
METRICS_TOKENMonitoring
CUSTOM_DOMAIN_TARGET, CUSTOM_DOMAIN_EDGE_SECRETKunden-Domains über den TLS-Edge (eigener Server)
FRANKENPHP_WORKER_COUNTPHP-Worker der API (leer = 2 × CPU-Kerne)
POLLYOU_PG_TUNING1 = PostgreSQL-Einstellungen für 16 GB RAM, wirkt nur beim allerersten Start der Datenbank
BACKUP_*, RCLONE_CONFIG_BACKUP_*Backups, siehe Schritt 9

Secrets sichern

Die von Coolify erzeugten Werte (SERVICE_PASSWORD_*, SERVICE_HEX_64_*, SERVICE_BASE64_64_*) nach dem ersten Laden in den Passwort-Manager übernehmen. SERVICE_BASE64_64_TWOFACTORKEY verschlüsselt die Zwei-Faktor-Secrets und darf sich nie ändern. Ein neuer SERVICE_HEX_64_JWTSECRET meldet alle Nutzer ab. Die Datenbank-Passwörter (SERVICE_PASSWORD_POSTGRES, …_PGAPP, …_PGWORKER) werden nur beim ersten Start in die Datenbank geschrieben; ändert man sie danach in Coolify, passen sie nicht mehr zur Datenbank.

7. Erstes Deployment ​

  1. Deploy klicken. Der erste Build dauert je nach Server 10–20 Minuten (PHP-Abhängigkeiten, vier Frontend-Builds). Fortschritt unter Deployments.

  2. Reihenfolge beim Start: Datenbank und Redis → API (führt die Migrationen aus) → Worker, Scheduler, Dashboard.

  3. Log der API (Logs → api): Ausgabe der Migrationen, danach „Row-level security: enforced for "pollyou_app"“ und der Start von FrankenPHP.

  4. Prüfen:

    • https://api.pollyou.org/health liefert {"status":"ok",…}
    • https://app.pollyou.org/healthz, https://embed.pollyou.org/healthz, https://docs.pollyou.org/healthz und https://rt.pollyou.org/healthz liefern 200
  5. Smoke-Test von einem beliebigen Rechner mit Repository-Checkout (legt ein Wegwerf-Konto smoke-…@example.com an):

    bash
    API_URL=https://api.pollyou.org DASHBOARD_URL=https://app.pollyou.org \
    EMBED_URL=https://embed.pollyou.org MERCURE_URL=https://rt.pollyou.org \
    MAILPIT_URL= COMPOSE_PROJECT= METRICS_TOKEN=<Wert aus Coolify oder leer> ci/smoke-prod.sh

    Ohne METRICS_TOKEN= prüft der Test die Metriken mit dem Token von make prod-check und schlägt dort fehl.

  6. Im Browser unter https://app.pollyou.org registrieren. Die Bestätigungs-Mail kommt vom Worker; bleibt sie aus, zeigt Logs → worker den SMTP-Fehler.

8. Automatisch deployen ​

Unter Webhooks in der Anwendung „Bitbucket“ wählen, ein Webhook-Secret setzen und die angezeigte URL in Bitbucket eintragen (Repository settings → Webhooks → „Add webhook“, Trigger „Repository push“, gleiches Secret). Jeder Push auf main löst dann ein Deployment aus. Wer lieber manuell ausrollt, lässt den Webhook weg und klickt Redeploy.

Das Deployment baut alle Images neu und ersetzt die Container. Die API migriert beim Start; Migrationen sind abwärtskompatibel (siehe Migrationen).

9. Backups ​

Ohne BACKUP_REMOTE erzeugt der Dienst backup keine Backups und schreibt nur eine Warnung ins Log. Für einen S3-kompatiblen Speicher (z. B. Hetzner Object Storage) setzen:

VariableBeispiel
BACKUP_REMOTEbackup:pollyou-backups/production
RCLONE_CONFIG_BACKUP_ENDPOINThttps://fsn1.your-objectstorage.com
RCLONE_CONFIG_BACKUP_ACCESS_KEY_IDZugangsschlüssel
RCLONE_CONFIG_BACKUP_SECRET_ACCESS_KEYSecret
BACKUP_AGE_RECIPIENTöffentlicher age-Schlüssel (age1…), empfohlen
BACKUP_HEARTBEAT_URLHeartbeat-URL des Uptime-Monitors
BACKUP_SCHEDULE, BACKUP_RETENTION_DAYSStandard 15 3 * * * (UTC) bzw. 14

RCLONE_CONFIG_BACKUP_TYPE (s3) und RCLONE_CONFIG_BACKUP_PROVIDER (Other) sind vorbelegt. Nach dem Speichern Redeploy, dann im Terminal der Anwendung den Container backup wählen und ein erstes Backup anstoßen:

bash
backup-entrypoint backup
backup-entrypoint list

Anschließend den Restore-Test einmal durchspielen. Coolify sichert die PostgreSQL-Datenbank dieser Anwendung nicht selbst; seine eingebauten Datenbank-Backups gelten nur für eigenständige Coolify-Datenbanken.

Betrieb ​

Konsole: Befehle wie bin/console app:… im Terminal der Anwendung im Container worker ausführen (nur die Worker-Rolle sieht alle Organisationen). Migrationsstand im Container api: php bin/console doctrine:migrations:status.

Mehr Worker: in docker-compose.coolify.yml den Dienst worker als worker-2 (usw.) kopieren und deployen. Jeder Container liest unter seinem eigenen Redis-Consumer-Namen. Den scheduler nie vervielfachen.

Datenbank-Einstellungen nachträglich: POLLYOU_PG_TUNING wirkt nur auf ein leeres Datenverzeichnis. Für eine laufende Datenbank im Terminal des Containers database:

bash
psql -v ON_ERROR_STOP=1 -U pollyou -d pollyou -f /opt/pollyou/20-pollyou-tuning.sql

danach den Dienst neu starten. Die Werte sind für 16 GB RAM bemessen; auf kleineren Servern die Speicherwerte vorher anpassen.

Rollback: den fehlerhaften Commit in Git rückgängig machen (git revert) und deployen. Die Datenbank wird dabei nicht zurückgerollt (siehe Rollback).

Mercure hinter Traefik: Kommen Live-Ergebnisse im Dashboard oder in Embeds nicht an, obwohl https://rt.…/healthz antwortet, unter Advanced die Gzip-Kompression von Coolify abschalten. Alle PollYou-Dienste komprimieren ihre Antworten ohnehin selbst.

Monitoring: Uptime-Checks wie in der Deployment-Doku beschrieben; Logs aller Dienste unter Logs (API, Worker und Scheduler schreiben JSON).

Guthaben vergeben / Plan ändern ​

Support-Befehle für Guthaben laufen im Terminal der Anwendung im Container worker – nicht in api: Die Datenbankrolle der API sieht nur die Daten der jeweils angemeldeten Organisation; ein Befehl, der eine Organisation per ID oder Slug sucht, findet dort nichts.

bash
bin/console app:billing:show <organisation-id|slug>                         # Plan, Guthaben, letzte Buchungen
bin/console app:credits:grant <organisation> 5 --note="Kulanz Ticket 123"   # Guthaben schenken
bin/console app:credits:grant <organisation> -5 --reason=refund             # nach Erstattung zurücknehmen
bin/console app:organization:set-plan <organisation> internal               # oder standard

Nach einer Preisänderung (CreditPacks.php, neues Deployment) einmal bin/console app:billing:sync-products im selben Container ausführen.

Lokal prüfen ​

Die Compose-Datei lässt sich ohne Coolify starten. Die Variablen, die Coolify sonst erzeugt, kommen dann aus einer Env-Datei, Ports und ein Mailpit aus einem Override:

bash
docker compose -p pollyou-coolifycheck -f docker-compose.coolify.yml -f override.yml \
  --env-file coolify.env up -d --build --wait

coolify.env enthält alle SERVICE_*-Variablen sowie die Pflichtvariablen aus Schritt 6 mit http://localhost:<port>-Adressen; override.yml veröffentlicht die Ports von api, dashboard, embed und mercure und ergänzt einen Dienst mailpit (MAILER_DSN=smtp://mailpit:1025). Danach läuft ci/smoke-prod.sh mit den lokalen URLs.