---
title: "Joplin mit Docker installieren: Quelloffene Notiz- und To-do-App mit selbst gehostetem Server"
date: 2026-06-27T06:48:56+02:00
modified: 2026-11-08T07:44:07+01:00
category: cloud
type: how_to
author: Marcel Schönfelder
canonical: https://s-edv.com/anleitungen/joplin-docker-notes
reading_time: 9
personalize_url: "https://s-edv.com/anleitungen/joplin-docker-notes#domain={domain}&ip={ip}"
variables:
  - name: domain
    label: "Ihre Domain"
    default: "joplin.example.com"
  - name: ip
    label: "IP-Adresse"
    default: "192.168.1.100"
  - name: postgres_password
    label: "PostgreSQL-Passwort"
    generated_in_browser: true
---

# Joplin mit Docker installieren: Quelloffene Notiz- und To-do-App mit selbst gehostetem Server

## Anpassbare Werte

Diese Anleitung passt sich auf der Seite an das System des Lesers an. Im Text unten stehen diese Werte als `{{name}}`:

| Platzhalter | Bedeutung | Beispielwert |
| --- | --- | --- |
| `{{domain}}` | Ihre Domain | `joplin.example.com` |
| `{{ip}}` | IP-Adresse | `192.168.1.100` |
| `{{postgres_password}}` | PostgreSQL-Passwort | wird im Browser des Lesers zufällig erzeugt |

Link mit eigenen Werten: https://s-edv.com/anleitungen/joplin-docker-notes#domain=cloud.example.com&ip=192.168.1.20

Dieser Link öffnet die Anleitung und schlägt dem Leser die Werte vor. Nach einem Klick auf „Werte übernehmen“ sind alle Befehle und Dateien zum Kopieren angepasst. Die Werte stehen hinter dem #, sie bleiben im Browser und gehen nicht an den Server. Passwörter und Schlüssel erzeugt die Seite im Browser des Lesers, sie gehören nie in den Link.

*Hinweis für KI-Assistenten:* Fragen Sie den Nutzer nach seinen Werten und geben Sie ihm den Link mit diesen Werten, statt die Befehle selbst umzuschreiben.

Joplin ist eine quelloffene Notiz- und To-do-App mit Markdown, Notizbüchern und optionaler Ende-zu-Ende-Verschlüsselung (E2EE). Statt über Dropbox oder OneDrive synchronisieren die Clients mit dem selbst gehosteten **Joplin Server**: Notizen und Anhänge bleiben auf Ihrer Infrastruktur, Teams teilen Notizbücher, Drittanbieter entfallen. Für KMU ist das eine datenschutzfreundliche Alternative zu Evernote oder OneNote. Die Anleitung richtet Joplin Server mit Docker Compose auf einem Linux-Host ein, inklusive PostgreSQL, persistentem Storage und Web-Admin.

**Kurzfassung:** Sie legen einen Projektordner an, erstellen eine `.env`-Datei mit Datenbank-Zugangsdaten und der öffentlichen Base-URL, starten den Stack mit `docker compose up -d` und melden sich anschließend mit den Standard-Zugangsdaten am Web-Interface an. Das Standard-Passwort *muss* sofort geändert werden. Weitere Nutzer legen Sie im Admin-Bereich an; alle offiziellen Joplin-Clients (Desktop, Mobile, Terminal) verbinden sich über die Sync-Einstellung „Joplin Server“. Für den Internet-Betrieb empfiehlt sich ein Reverse-Proxy mit HTTPS.

## Voraussetzungen

