# FEDEO Hosting Guide Diese Anleitung beschreibt ein produktionsnahes Self-Hosting von FEDEO mit Docker Compose, Traefik, PostgreSQL und optionalem S3-kompatiblem Objektspeicher via MinIO. ## Architektur Der Stack besteht aus: - `frontend`: Nuxt-Frontend auf Port `3000` - `backend`: Node/Fastify-API auf Port `3100` - `db`: PostgreSQL - `traefik`: Reverse Proxy mit automatischen Let's-Encrypt-Zertifikaten - optional `minio`: S3-kompatibler Objektspeicher fur Dateiuploads Die Konfiguration erfolgt uber Umgebungsvariablen beziehungsweise eine `.env`-Datei im Deploy-Verzeichnis. ## Voraussetzungen Vor dem Deployment sollten folgende Punkte erfullt sein: - Ein Linux-Server oder VPS mit offentlichen Ports `80` und `443` - Docker Engine inkl. Compose Plugin - Eine Domain, die auf den Server zeigt, z. B. `app.example.com` - Optional: SMTP-Zugang fur E-Mails - Optional: S3-Bucket oder MinIO fur Dateispeicher Empfohlen: - mindestens 2 vCPU - mindestens 4 GB RAM - SSD-Speicher fur PostgreSQL und Dateiuploads ## DNS und Netzwerk Lege mindestens einen A- oder AAAA-Record an: - `app.example.com -> ` Traefik terminiert TLS direkt im Compose-Stack. Es ist kein zusatzlicher Reverse Proxy davor erforderlich. ## Benotigte Backend-Umgebungsvariablen Das Backend erwartet mindestens diese Umgebungsvariablen: - `COOKIE_SECRET` - `JWT_SECRET` - `PORT` - `HOST` - `DATABASE_URL` - `S3_BUCKET` - `ENCRYPTION_KEY` - `MAILER_SMTP_HOST` - `MAILER_SMTP_PORT` - `MAILER_SMTP_SSL` - `MAILER_SMTP_USER` - `MAILER_SMTP_PASS` - `MAILER_FROM` - `S3_ENDPOINT` - `S3_REGION` - `S3_ACCESS_KEY` - `S3_SECRET_KEY` - `M2M_API_KEY` - `API_BASE_URL` - `GOCARDLESS_BASE_URL` - `GOCARDLESS_SECRET_ID` - `GOCARDLESS_SECRET_KEY` - `DOKUBOX_IMAP_HOST` - `DOKUBOX_IMAP_PORT` - `DOKUBOX_IMAP_SECURE` - `DOKUBOX_IMAP_USER` - `DOKUBOX_IMAP_PASSWORD` - `OPENAI_API_KEY` - `STIRLING_API_KEY` Minimal wichtige Werte fur den ersten Start: - `HOST=0.0.0.0` - `PORT=3100` - `DATABASE_URL=postgres://fedeo:@db:5432/fedeo` - `API_BASE_URL=https://app.example.com/backend` Wenn du MinIO verwendest, setze zusatzlich: - `S3_ENDPOINT=http://minio:9000` - `S3_REGION=eu-central-1` - `S3_ACCESS_KEY=` - `S3_SECRET_KEY=` - `S3_BUCKET=fedeo` ## Deploy-Struktur Deploye den Stack in einem eigenen Betriebsverzeichnis. Der Selfhost-Installer lädt dafür nur die benötigten Betriebsdateien und klont nicht das komplette Repository. Beispiel für die manuelle Vorbereitung: ```bash mkdir -p /opt/fedeo/scripts curl -fsSL https://git.federspiel.tech/flfeders/FEDEO/raw/branch/dev/docker-compose.selfhost.yml -o /opt/fedeo/docker-compose.yml curl -fsSL https://git.federspiel.tech/flfeders/FEDEO/raw/branch/dev/.env.example -o /opt/fedeo/.env.example curl -fsSL https://git.federspiel.tech/flfeders/FEDEO/raw/branch/dev/scripts/selfhost-setup.sh -o /opt/fedeo/scripts/selfhost-setup.sh chmod +x /opt/fedeo/scripts/selfhost-setup.sh ``` Die Verzeichnisstruktur sollte dann mindestens so aussehen: ```text /opt/fedeo/ docker-compose.yml .env scripts/ traefik/ letsencrypt/ logs/ postgres/ minio/ ``` Danach: ```bash mkdir -p /opt/fedeo/traefik/letsencrypt mkdir -p /opt/fedeo/traefik/logs mkdir -p /opt/fedeo/postgres mkdir -p /opt/fedeo/minio mkdir -p /opt/fedeo/seafile/mysql mkdir -p /opt/fedeo/seafile/data touch /opt/fedeo/traefik/letsencrypt/acme.json chmod 600 /opt/fedeo/traefik/letsencrypt/acme.json ``` Als Startpunkt kannst du die Beispielumgebung kopieren: ```bash cp .env.example .env ``` Ersetze anschließend alle Platzhalter und passe mindestens `DOMAIN`, `CONTACT_EMAIL`, Datenbank-, Secret-, SMTP- und S3-Werte an. Seafile läuft im Standard-Stack unter derselben Domain auf `/files`, zum Beispiel `https://app.example.com/files`. Alternativ kannst du die Konfiguration geführt erzeugen lassen: ```bash bash scripts/selfhost-setup.sh ``` Auf einem frischen Server kannst du die Betriebsdateien und die Konfiguration direkt per One-Liner vorbereiten: ```bash curl -fsSL https://git.federspiel.tech/flfeders/FEDEO/raw/branch/dev/scripts/selfhost-install.sh | bash ``` Der schnelle One-Liner mit direktem Stack-Start: ```bash curl -fsSL https://git.federspiel.tech/flfeders/FEDEO/raw/branch/dev/scripts/selfhost-install.sh | bash -s -- --simple --start ``` Der Installer prüft Basispakete, installiert Docker auf Wunsch über das offizielle Docker-Installationsscript, lädt nur die Selfhost-Dateien nach `/opt/fedeo` und startet anschließend den geführten Setup-Assistenten. Für den schnellen Standardpfad: ```bash bash scripts/selfhost-setup.sh --simple ``` Für mehr Rückfragen zu SMTP, API-Schlüsseln und optionalen Diensten: ```bash bash scripts/selfhost-setup.sh --advanced ``` Der Assistent erklärt zuerst die Selfhost-Verzeichnisstruktur, schreibt anschließend `.env`, legt persistente Verzeichnisse inklusive `traefik/letsencrypt/acme.json` an und kann den Stack optional direkt starten. ## Beispiel `.env` Diese Datei liegt neben der `docker-compose.yml`: ```env DOMAIN=app.example.com CONTACT_EMAIL=admin@deine-domain.de DB_NAME=fedeo DB_USER=fedeo DB_PASSWORD=change-this-db-password DATABASE_URL=postgres://fedeo:change-this-db-password@db:5432/fedeo MINIO_ROOT_USER=fedeo-minio MINIO_ROOT_PASSWORD=change-this-minio-password MINIO_BUCKET=fedeo HOST=0.0.0.0 PORT=3100 COOKIE_SECRET=change-this-cookie-secret JWT_SECRET=change-this-jwt-secret ENCRYPTION_KEY=change-this-encryption-key MAILER_SMTP_HOST=smtp.example.com MAILER_SMTP_PORT=587 MAILER_SMTP_SSL=false MAILER_SMTP_USER=mailer@example.com MAILER_SMTP_PASS=change-this-mail-password MAILER_FROM=FEDEO S3_ENDPOINT=http://minio:9000 S3_REGION=eu-central-1 S3_ACCESS_KEY=fedeo-minio S3_SECRET_KEY=change-this-minio-password S3_BUCKET=fedeo FEDEO_FILE_BACKEND=s3 SEAFILE_SERVER_HOSTNAME=app.example.com SEAFILE_SERVER_PROTOCOL=https SEAFILE_SITE_ROOT=/files/ SEAFILE_BASE_URL=https://app.example.com/files SEAFILE_INTERNAL_URL=http://seafile:80 SEAFILE_IMAGE=seafileltd/seafile-mc:13.0-latest SEAFILE_DB_IMAGE=mariadb:10.11 SEAFILE_REDIS_IMAGE=redis:7-alpine INIT_SEAFILE_MYSQL_ROOT_PASSWORD=change-this-seafile-root-password SEAFILE_MYSQL_DB_USER=seafile SEAFILE_MYSQL_DB_PASSWORD=change-this-seafile-db-password SEAFILE_REDIS_PASSWORD=change-this-seafile-redis-password SEAFILE_JWT_PRIVATE_KEY=change-this-seafile-jwt-private-key-at-least-32-chars INIT_SEAFILE_ADMIN_EMAIL=admin@example.com INIT_SEAFILE_ADMIN_PASSWORD=change-this-seafile-admin-password SEAFILE_ENABLE_GO_FILESERVER=true SEAFILE_ENABLE_SEADOC=false SEAFILE_ENABLE_NOTIFICATION_SERVER=false SEAFILE_ENABLE_AI=false SEAFILE_ENABLE_FACE_RECOGNITION=false SEAFILE_MD_FILE_COUNT_LIMIT=100000 M2M_API_KEY=change-this-m2m-key API_BASE_URL=https://app.example.com/backend GOCARDLESS_BASE_URL=https://api.gocardless.com GOCARDLESS_SECRET_ID=replace-this GOCARDLESS_SECRET_KEY=replace-this DOKUBOX_IMAP_HOST=imap.example.com DOKUBOX_IMAP_PORT=993 DOKUBOX_IMAP_SECURE=true DOKUBOX_IMAP_USER=dokubox@example.com DOKUBOX_IMAP_PASSWORD=change-this-imap-password OPENAI_API_KEY=replace-this STIRLING_API_KEY=replace-this NUXT_PUBLIC_PDF_LICENSE=replace-with-your-pdf-license FEDEO_BOOTSTRAP_ADMIN_EMAIL=admin@example.com FEDEO_BOOTSTRAP_ADMIN_PASSWORD=change-this-admin-password FEDEO_BOOTSTRAP_ADMIN_FIRST_NAME=Admin FEDEO_BOOTSTRAP_ADMIN_LAST_NAME=Benutzer FEDEO_BOOTSTRAP_TENANT_NAME=Mein Unternehmen FEDEO_BOOTSTRAP_TENANT_SHORT=MEIN MATRIX_SERVER_NAME=app.example.com MATRIX_POSTGRES_DB=synapse MATRIX_POSTGRES_USER=synapse MATRIX_POSTGRES_PASSWORD=change-this-matrix-db-password MATRIX_TURN_SHARED_SECRET=change-this-turn-secret MATRIX_HOMESERVER_URL=http://matrix-synapse:8008 MATRIX_RTC_HOST=app.example.com MATRIX_RTC_JWT_URL=https://app.example.com/livekit/jwt MATRIX_LIVEKIT_URL=wss://app.example.com/livekit/sfu MATRIX_REGISTRATION_SHARED_SECRET=change-this-matrix-registration-secret MATRIX_SERVICE_USER_LOCALPART=fedeo_service LIVEKIT_KEY=fedeo-livekit LIVEKIT_SECRET=change-this-livekit-secret-please-replace NUXT_PUBLIC_MATRIX_ELEMENT_URL=https://app.example.com/element ``` Die `FEDEO_BOOTSTRAP_*`-Werte sind für den ersten Start gedacht. Wenn `FEDEO_BOOTSTRAP_ADMIN_EMAIL` und `FEDEO_BOOTSTRAP_ADMIN_PASSWORD` gesetzt sind, legt das Backend idempotent einen Admin-Benutzer, einen ersten Mandanten, eine Administrator-Rolle und grundlegende Stammdaten an. Nach erfolgreichem Erstzugriff solltest du das Bootstrap-Passwort aus der `.env` entfernen oder ändern. ## Docker Compose mit optionalem S3, Seafile und Matrix Die Selfhost-Konfiguration wird im Betriebsverzeichnis als `docker-compose.yml` abgelegt. Sie startet MinIO standardmäßig mit. Wenn du stattdessen AWS S3, Hetzner Object Storage, Backblaze B2 S3 oder einen anderen externen S3-Dienst nutzen willst, kannst du die Services `minio` und `createbuckets` entfernen und nur die entsprechenden S3-Umgebungsvariablen auf den externen Anbieter zeigen lassen. Der Stack enthält zusätzlich Seafile CE als vorbereitetes File-Backend. Seafile läuft unter `https://DOMAIN/files`, nutzt MariaDB und Redis intern und ist damit ohne zusätzliche Subdomain erreichbar. FEDEO nutzt aktuell weiter S3 als Standard; `FEDEO_FILE_BACKEND=s3` bleibt deshalb gesetzt, bis das Backend die Seafile-Integration vollständig unterstützt. Der Matrix-Stack ist im Selfhost-Compose direkt enthalten. Er umfasst Synapse, eine eigene PostgreSQL-Datenbank für Synapse, Redis, `.well-known/matrix`, coturn, LiveKit, den LiveKit-JWT-Service und Element Web. Das einfache Selfhost-Setup nutzt nur `DOMAIN`: Synapse läuft unter `https://DOMAIN/_matrix`, Matrix-Well-Known unter `https://DOMAIN/.well-known/matrix`, LiveKit unter `https://DOMAIN/livekit/sfu`, der JWT-Service unter `https://DOMAIN/livekit/jwt` und Element Web unter `https://DOMAIN/element`. Das Backend führt beim Containerstart standardmäßig `npm run migrate` aus. Setze `FEDEO_RUN_MIGRATIONS=false`, wenn du Migrationen bewusst manuell ausführen möchtest. ```yaml services: traefik: image: traefik:v2.11 container_name: fedeo-traefik restart: unless-stopped command: - --api.insecure=false - --api.dashboard=false - --providers.docker=true - --providers.docker.exposedbydefault=false - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --certificatesresolvers.letsencrypt.acme.tlschallenge=true - --certificatesresolvers.letsencrypt.acme.email=${CONTACT_EMAIL} - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json - --accesslog=true - --accesslog.filepath=/logs/access.log ports: - "80:80" - "443:443" volumes: - ./traefik/letsencrypt:/letsencrypt - ./traefik/logs:/logs - /var/run/docker.sock:/var/run/docker.sock:ro networks: - web db: image: postgres:16 container_name: fedeo-db restart: unless-stopped environment: POSTGRES_DB: ${DB_NAME} POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - ./postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"] interval: 10s timeout: 5s retries: 10 networks: - internal minio: image: minio/minio:latest container_name: fedeo-minio restart: unless-stopped command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} volumes: - ./minio:/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 10s timeout: 5s retries: 10 networks: - internal createbuckets: image: minio/mc:latest container_name: fedeo-minio-init depends_on: minio: condition: service_healthy entrypoint: > /bin/sh -c " mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD}; mc mb --ignore-existing local/${MINIO_BUCKET}; mc anonymous set private local/${MINIO_BUCKET}; exit 0; " restart: "no" networks: - internal backend: image: git.federspiel.tech/flfeders/fedeo/backend:dev container_name: fedeo-backend restart: unless-stopped depends_on: db: condition: service_healthy minio: condition: service_healthy createbuckets: condition: service_completed_successfully environment: NODE_ENV: production HOST: ${HOST} PORT: ${PORT} COOKIE_SECRET: ${COOKIE_SECRET} JWT_SECRET: ${JWT_SECRET} ENCRYPTION_KEY: ${ENCRYPTION_KEY} DATABASE_URL: ${DATABASE_URL} MAILER_SMTP_HOST: ${MAILER_SMTP_HOST} MAILER_SMTP_PORT: ${MAILER_SMTP_PORT} MAILER_SMTP_SSL: ${MAILER_SMTP_SSL} MAILER_SMTP_USER: ${MAILER_SMTP_USER} MAILER_SMTP_PASS: ${MAILER_SMTP_PASS} MAILER_FROM: ${MAILER_FROM} S3_ENDPOINT: ${S3_ENDPOINT} S3_REGION: ${S3_REGION} S3_ACCESS_KEY: ${S3_ACCESS_KEY} S3_SECRET_KEY: ${S3_SECRET_KEY} S3_BUCKET: ${S3_BUCKET} M2M_API_KEY: ${M2M_API_KEY} API_BASE_URL: ${API_BASE_URL} GOCARDLESS_BASE_URL: ${GOCARDLESS_BASE_URL} GOCARDLESS_SECRET_ID: ${GOCARDLESS_SECRET_ID} GOCARDLESS_SECRET_KEY: ${GOCARDLESS_SECRET_KEY} DOKUBOX_IMAP_HOST: ${DOKUBOX_IMAP_HOST} DOKUBOX_IMAP_PORT: ${DOKUBOX_IMAP_PORT} DOKUBOX_IMAP_SECURE: ${DOKUBOX_IMAP_SECURE} DOKUBOX_IMAP_USER: ${DOKUBOX_IMAP_USER} DOKUBOX_IMAP_PASSWORD: ${DOKUBOX_IMAP_PASSWORD} OPENAI_API_KEY: ${OPENAI_API_KEY} STIRLING_API_KEY: ${STIRLING_API_KEY} labels: - traefik.enable=true - traefik.http.routers.fedeo-backend.rule=Host(`${DOMAIN}`) && PathPrefix(`/backend`) - traefik.http.routers.fedeo-backend.entrypoints=websecure - traefik.http.routers.fedeo-backend.tls.certresolver=letsencrypt - traefik.http.middlewares.fedeo-backend-strip.stripprefix.prefixes=/backend - traefik.http.routers.fedeo-backend.middlewares=fedeo-backend-strip - traefik.http.services.fedeo-backend.loadbalancer.server.port=3100 - traefik.docker.network=fedeo_web networks: - web - internal frontend: image: git.federspiel.tech/flfeders/fedeo/frontend:dev container_name: fedeo-frontend restart: unless-stopped depends_on: - backend environment: NODE_ENV: production NUXT_PUBLIC_API_BASE: https://${DOMAIN}/backend NUXT_PUBLIC_PDF_LICENSE: ${NUXT_PUBLIC_PDF_LICENSE} labels: - traefik.enable=true - traefik.http.routers.fedeo-frontend.rule=Host(`${DOMAIN}`) - traefik.http.routers.fedeo-frontend.entrypoints=websecure - traefik.http.routers.fedeo-frontend.tls.certresolver=letsencrypt - traefik.http.services.fedeo-frontend.loadbalancer.server.port=3000 - traefik.docker.network=fedeo_web networks: - web networks: web: name: fedeo_web driver: bridge internal: name: fedeo_internal driver: bridge ``` ## Externe S3-Provider statt MinIO Wenn du keinen lokalen MinIO-Container betreiben willst: 1. Entferne die Services `minio` und `createbuckets` aus der Compose-Datei. 2. Entferne im Backend `depends_on` fur `minio` und `createbuckets`. 3. Trage in `.env` die Zugangsdaten des externen S3-Dienstes ein. Beispiel fur die relevanten Werte: ```env S3_ENDPOINT=https://s3.eu-central-1.amazonaws.com S3_REGION=eu-central-1 S3_ACCESS_KEY=... S3_SECRET_KEY=... S3_BUCKET=fedeo ``` Hinweis: Das Backend nutzt `forcePathStyle: true`. Das funktioniert sauber mit MinIO und vielen S3-kompatiblen Providern. Bei reinem AWS S3 kann je nach Endpoint-Setup ein abweichendes Verhalten sinnvoll sein. Falls du AWS S3 einsetzen willst, sollte die S3-Initialisierung im Backend gegen den konkreten Zielprovider getestet werden. ## Start des Stacks Im Deploy-Verzeichnis: ```bash docker compose --env-file /opt/fedeo/.env -f /opt/fedeo/docker-compose.yml up -d ``` Synapse erzeugt `matrix/synapse/homeserver.yaml` beim ersten Start automatisch und aktualisiert die für FEDEO relevanten Werte aus der `.env`. `MATRIX_REGISTRATION_SHARED_SECRET` muss in der `.env` gesetzt und geheim bleiben, weil FEDEO damit Matrix-Nutzer provisioniert. Danach Status prufen: ```bash docker compose --env-file /opt/fedeo/.env -f /opt/fedeo/docker-compose.yml ps docker compose --env-file /opt/fedeo/.env -f /opt/fedeo/docker-compose.yml logs -f traefik docker compose --env-file /opt/fedeo/.env -f /opt/fedeo/docker-compose.yml logs -f backend ``` Wenn du Migrationen manuell ausführen möchtest: ```bash docker compose --env-file /opt/fedeo/.env -f /opt/fedeo/docker-compose.yml run --rm backend npm run migrate ``` ## Funktionsprufung Nach dem ersten Start sollten mindestens diese Checks erfolgreich sein: ```bash curl -I https://app.example.com curl https://app.example.com/backend/health ``` Erwartung: - Frontend liefert `200` oder `302` - Backend liefert JSON wie `{"status":"ok"}` Wenn der Bootstrap aktiviert ist, kannst du dich danach mit `FEDEO_BOOTSTRAP_ADMIN_EMAIL` und `FEDEO_BOOTSTRAP_ADMIN_PASSWORD` anmelden. Die Mandantensperre wird über `locked` gesteuert; `hasActiveLicense` wird nicht mehr für den Selfhost-Zugriff ausgewertet. ## Updates Bei neuen Versionen: ```bash curl -fsSL https://git.federspiel.tech/flfeders/FEDEO/raw/branch/dev/docker-compose.selfhost.yml -o /opt/fedeo/docker-compose.yml curl -fsSL https://git.federspiel.tech/flfeders/FEDEO/raw/branch/dev/.env.example -o /opt/fedeo/.env.example curl -fsSL https://git.federspiel.tech/flfeders/FEDEO/raw/branch/dev/scripts/selfhost-setup.sh -o /opt/fedeo/scripts/selfhost-setup.sh chmod +x /opt/fedeo/scripts/selfhost-setup.sh docker compose --env-file /opt/fedeo/.env -f /opt/fedeo/docker-compose.yml up -d ``` Der Backend-Container wendet Datenbankmigrationen beim Start automatisch an. Bei kritischen Updates sollte vorher ein Backup von `./postgres` und `./minio` erstellt werden. Die Selfhost-Compose-Datei nutzt vorgebaute Images. Dadurch braucht der Server keinen Repository-Checkout und keine lokalen Build-Kontexte. ## Backup-Empfehlung Regelmassig sichern: - `./postgres` - `./minio` falls MinIO lokal genutzt wird - `./matrix/postgres` falls Matrix lokal betrieben wird - `./matrix/synapse` falls Matrix lokal betrieben wird - `./traefik/letsencrypt/acme.json` - deine `.env` - deine dokumentierten Secret-Werte aus der `.env` oder deinem Secret-Management ## Bekannte Betriebsbesonderheiten - Das Backend startet nur sauber, wenn alle Pflichtvariablen gesetzt sind. - Ohne korrekt gesetzte S3-Secrets funktionieren Dateiuploads und dateibasierte Funktionen nicht. - Fur die Frontend-PDF-Funktion wird eine gueltige `NUXT_PUBLIC_PDF_LICENSE` benotigt. - PostgreSQL ist im Projekt vorgesehen; andere SQL-Datenbanken sind in dieser Compose-Datei nicht berucksichtigt. ## Optional: Nur mit bestehender externer Infrastruktur Wenn bereits vorhanden: - externer Reverse Proxy - externer PostgreSQL-Server - externer S3-Speicher - externe Zertifikatsverwaltung dann konnen `traefik`, `db` und `minio` aus dem Stack entfernt werden. In diesem Fall mussen die zugehorigen Hostnamen und Zugangsdaten in der `.env` beziehungsweise im Frontend-Environment auf die externe Infrastruktur zeigen.