Joplin Server is the sync backend for Joplin, an open-source note-taking app with Markdown, notebooks, tags, attachments, and a real mobile and desktop client. It's E2EE by default and works offline — the notes are on your disk first and the server is a convenience.
What it is
Markdown notes with backlinks, tags, search across everything including attachments (if you set up the extraction service), and a sharing feature that publishes notes to read-only web pages. The desktop app is Electron and works with any filesystem folder.
Before you start
- Nextcloud and Joplin Server are different projects with similar names. This guide is the standalone Joplin Server, not the Nextcloud Joplin app.
- Newer tags dropped the
WEB_PORT/API_PORTsplit in favour ofSERVER_PORT. Check the release notes for the tag you pin. - The
joplin/workersidecar is needed for attachment text extraction. Skip it and search only covers note bodies.
1 — Create the directory
mkdir -p ~/services/joplin/{data,pgdata}
cd ~/services/joplin
openssl rand -hex 32 # DB_PASSWORD and POSTGRES_PASSWORD
2 — Write the compose file
services:
db:
image: postgres:16-alpine
container_name: joplin-db
restart: unless-stopped
environment:
POSTGRES_USER: joplin
POSTGRES_PASSWORD: CHANGE_ME
POSTGRES_DB: joplin
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U joplin"]
interval: 10s
timeout: 5s
retries: 5
server:
image: joplin/server:latest
container_name: joplin-server
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:41184:41184"
volumes:
- ./data:/data
environment:
DB_CLIENT: pg
DB_HOSTNAME: db
DB_PORT: 5432
DB_NAME: joplin
DB_USER: joplin
DB_PASSWORD: CHANGE_ME
SERVER_PORT: 41184
SERVER_PUBLIC_URL: https://notes.example.com
CLIENT_ID: CHANGE_ME_client_id
CLIENT_SECRET: CHANGE_ME_client_secret
# required for the share/public-page feature
SHARE_USER_SYSTEM: USE_SYSTEM_ACCOUNT
SUPERPASS: CHANGE_ME
TZ: UTC
worker:
image: joplin/worker:latest
container_name: joplin-worker
restart: unless-stopped
user: "1000:1000"
volumes:
- ./worker-data:/data
environment:
DB_CLIENT: pg
DB_HOSTNAME: db
DB_PORT: 5432
DB_NAME: joplin
DB_USER: joplin
DB_PASSWORD: CHANGE_ME
WORKER_CONTENT_API: http://server:41184
WORKER_CONTENT_TOKEN: CHANGE_ME
WORKER_LOG_LEVEL: warn
3 — Start it
docker compose up -d
docker compose logs -f server | grep -i "server started\|error"
4 — First-run setup
- Register the first account at
https://notes.example.com— the first registered user becomes the admin. - In the Joplin desktop app, go to Tools → Options → Sync target → Joplin Server and enter the URL, email and password. Add a second device and confirm the notes appear on both.
- Turn on E2EE encryption in the client with a strong passphrase. The server never sees note contents, and it means a compromise of the server reveals nothing useful.
- Test sharing: right-click a note → Share → publish. If the share page doesn't load, the
SHARE_USER_SYSTEMsetting is wrong. - Confirm the worker is extracting text: create a note, attach a PDF, search for a word inside the PDF. If that fails, the worker token is mismatched.
5 — Maintenance
docker exec -u postgres joplin-db pg_dump -U joplin joplin | gzip > joplin-$(date +%F).sql.gz
# attachments live in ./data, export in ./data/db.sqlite (older tags)