Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 29.10.2026 · 11 min Lesezeit

Dossier mit Docker Compose: Bewerbungs-App mit lokaler KI selbst hosten

Dossier schreibt aus einem Profil zugeschnittene Lebensläufe und Anschreiben und verwaltet alle Bewerbungen. Die Anleitung zeigt die Installation mit Docker Compose und PostgreSQL 18, die Anbindung eines lokalen Modells über Ollama, Export, pg_dump und Wiederherstellung sowie das Update auf Version 1.9.2.

Geprüft am 29.10.2026 · für Dossier 1.9.2

Mit KI erstellt – redaktionelle Prüfung ausstehend

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Titelbild: Dossier selbst hosten mit Docker Compose, Lebenslauf, Backup und lokaler KI

Dossier ist eine selbst gehostete Bewerbungs-App: Sie pflegen ein einziges Profil, fügen eine Stellenanzeige ein, und die App gleicht die Anforderungen mit Ihrem Profil ab, schreibt einen zugeschnittenen Lebenslauf samt Anschreiben und druckt beides als PDF. Das Projekt AienTech/dossier steht unter AGPL-3.0, wurde am 03.10.2026 angelegt und hatte am 29.10.2026 bereits 2.759 Sterne auf GitHub; gittrend.io zählte in seiner Self-Hosted-Rangliste über 1.100 neue Sterne an einem Tag. Diese Anleitung führt durch Installation, lokale KI, Sicherung mit Wiederherstellung und Update.

Voraussetzungen

Dossier ist für eine Person gedacht. Das Image gibt es nur für linux/amd64, ARM-Server wie ein Raspberry Pi scheiden für den Docker-Weg aus. Sie brauchen:

  • einen Linux-Server mit x86-64-CPU, etwa Debian 12 oder Ubuntu 24.04, mit Docker Engine und dem Docker Compose Plugin (Einrichtung: Docker Compose unter Linux als Selfhosting-Grundlage)
  • ohne lokale KI mindestens 2 CPU-Kerne und 2 GB RAM; die App ist auf 1 GB begrenzt, im Test belegte sie rund 230 MB, PostgreSQL knapp 30 MB
  • mit lokalem Modell zusätzlich Arbeitsspeicher: Ollama belegte mit qwen2.5:3b im Test knapp 2,5 GB, das Projekt nennt für qwen2.5:7b etwa 6 GB; 4 oder mehr CPU-Kerne helfen spürbar, eine GPU noch mehr
  • rund 4 GB freien Speicher für die Images (das App-Image ist etwa 1,7 GB groß) plus 2 bis 5 GB je Sprachmodell
  • eine Domain mit Reverse Proxy und HTTPS
EckdatenWert
Imageghcr.io/aientech/dossier:v1.9.2, Datenbank postgres:18-alpine, optional ollama/ollama:0.40.6
Port127.0.0.1:3000 (nur lokal, für den Reverse Proxy)
Volumespgdata (Datenbank), data unter /data (nächtliche Sicherungen, PDF-Archive), ollama (Modelle)
Pflicht-VariablenPOSTGRES_PASSWORD, DOSSIER_SECRET, DOSSIER_PASSWORD (Hash)
Getestete VersionDossier 1.9.2 vom 27.10.2026 (Update von 1.9.1 geprüft)

Schritt 1: Projektordner und Geheimnisse anlegen

Legen Sie einen eigenen Ordner an und erzeugen Sie das Datenbankpasswort sowie das Sitzungsgeheimnis. DOSSIER_SECRET signiert die Sitzungen und verschlüsselt gespeicherte API-Schlüssel; es muss mindestens 32 Zeichen lang sein.

mkdir -p ~/dossier && cd ~/dossier
umask 077
echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)" > .env
echo "DOSSIER_SECRET=$(openssl rand -base64 32)" >> .env

Das Anmeldepasswort speichert Dossier nie im Klartext. Der Befehl hash-password im Image fragt es zweimal verdeckt ab und gibt eine Zeile aus, die mit argon2id: beginnt:

