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

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
| Eckdaten | Wert |
|---|---|
| Image | ghcr.io/aientech/dossier:v1.9.2, Datenbank postgres:18-alpine, optional ollama/ollama:0.40.6 |
| Port | 127.0.0.1:3000 (nur lokal, für den Reverse Proxy) |
| Volumes | pgdata (Datenbank), data unter /data (nächtliche Sicherungen, PDF-Archive), ollama (Modelle) |
| Pflicht-Variablen | POSTGRES_PASSWORD, DOSSIER_SECRET, DOSSIER_PASSWORD (Hash) |
| Getestete Version | Dossier 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.

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.

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.

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.

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:

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
| Meldung | Ursache | Lösung |
|---|---|---|
required variable DOSSIER_SECRET is missing a value: Set DOSSIER_SECRET in .env | Variable fehlt in der .env, Compose bricht vor dem Start ab | Wert 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 neu | längeren Wert setzen, docker compose up -d |
DOSSIER_PASSWORD must be a hash of your password, not the password itself | Klartextpasswort statt Hash in der .env | Hash mit hash-password erzeugen |
{"status":"unavailable","db":"unreachable"} mit HTTP 503 | Datenbank-Container gestoppt oder nicht erreichbar | docker compose ps, Container db starten |
The server doesn't have the model “qwen2.5:3b” | Modell fehlt in Ollama, etwa nach down -v | docker 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 Sitzung | neu anmelden |
This request didn't come from Dossier's own pages, so it was blocked. (HTTP 403) | Skript schickt Import ohne passenden Origin-Header | Import ü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
- Reactive Resume mit Docker Compose: Lebenslauf-Editor selbst hosten
- Docker Compose unter Linux: Selfhosting-Grundlage
- Caddy als Reverse Proxy mit automatischem HTTPS
- AienTech/dossier auf GitHub
- Release-Notes Dossier 1.9.2 und 1.9.1
- Wiki: Backups and restore
- Wiki: Upgrading
- Wiki: Choosing an AI provider
- Wiki: Privacy
- gittrend.io: Trending Self-Hosted


