2026-01-15 12:30:33 +01:00
2026-03-16 20:46:26 +01:00
2026-04-08 18:52:04 +02:00
2026-04-08 18:52:04 +02:00
2026-03-16 20:46:26 +01:00
2026-02-19 18:29:06 +01:00
2026-03-18 18:26:39 +01:00

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 -> <SERVER-IP>

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:<starkes-passwort>@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=<MINIO_ROOT_USER>
  • S3_SECRET_KEY=<MINIO_ROOT_PASSWORD>
  • S3_BUCKET=fedeo

Deploy-Struktur

Deploye den Stack direkt aus einem geklonten Checkout dieses Repositories, weil die Compose-Datei die lokalen Build-Kontexte ./frontend und ./backend verwendet.

Beispiel:

git clone <DEIN-REPO-URL> /opt/fedeo
cd /opt/fedeo

Die Verzeichnisstruktur sollte dann mindestens so aussehen:

/opt/fedeo/
  docker-compose.yml
  .env
  backend/
  frontend/
  traefik/
    letsencrypt/
    logs/
  postgres/
  minio/

Danach:

mkdir -p /opt/fedeo/traefik/letsencrypt
mkdir -p /opt/fedeo/traefik/logs
mkdir -p /opt/fedeo/postgres
mkdir -p /opt/fedeo/minio
touch /opt/fedeo/traefik/letsencrypt/acme.json
chmod 600 /opt/fedeo/traefik/letsencrypt/acme.json

Beispiel .env

Diese Datei liegt neben der docker-compose.yml:

DOMAIN=app.example.com
CONTACT_EMAIL=admin@example.com

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 <no-reply@example.com>

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

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

Vollstandiges Docker Compose mit optionaler S3-MinIO-Option

Hinweis: Der Stack unten startet MinIO standardmassig 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.

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:
    build:
      context: ./backend
    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
    networks:
      - web
      - internal

  frontend:
    build:
      context: ./frontend
    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
    networks:
      - web

networks:
  web:
    driver: bridge
  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:

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:

docker compose build
docker compose up -d

Danach Status prufen:

docker compose ps
docker compose logs -f traefik
docker compose logs -f backend

Funktionsprufung

Nach dem ersten Start sollten mindestens diese Checks erfolgreich sein:

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"}

Updates

Bei neuen Versionen:

git pull
docker compose build
docker compose up -d

Falls du statt lokaler Builds vorgebaute Images verwenden willst, kannst du in der Compose-Datei build: durch passende image:-Eintrage ersetzen. Erst dann ist ein vorgelagertes docker compose pull sinnvoll.

Backup-Empfehlung

Regelmassig sichern:

  • ./postgres
  • ./minio falls MinIO lokal genutzt 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.

Description
No description provided
https://fedeo.de
Readme 16 MiB
Languages
Vue 50.9%
TypeScript 40.1%
JavaScript 8.9%