docker run --rm -it ghcr.io/aientech/dossier:v1.9.2 hash-password
Set DOSSIER_PASSWORD to this, then restart Dossier:
argon2id:m=19456,t=2,p=1:...

Tragen Sie diese Zeile als DOSSIER_PASSWORD in die .env ein und ergänzen Sie die übrigen Werte. Mit DOSSIER_IMAGE schreiben Sie die Version fest, statt dem Standard latest zu folgen. DOSSIER_TELEMETRY=off beantwortet die Frage nach dem täglichen Nutzungs-Ping vorab mit Nein; ohne die Variable fragt Dossier nach der ersten Anmeldung und sendet vorher nichts.

POSTGRES_PASSWORD=HIER_AUSGABE_VON_openssl_rand_-hex_24
DOSSIER_SECRET=HIER_AUSGABE_VON_openssl_rand_-base64_32
DOSSIER_PASSWORD=argon2id:m=19456,t=2,p=1:...
DOSSIER_IMAGE=ghcr.io/aientech/dossier:v1.9.2
TZ=Europe/Berlin
DOSSIER_TELEMETRY=off

Verifizieren: grep -c "^DOSSIER_PASSWORD=argon2id:" .env liefert 1, und ls -l .env zeigt die Rechte -rw-------.

Schritt 2: compose.yaml anlegen und starten

Die Datei folgt der offiziellen compose.production.yml. Wir haben die Variablen für Jobbörsen, Gmail und Kalender weggelassen, die Sie für den Start nicht brauchen, und den Standard-Tag auf v1.9.2 gesetzt. Wichtig ist das Volume /var/lib/postgresql: Ab PostgreSQL 18 liegen die Daten in einem versionierten Unterordner, ein Mount auf /var/lib/postgresql/data wäre veraltet.

services:
  db:
    image: postgres:18-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: dossier
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
      POSTGRES_DB: dossier
    volumes:
      # Ab Postgres 18 liegt PGDATA in einem versionierten Unterordner
      - pgdata:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dossier -d dossier"]
      interval: 5s
      timeout: 5s
      retries: 10

  app:
    image: ${DOSSIER_IMAGE:-ghcr.io/aientech/dossier:v1.9.2}
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql://dossier:${POSTGRES_PASSWORD}@db:5432/dossier
      DOSSIER_PASSWORD: ${DOSSIER_PASSWORD:?Set DOSSIER_PASSWORD in .env to a hash from hash-password}
      DOSSIER_SECRET: ${DOSSIER_SECRET:?Set DOSSIER_SECRET in .env}
      DOSSIER_BACKUP_RETENTION_DAYS: ${DOSSIER_BACKUP_RETENTION_DAYS:-}
      TZ: ${TZ:-}
      DOSSIER_AI_PROVIDER: ${DOSSIER_AI_PROVIDER:-}
      DOSSIER_AI_URL: ${DOSSIER_AI_URL:-}
      DOSSIER_AI_MODEL: ${DOSSIER_AI_MODEL:-}
      DOSSIER_AI_TIMEOUT_SECONDS: ${DOSSIER_AI_TIMEOUT_SECONDS:-}
      DOSSIER_TELEMETRY: ${DOSSIER_TELEMETRY:-}
      DOSSIER_PUBLIC_URL: ${DOSSIER_PUBLIC_URL:-}
    ports:
      - "127.0.0.1:${DOSSIER_PORT:-3000}:3000"
    volumes:
      - data:/data
    # Platz für Chromium, das die PDFs druckt
    mem_limit: 1g
    extra_hosts:
      - "host.docker.internal:host-gateway"

  ollama:
    profiles: [ai]
    image: ollama/ollama:0.40.6
    restart: unless-stopped
    volumes:
      - ollama:/root/.ollama

volumes:
  pgdata:
  data:
  ollama:
docker compose up -d
docker compose logs app | tail -8
app-1  | All migrations have been successfully applied.
app-1  | Seeded the built-in themes.
app-1  | ▲ Next.js 16.3.6
app-1  | - Local:         http://localhost:3000
app-1  | ✓ Ready in 0ms

Das Image bringt einen eigenen Healthcheck auf /api/health mit, die App läuft als Benutzer node. Weitere Härtung beschreibt die Anleitung Docker Compose absichern: Secrets, Healthchecks, non-root.

Der Port ist bewusst nur auf 127.0.0.1 veröffentlicht. Laut Projekt verlässt sich Dossier auf die Header X-Forwarded-Proto und X-Forwarded-For des Reverse Proxys, Port 3000 darf daher nie direkt aus dem Internet erreichbar sein. Einen Proxy mit automatischem Zertifikat zeigt der Vergleich Caddy, nginx und Traefik; die Server-Firewall richten Sie mit ufw und fail2ban ein.

Verifizieren: docker compose ps zeigt app und db als healthy, und curl -s localhost:3000/api/health antwortet mit {"status":"ok","version":"1.9.2","db":"ok"}.

Schritt 3: Anmelden und Profil anlegen

Öffnen Sie Dossier über Ihre Domain. Ohne Sitzung leitet jede Seite auf /login um, dort genügt das Passwort, dessen Hash in der .env steht. Ein falsches Passwort quittiert die App mit „That's not the password set on the server. Try again.“; nach fünf Fehlversuchen in einer Minute pausiert die Anmeldung. Die Oberfläche ist englisch.

Unter Profile tragen Sie Basisdaten, Kenntnisse, Berufsstationen und Ausbildung ein. Alles speichert sofort; unser fiktives Testprofil für Erika Musterfrau kam auf 85 Prozent.

Dossier-Profilseite mit den Basisdaten der fiktiven Erika Musterfrau und der Anzeige Profile strength
Das Profil ist die einzige Quelle für Lebensläufe und Anschreiben.

Schon ohne KI druckt Dossier unter Base resume einen Basis-Lebenslauf per eingebautem Chromium.

Verifizieren: Ein Klick auf den PDF-Download des Basis-Lebenslaufs liefert eine Datei. Im Test dauerte das erste PDF 2,3 Sekunden, weil Chromium erst starten musste, jedes weitere rund 0,2 Sekunden; pdfinfo nennt als Creator „Chromium“.

Schritt 4: Lokales KI-Modell anbinden und erste Bewerbung erstellen

Für das Lesen der Anzeige und das Schreiben der Dokumente braucht Dossier ein Sprachmodell. Mit einem lokalen Modell bleiben Ihre Daten auf dem Server, gehostete Anbieter (OpenAI-kompatibel, Anthropic, Mistral) erhalten sie. Aktivieren Sie dazu das Compose-Profil und laden Sie ein Modell:

cat >> .env <<'EOF'
COMPOSE_PROFILES=ai
DOSSIER_AI_PROVIDER=local
DOSSIER_AI_URL=http://ollama:11434
DOSSIER_AI_MODEL=qwen2.5:3b
EOF
docker compose up -d
docker compose exec ollama ollama pull qwen2.5:3b

Wir nutzen qwen2.5:3b (1,9 GB), für bessere Texte empfiehlt das Projekt qwen2.5:7b; welches Modell zu welcher Hardware passt, vergleicht unsere Übersicht Ollama-Modelle nach VRAM und Quantisierung. Innerhalb von Docker ist localhost der App-Container selbst, die Adresse lautet deshalb http://ollama:11434.

Dossier-Einstellungen AI provider mit lokalem Modell qwen2.5:3b und der Meldung Connected
Settings, AI provider: Test connection bestätigt das lokale Modell.

Unter New application fügen Sie den Text einer Stellenanzeige ein und klicken auf „Read the posting“. Dossier zieht Firma, Rolle, Ort und Anforderungen heraus und zeigt unter „How you match“, welche Anforderungen Ihr Profil abdeckt. Fehlendes markiert die Funktion „Never invent experience“ als Lücke, statt es zu erfinden.