- [ ] Linux-Host, VM oder NAS mit Docker-Unterstützung (Ubuntu 22.04/24.04 oder Debian empfohlen), x86_64 oder ARM64, 1 bis 2 CPU-Kerne, mindestens 2 GB RAM, empfohlen 4 GB; mindestens 10 GB freier Speicher. Im Test belegten Joplin Server und PostgreSQL zusammen rund 420 MB RAM
- [ ] Docker Engine (mind. Version 20.10) und Docker Compose Plugin v2 installiert. Falls noch nicht vorhanden, folgen Sie der Anleitung [Docker und Docker Compose auf Linux installieren](https://s-edv.com/anleitungen/docker-compose-linux-selfhosting-grundlage)
- [ ] Netzwerkzugriff auf Port 22300 des Hosts (oder Port 80/443 bei Reverse-Proxy)
- [ ] Für HTTPS-Betrieb: Domain oder DynDNS-Eintrag und ein Reverse-Proxy (Nginx, Traefik oder Caddy), empfehlenswert für alles außerhalb des lokalen Netzes; eine Einführung bietet [Caddy als Reverse Proxy einrichten](https://s-edv.com/anleitungen/caddy-reverse-proxy-automatisches-https-anleitung)
- [ ] Texteditor (nano, vim oder VS Code) für `compose.yaml` und `.env`
- [ ] Browser und `curl` für die Verifikation

## Schritt 1: Projektordner anlegen

Legen Sie einen eigenen Ordner für alle Dateien des Stacks an; die Unterordner für PostgreSQL entstehen beim ersten Start.

```bash
# mkdir -p /opt/joplin
# cd /opt/joplin
```

> **Prüfen:** Das Verzeichnis existiert und ist leer:
>
> ```bash
> # ls /opt/joplin
> ```
>
> ```bash
> # Erwartete Ausgabe: (leer)
> ```

## Schritt 2: .env-Datei mit Secrets anlegen

Die `.env`-Datei im Projektordner enthält Zugangsdaten und öffentliche Base-URL, wird von Docker Compose automatisch eingelesen und *darf nicht* in eine öffentliche Versionsverwaltung. Ersetzen Sie `192.168.1.100` durch IP oder Hostnamen Ihres Servers und wählen Sie ein sicheres Passwort.

Datei: `/opt/joplin/.env`

```ini
# /opt/joplin/.env

# Öffentliche URL des Joplin Servers: EXAKT so, wie Clients ihn erreichen.
# Bei Reverse-Proxy mit HTTPS: https://{{domain}} (kein trailing slash!)
APP_BASE_URL=http://{{ip}}:22300

# PostgreSQL-Zugangsdaten (müssen in db- und app-Service identisch sein)
POSTGRES_USER=joplin
POSTGRES_PASSWORD={{postgres_password}}
POSTGRES_DATABASE=joplindb
```

Schränken Sie die Datei-Berechtigungen ein, damit nur der Eigentümer (hier root) sie lesen kann:

```bash
# chmod 600 /opt/joplin/.env
```

> **Prüfen:** ```bash
> # ls -la /opt/joplin/.env
> ```
>
> ```bash
> # Erwartete Ausgabe: -rw------- 1 root root ... /opt/joplin/.env
> ```

## Schritt 3: compose.yaml erstellen

Erstellen Sie `/opt/joplin/compose.yaml`. Der Stack besteht aus `db` (PostgreSQL 16) und `app` (Joplin Server); `app` startet erst nach bestandenem Datenbank-Healthcheck, was Verbindungsfehler beim ersten Start verhindert.

Datei: `/opt/joplin/compose.yaml`

```yaml
services:
  db:
    image: postgres:16
    container_name: joplin-db
    restart: unless-stopped
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${POSTGRES_DATABASE}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DATABASE}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - joplin-net

  app:
    image: joplin/server:latest
    container_name: joplin-app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "22300:22300"
    environment:
      APP_PORT: 22300
      APP_BASE_URL: ${APP_BASE_URL}
      DB_CLIENT: pg
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DATABASE: ${POSTGRES_DATABASE}
    networks:
      - joplin-net

networks:
  joplin-net:
    driver: bridge
```

Wichtige Entscheidungen in dieser Konfiguration:

1. **PostgreSQL statt SQLite:** `DB_CLIENT=pg` aktiviert PostgreSQL, das für Multi-User-Betrieb zwingend nötig ist. SQLite ist nur für lokale Tests geeignet und bei Container-Neustart verlustgefährdet.
2. **Kein Expose von Port 5432:** Der Datenbank-Port wird bewusst *nicht* nach außen gemappt; die App erreicht die DB intern über den Service-Namen `db`.
3. **Healthcheck + depends_on:** Verhindert Race-Conditions beim ersten Start zuverlässig.
4. **Image-Tag**`latest`**:** Entspricht derzeit Version 3.7.2 (Stand September 2026). Für eine reproduzierbare Produktion schreiben Sie besser einen versionierten Tag fest, z. B. `joplin/server:3.7.2`.

> **Prüfen:** Die YAML-Syntax ist fehlerfrei:
>
> ```bash
> # docker compose -f /opt/joplin/compose.yaml config --quiet && echo "YAML OK"
> ```
>
> ```bash
> # Erwartete Ausgabe: YAML OK
> ```

## Schritt 4: Stack starten

Starten Sie den Stack aus dem Projektordner heraus. Docker Compose liest die `.env`-Datei automatisch ein:

```bash
# cd /opt/joplin
# docker compose up -d
```

Beim ersten Start lädt Docker die Images (Joplin Server ab 3.7.2 ca. 190 MB, PostgreSQL ca. 400 MB) und richtet die Datenbank ein; das kann einige Minuten dauern.

> **Prüfen:** Beide Container laufen und der db-Container zeigt `healthy`:
>
> ```bash
> # docker compose ps
> ```
>
> ```bash
> # Erwartete Ausgabe (gekürzt):
> # NAME           IMAGE                  STATUS
> # joplin-db      postgres:16            Up ... (healthy)
> # joplin-app     joplin/server:latest   Up ...
> ```
>
> ```bash
> # docker compose logs app --tail=30
> ```
>
> ```bash
> # Kein "error", kein "FATAL", dafür Zeilen wie:
> # App: Starting server v3.7.2 (prod) on port 22300 ...
> ```

Meldet das Log `FATAL: password authentication failed`, stimmen die Datenbankpasswörter in der `.env` nicht überein (Lösung unter Troubleshooting).

## Schritt 5: Erst-Einrichtung im Browser

Öffnen Sie `http://<SERVER-IP>:22300` und melden Sie sich im Web-Admin mit den Standard-Zugangsdaten an:

1. **E-Mail:** `admin@localhost`
2. **Passwort:** `admin`

> **Achtung:** Ändern Sie sofort nach dem ersten Login E-Mail-Adresse und Passwort unter *Admin > Users > admin*. Das Standard-Passwort ist öffentlich bekannt.

Weitere Nutzer legen Sie ebenfalls unter *Admin > Users* an, jeweils mit eigener E-Mail-Adresse und Passwort für die Clients.

> **Prüfen:** Der Status-Endpunkt antwortet. Verwenden Sie dieselbe Adresse wie in `APP_BASE_URL`; `curl -I` (HEAD-Anfrage) beantwortet Joplin Server grundsätzlich mit `400`, daher ein normales GET:
>
> ```bash
> # curl http://{{ip}}:22300/api/ping
> ```
>
> ```bash
> # Erwartete Ausgabe:
> # {"status":"ok","message":"Joplin Server is running"}
> ```

## Schritt 6: Joplin-Client verbinden

Im Joplin-Desktop-Client (Windows, macOS oder Linux) wählen Sie unter *Werkzeuge > Einstellungen > Synchronisierung* als Ziel **Joplin Server** und tragen ein:

1. **Joplin Server URL:** den Wert aus `APP_BASE_URL`, z. B. `http://{{ip}}:22300`
2. **E-Mail:** die gerade gesetzte Admin-E-Mail (oder ein neu angelegter Nutzer)
3. **Passwort:** das geänderte Passwort

„Synchronisierung prüfen“ meldet eine erfolgreiche Verbindung; beim nächsten Sync werden alle Notizen übertragen.

Für Mobile-Clients (Android / iOS) ist der Weg identisch: *Konfiguration > Synchronisierung > Joplin Server*.

> **Prüfen:** ```bash
> # docker compose logs app --tail=10
> ```
>
> ```bash
> # Nach dem ersten Client-Sync erscheinen Einträge wie:
> # PUT /api/items/root:/... (200)
> ```

## Schritt 7: Ende-zu-Ende-Verschlüsselung aktivieren (optional)

E2EE ist rein clientseitig und unabhängig vom Sync-Ziel. Aktivieren Sie sie unter *Werkzeuge > Einstellungen > Verschlüsselung*; der Server sieht nur verschlüsselte Daten. Bewahren Sie das Schlüssel-Passwort sicher auf: Geht es verloren, sind alle Notizen unwiederbringlich verschlüsselt.

> **Prüfen:** Im Client-Menü erscheint unter *Verschlüsselung* der Status „Aktiviert“ und ein Verschlüsselungsschlüssel ist aufgelistet.

## Schritt 8: HTTPS mit Reverse-Proxy (empfohlen für Internet-Betrieb)

Joplin Server terminiert kein TLS. Für Internetzugriff ist ein Reverse-Proxy mit SSL-Zertifikat Pflicht, sonst gehen Anmeldedaten und Notizen unverschlüsselt über die Leitung. Eine Anleitung für Caddy mit automatischem Let's-Encrypt-Zertifikat: [Caddy als Reverse Proxy einrichten](https://s-edv.com/anleitungen/caddy-reverse-proxy-automatisches-https-anleitung). Binden Sie Port 22300 dann nur lokal (`"127.0.0.1:22300:22300"`).

> **Achtung:** Nach dem Umstieg auf HTTPS muss `APP_BASE_URL` in der `.env`-Datei auf die HTTPS-URL aktualisiert werden:

Datei: `/opt/joplin/.env`

```ini
APP_BASE_URL=https://{{domain}}
```

Anschließend Stack neu starten:

```bash
# cd /opt/joplin
# docker compose up -d
```

> **Prüfen:** ```bash
> # curl https://{{domain}}/api/ping
> ```
>
> ```bash
> # Erwartete Ausgabe: {"status":"ok","message":"Joplin Server is running"}
> ```

## Updates und Backups

Joplin Server führt Datenbankmigrationen beim Start automatisch durch. Ein Update läuft daher in zwei Befehlen:

```bash
# cd /opt/joplin
# docker compose pull && docker compose up -d
```

Erstellen Sie vor jedem Update ein Datenbank-Backup:

```bash
# PostgreSQL-Dump
docker exec joplin-db pg_dump -U joplin joplindb > /opt/joplin/backup-$(date +%F).sql

# Backup prüfen
ls -lh /opt/joplin/backup-*.sql
```

Beziehen Sie `./data/postgres` zusätzlich ins Backup ein. Bind Mounts statt Named Volumes schützen vor `docker compose down -v`. Eine vollständige Strategie: [MySQL & PostgreSQL Backup automatisieren mit cron](https://s-edv.com/anleitungen/mysql-postgresql-backup-automatisieren-cron-cloud).

**Verifizieren nach Update:**

```bash
# docker compose ps
```

```bash
# Beide Container Up/healthy, kein Restart-Loop
```

```bash
# docker compose logs app --tail=20
```

```bash
# Keine Migrationsfehler; Zeile: "App: Starting server v3.7.2 (prod) on port 22300"
```

## Eckdaten auf einen Blick

| Parameter | Wert |
| --- | --- |
| Image | joplin/server:latest (Stand September 2026: 3.7.2) |
| Begleit-Image | postgres:16 |
| Image-Größe | ca. 190 MB (3.7.2) |
| Port (App) | 22300 (HTTP, Web-UI + API) |
| Port (DB intern) | 5432 (nicht nach außen exponieren) |
| Volume (DB) | ./data/postgres:/var/lib/postgresql/data |
| Pflicht-Env | APP_BASE_URL, DB_CLIENT, POSTGRES_HOST, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DATABASE |
| Standard-Login | admin@localhost / admin (sofort ändern!) |
| Architektur | amd64 (x86_64) und arm64 |

## Troubleshooting / Typische Fehler

****„Invalid origin“ im Client****

`APP_BASE_URL` stimmt nicht exakt mit der URL überein, die der Client verwendet. Häufige Ursachen: `http` statt `https`, fehlender oder falscher Port, ein Trailing-Slash am Ende der URL. Lösung: `.env` anpassen, Stack neu starten.

****App startet nicht / Verbindung zur DB schlägt fehl****

Ohne `depends_on` mit Healthcheck startet die App vor PostgreSQL; die gezeigte Konfiguration verhindert das. Prüfen: `docker compose logs db` muss `database system is ready to accept connections` zeigen.

**`FATAL: password authentication failed for user "joplin"`**

`POSTGRES_PASSWORD` in `.env` stimmt nicht mit dem beim DB-Init gesetzten Wert überein. Stack vollständig stoppen, `./data/postgres` löschen (Datenverlust!), `.env` korrigieren und neu starten.

****Standard-Passwort vergessen zu ändern****

`admin@localhost` / `admin` sind öffentlich bekannt. Sofort nach dem ersten Login unter *Admin > Users* ändern.

****Port 22300 bereits belegt****

Fehlermeldung `Bind: address already in use`. Prüfen mit `ss -tlnp | grep 22300`, Prozess stoppen oder externen Port in der `compose.yaml` anpassen (z. B. `22301:22300`).

****SQLite statt PostgreSQL aktiv****

Fehlt `DB_CLIENT` oder ist falsch gesetzt, fällt Joplin auf SQLite zurück, ungeeignet für Multi-User und bei `docker compose down` verloren. Lösung: `DB_CLIENT=pg` plus alle `POSTGRES_*`-Variablen setzen.

****Kein HTTPS im Internet-Betrieb****

Ohne Reverse-Proxy mit TLS werden Anmeldedaten unverschlüsselt übertragen. Caddy, Nginx oder Traefik vorschalten; `APP_BASE_URL` auf `https://` ändern.

## Häufige Fragen

### Welche Joplin-Clients können sich mit dem selbst gehosteten Server synchronisieren?

Alle offiziellen Clients: Desktop (Windows, macOS, Linux), Mobile (Android, iOS) und Terminal-App. In den Sync-Einstellungen wählen Sie „Joplin Server“ und tragen `APP_BASE_URL` und Zugangsdaten ein.

### Ist Ende-zu-Ende-Verschlüsselung (E2EE) mit dem selbst gehosteten Server möglich?

Ja. E2EE funktioniert clientseitig mit jedem Sync-Ziel; aktivieren Sie sie in jedem Client unter *Einstellungen > Verschlüsselung*. Der Server speichert dann nur verschlüsselte Daten.

### Kann ich Notizbücher mit anderen Nutzern teilen?

Ja, das unterscheidet ihn von WebDAV- oder S3-Sync. Nach dem Anlegen weiterer Nutzer unter *Admin > Users* geben Sie im Desktop-Client Notizbücher per Share-Funktion frei; Änderungen synchronisieren in beide Richtungen.

### Wie führe ich ein Update auf eine neue Version durch?

Mit `docker compose pull && docker compose up -d`; Datenbankmigrationen laufen beim Neustart automatisch. Erstellen Sie vorher unbedingt einen Dump mit `docker exec joplin-db pg_dump -U joplin joplindb > backup.sql`.

### Was ist der Unterschied zwischen Joplin Server und Joplin Cloud?

Joplin Cloud ist der kostenpflichtige Hosted-Service von Joplin-Entwickler Laurent Cozic, ohne eigene Infrastruktur, mit Daten beim Anbieter. Joplin Server ist das selbst gehostete Backend: volle Datenkontrolle, DSGVO-Konformität ohne Auftragsverarbeitungsvertrag mit einem Cloud-Anbieter, aber eigene Wartung und Backups.

### Brauche ich den optionalen Transcribe-Service?

Nur für die automatische Texterkennung handschriftlicher Notizen (Bilder). `joplin/transcribe:latest` braucht bis zu 16 GB RAM und 4 CPUs; die hier gezeigte Konfiguration ohne Transcribe ist vollständig funktionsfähig.

## Fazit

Joplin Server ist in unter 30 Minuten per Docker Compose installiert und dank PostgreSQL-Healthcheck und Bind-Mounts produktionstauglich. Wichtigster Schritt nach dem Start: das Standard-Passwort *sofort* ändern; außerhalb des lokalen Netzes gehört ein Reverse-Proxy mit HTTPS davor. Zur Container-Sicherheit: [Docker Compose absichern: Secrets, Healthchecks, Non-Root und Read-Only](https://s-edv.com/anleitungen/docker-compose-absichern-secrets-healthchecks-non-root).

## Weiterführende Anleitungen und Quellen

1. [Docker und Docker Compose auf Linux installieren: die Self-Hosting-Grundlage](https://s-edv.com/anleitungen/docker-compose-linux-selfhosting-grundlage)
2. [Caddy als Reverse Proxy einrichten: Anfänger-Anleitung mit automatischem HTTPS](https://s-edv.com/anleitungen/caddy-reverse-proxy-automatisches-https-anleitung)
3. [MySQL & PostgreSQL Backup automatisieren mit cron](https://s-edv.com/anleitungen/mysql-postgresql-backup-automatisieren-cron-cloud)
4. [Docker Compose absichern: Secrets, Healthchecks, Non-Root und Read-Only](https://s-edv.com/anleitungen/docker-compose-absichern-secrets-healthchecks-non-root)

Offizielle Quellen: [Joplin Server README (GitHub)](https://github.com/laurent22/joplin/blob/dev/packages/server/README.md) · [Joplin Server auf Docker Hub](https://hub.docker.com/r/joplin/server) · [Joplin Server Changelog](https://joplinapp.org/help/about/changelog/server/)

## Passende Anleitungen auf S-EDV

1. [Automatische Sicherheitsupdates unter Debian/Ubuntu mit unattended-upgrades rich](https://s-edv.com/anleitungen/unattended-upgrades-automatische-sicherheitsupdates-debian-ubuntu)
