Erweitert die Ausgangsbeleg-Tools um explizite Dokument-Steuertypen. Der MCP listet die unterstützten Steuertypen, validiert taxType als Dokumentfeld und setzt bei 13b UStG, 19 UStG und 12.3 UStG die Positions-USt analog zur Oberfläche auf 0 Prozent.
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 Port3000backend: Node/Fastify-API auf Port3100db: PostgreSQLtraefik: 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
80und443 - 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_SECRETJWT_SECRETPORTHOSTDATABASE_URLS3_BUCKETENCRYPTION_KEYMAILER_SMTP_HOSTMAILER_SMTP_PORTMAILER_SMTP_SSLMAILER_SMTP_USERMAILER_SMTP_PASSMAILER_FROMS3_ENDPOINTS3_REGIONS3_ACCESS_KEYS3_SECRET_KEYM2M_API_KEYAPI_BASE_URLGOCARDLESS_BASE_URLGOCARDLESS_SECRET_IDGOCARDLESS_SECRET_KEYDOKUBOX_IMAP_HOSTDOKUBOX_IMAP_PORTDOKUBOX_IMAP_SECUREDOKUBOX_IMAP_USERDOKUBOX_IMAP_PASSWORDOPENAI_API_KEYSTIRLING_API_KEY
Minimal wichtige Werte fur den ersten Start:
HOST=0.0.0.0PORT=3100DATABASE_URL=postgres://fedeo:<starkes-passwort>@db:5432/fedeoAPI_BASE_URL=https://app.example.com/backend
Wenn du MinIO verwendest, setze zusatzlich:
S3_ENDPOINT=http://minio:9000S3_REGION=eu-central-1S3_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:
- Entferne die Services
minioundcreatebucketsaus der Compose-Datei. - Entferne im Backend
depends_onfurminioundcreatebuckets. - Trage in
.envdie 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
200oder302 - 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./miniofalls MinIO lokal genutzt wird./traefik/letsencrypt/acme.json- deine
.env - deine dokumentierten Secret-Werte aus der
.envoder 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_LICENSEbenotigt. - 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.