Dossier-Abgleich einer fiktiven Stellenanzeige mit dem Profil: zwei von drei Anforderungen abgedeckt
Abgleich mit dem Profil: abgedeckt, teilweise abgedeckt, nicht im Profil.

Mit „Create application“ entstehen Lebenslauf und Anschreiben, die Sie im Chat verfeinern und als PDF laden. Auf unserem Testhost ohne GPU brauchte qwen2.5:3b für das Auslesen der Anzeige einige Minuten und für beide Dokumente rund 80 Sekunden. Das Anschreiben schrieb das kleine Modell auf Englisch, obwohl Anzeige und Lebenslauf-Überschriften deutsch waren; prüfen Sie die Dokumentsprache vor dem Versand.

Fertige Bewerbung in Dossier mit zugeschnittenem Lebenslauf und dem Bereich Refine with AI
Die fertige Bewerbung: Lebenslauf, Anschreiben, E-Mail und Status an einem Ort.

Verifizieren: „Test connection“ meldet „Connected · qwen2.5:3b answered in 2.8 s“ (Zeit je nach Hardware), und die neue Bewerbung erscheint unter Applications im Status Draft. Die PDF-Downloads für Lebenslauf und Anschreiben liefern HTTP 200.

Schritt 5: Backup und Wiederherstellung

Dossier bietet drei Wege, die sich ergänzen. Settings, Your data, Export lädt alles als eine JSON-Datei herunter (in unserem Test 16 KB, Format dossier-export, Schema 5); API-Schlüssel fehlen darin, solange Sie sie nicht ausdrücklich einschließen. Die nächtliche Sicherung schreibt denselben Export um 03:00 Uhr in /data/backups und hält ihn standardmäßig 14 Tage vor. Der Ordner entsteht erst mit dem ersten Lauf. Dazu kommt der Dump der Datenbank:

Dossier-Einstellungen Your data mit Export, Import und nächtlichen Sicherungen
Your data: Export, Import und nächtliche Sicherungen nach /data/backups.
docker compose exec -T db pg_dump -U dossier -Fc dossier > dossier.dump
docker compose cp app:/data/backups ./backups
cp .env env.backup

Die .env gehört zwingend dazu: Ohne das ursprüngliche DOSSIER_SECRET kann Dossier gespeicherte API-Schlüssel nicht mehr lesen, ohne den Hash kommen Sie nicht hinein. Nach docker compose down -v im Test waren alle Volumes weg und die neue Instanz leer. Weg eins ist der Import der JSON-Datei über Settings, Your data, Import; Dossier legt dabei vorher selbst eine Sicherung des aktuellen Stands an (dossier-backup-before-import-….json). Weg zwei ist der Dump bei gestoppter App:

docker compose up -d db
docker compose stop app
docker compose exec -T db pg_restore --clean --if-exists -U dossier -d dossier < dossier.dump
docker compose up -d app

In beiden Fällen war unsere Testbewerbung bei der Musterstadt Logistik AG wieder da, die Anmeldung funktionierte mit dem bisherigen Passwort, und der PDF-Download lieferte dieselbe Datei. Beachten Sie: down -v löscht auch das Volume ollama. Das Modell müssen Sie danach neu laden, sonst meldet Dossier „The server doesn't have the model “qwen2.5:3b”“.

Verifizieren: docker compose exec -T db psql -U dossier -d dossier -tAc 'select company,status from "Application";' zeigt nach der Wiederherstellung Ihre Bewerbungen, und pg_restore endet mit Exit-Code 0.

Schritt 6: Auf eine neue Version aktualisieren

Dossier zeigt neue Versionen selbst in der Seitenleiste an, im Test etwa „Dossier 1.9.2 is out. You have 1.9.1.“ Sichern Sie vor dem Update, ändern Sie dann den Tag in der .env und ziehen Sie das Image:

docker compose exec -T db pg_dump -U dossier -Fc dossier > vor-update.dump
sed -i 's|^DOSSIER_IMAGE=.*|DOSSIER_IMAGE=ghcr.io/aientech/dossier:v1.9.2|' .env
docker compose pull app
docker compose up -d
curl -s localhost:3000/api/health
app-1  | 31 migrations found in prisma/migrations
app-1  | No pending migrations to apply.
{"status":"ok","version":"1.9.2","db":"ok"}

Beim Sprung von 1.9.1 auf 1.9.2 blieben Daten und Sitzung erhalten. Migriert eine Version die Datenbank, geht es zurück laut Wiki nur über die Sicherung. Die GHCR-Tags tragen ein v (v1.9.2), ein Tag 1.9.2 existiert nicht. Statt automatischer Updates per Watchtower empfiehlt sich bei einer festgeschriebenen Version eine Benachrichtigung, siehe Container-Updates mit Diun und WUD.

Verifizieren: /api/health meldet die neue Versionsnummer, die Seitenleiste zeigt „Self-hosted · v1.9.2“, und Ihre Bewerbungen sind vollständig.

Troubleshooting

MeldungUrsacheLösung
required variable DOSSIER_SECRET is missing a value: Set DOSSIER_SECRET in .envVariable fehlt in der .env, Compose bricht vor dem Start abWert mit openssl rand -base64 32 erzeugen und eintragen
Dossier can't start: DOSSIER_SECRET is too short (7 characters)Geheimnis kürzer als 32 Zeichen, Container startet in Schleife neulängeren Wert setzen, docker compose up -d
DOSSIER_PASSWORD must be a hash of your password, not the password itselfKlartextpasswort statt Hash in der .envHash mit hash-password erzeugen
{"status":"unavailable","db":"unreachable"} mit HTTP 503Datenbank-Container gestoppt oder nicht erreichbardocker compose ps, Container db starten
The server doesn't have the model “qwen2.5:3b”Modell fehlt in Ollama, etwa nach down -vdocker compose exec ollama ollama pull qwen2.5:3b
You've been signed out. Sign in again to continue. (HTTP 401)API-Aufruf ohne gültige Sitzungneu anmelden
This request didn't come from Dossier's own pages, so it was blocked. (HTTP 403)Skript schickt Import ohne passenden Origin-HeaderImport über die Oberfläche oder mit korrektem Origin

Den Log zeigt docker compose logs app. Bei langsamen lokalen Modellen hilft ein höheres DOSSIER_AI_TIMEOUT_SECONDS.

Häufige Fragen

Kann ich Dossier mit mehreren Personen nutzen?

Nein. Dossier kennt ein Passwort und ein Profil. Für mehrere Personen brauchen Sie getrennte Instanzen mit eigener Datenbank; laut Projekt darf je Datenbank nur eine App-Instanz laufen.

Brauche ich zwingend eine KI?

Für Profil, Basis-Lebenslauf und PDF-Druck nicht. Neue Bewerbungen aus einer Stellenanzeige entstehen aber über das Sprachmodell.

Welche Daten verlassen den Server?

Ohne Zusatzdienste nur die tägliche Update-Abfrage bei GitHub und, nach Zustimmung, der Nutzungs-Ping mit Installations-ID und Version.

Fazit

Dossier ist schnell aufgesetzt und sauber gebaut: Passwort nur als Hash, Port nur lokal, klare Startfehler und ein Restore, der per JSON-Import wie per pg_dump funktionierte. Ein kleines lokales Modell hält die Unterlagen im Haus, schreibt aber schlichter und auf der CPU langsam. Schreiben Sie die Version fest, sichern Sie JSON, Dump und .env regelmäßig weg und prüfen Sie jeden Entwurf vor dem Versand.

Weiterführende Anleitungen und Quellen

DossierDocker ComposeBewerbungLebenslaufOllamaLokale KIPostgreSQLSelfhosting