apps.header.allAppsCount
obsidian-sync-station
apps.detail.types.app from benjaminmue's Repository
apps.detail.sections.overview
Readme
View on GitHubObsidian Sync Station
🤖 Built with AI — disclosed openly. This project is developed with heavy assistance from AI (Anthropic's Claude, via Claude Code): code, tests, and documentation. This is stated up front, not hidden. It's used for a personal homelab; review the code yourself before trusting it with your data, and treat it accordingly. Issues and PRs are welcome.
A small Docker container with a web UI that keeps an Obsidian vault synced using Obsidian's official headless Sync client — no LiveSync/CouchDB, no VNC/GUI container, no third-party sync protocol. Built for Unraid, runs anywhere Docker does.
Independent, unofficial project. Not affiliated with Obsidian. Requires an active Obsidian Sync subscription. See NOTICE.md.
Why
Obsidian Sync is proprietary: no self-hosted server, no REST API. In February
2026 Obsidian shipped an official headless client
(obsidian-headless, the ob
CLI) that runs the real sync from the command line. This project wraps it in a
configurable web UI and packages it for a server, so a vault stays continuously
synced and (optionally) backed up on your own hardware.
Features
- Official, headless Obsidian Sync — no LiveSync/CouchDB, no VNC/GUI container.
- Web UI (Obsidian-style dark/purple), LAN-only, gated by its own password.
- Obsidian login (email / password / MFA), vault picker, optional end-to-end decryption password.
- Two sync modes: continuous (live, ~30s) or every N minutes — supervised with auto-restart, live status and log tail.
- Local backups (
BACKUP=true): scheduledtar.gzsnapshots with retention, run-now, and a snapshot list. - Off-box mirror (
MIRROR=true): copy each snapshot to a second volume. - Encrypted off-site backups via restic (local path or cloud: S3/B2/SFTP).
- Restore a snapshot to a safe staging area or over the live vault.
- ntfy notifications on backup/sync events.
- Unraid listed in Community Applications + prebuilt multi-arch image on GHCR.
Install on Unraid
Open Apps (Community Applications), search for obsidian-sync-station and install it.
The CA template is maintained in the repository
benjaminmue/unraid.
To add the container without CA, use the Docker tab → Add Container and paste that
template URL:
https://raw.githubusercontent.com/benjaminmue/unraid/main/templates/obsidian-sync-station.xml
After installing:
- Adjust the paths (
/vaultshould be its own dedicated share), set Enable Backup if you want backups (and map/backup), then Apply. - Open the WebUI, set an access password, log in to Obsidian, pick your vault.
The image is published to GHCR: ghcr.io/benjaminmue/obsidian-sync-station:latest.
Sync mode
In the Sync card you can choose how syncing runs:
- Continuous (default) — Obsidian's own live watcher (
ob sync --continuous), which polls roughly every 30 seconds. - Every N minutes — the station runs a one-shot
ob syncon a timer instead (set the interval, e.g. 5 minutes). Lighter, for people who don't need near-realtime sync.
Changing the mode re-applies immediately if sync is currently running.
Backups
Set BACKUP=true and map a /backup volume to your storage. A "Backup" card
appears in the UI where you set the cron schedule (default 0 3 * * *) and how
many snapshots to keep. Snapshots are tar.gz archives of the vault contents;
older ones beyond the retention count are pruned automatically.
Mirror (off-box copy)
Set MIRROR=true and map a /mirror volume (a different disk or a remote share
mounted on the host) to copy every new snapshot there as well, pruned by the same
retention. For encrypted or cloud off-site copies, see restic.
Restore
Each snapshot in the UI has two restore actions:
- To staging — extracts into
/config/restores/<snapshot>/for inspection. Safe; never touches the live vault. - To vault — extracts over the live vault. Destructive: sync is stopped and left stopped. When you restart sync, the restored state is pushed to Obsidian's remote and may overwrite newer changes. Requires an explicit confirmation.
Off-site backup (restic)
Set RESTIC_REPOSITORY and RESTIC_PASSWORD to also back the vault up to an
encrypted restic repository — a local path, or cloud
storage (S3, Backblaze B2, SFTP, …) via the usual restic backend env vars. It
runs automatically after each local backup and keeps the newest N snapshots
(same retention). A "Off-site backup (restic)" card in the UI shows status,
snapshots, a run-now button, and restore-to-staging. The restic repo is
encrypted independently of the vault's own encryption.
Notifications
Set an ntfy topic URL (NTFY_URL or in the UI) to get push notifications on
successful backups and on backup/sync failures. Toggle each event in Settings.
Public topics need no token; for an auth-protected ntfy server set an access
token (NTFY_TOKEN or the masked field in the UI) — it's sent as a Bearer token.
Run it (local / any Docker host)
docker compose up --build
Then open http://localhost:8080, set an access password, log in to Obsidian, pick your vault, and start syncing.
Volumes
| Container path | Purpose |
|---|---|
/config |
Persistent state: settings, GUI password hash, ob login + install |
/vault |
The synced vault files (map to a dedicated share) |
/backup |
Snapshot target (only when BACKUP=true) |
/mirror |
Second snapshot target (only when MIRROR=true) |
Environment
| Variable | Default | Purpose |
|---|---|---|
BACKUP |
false |
Enable backups + backup options in the UI |
MIRROR |
false |
Also copy each snapshot to a second /mirror volume |
RESTIC_REPOSITORY |
— | Enable encrypted off-site backups via restic (local path or cloud) |
RESTIC_PASSWORD |
— | Encryption password for the restic repository |
NTFY_URL |
— | Optional ntfy topic URL for push notifications (also settable in UI) |
NTFY_TOKEN |
— | Optional ntfy access token (Bearer) for auth-protected servers (also settable in UI) |
WEBUI_PORT |
8080 |
Web UI port |
DEVICE_NAME |
obsidian-sync-station |
Label in Obsidian Sync history |
Important
- Never run the Obsidian desktop app against the same vault path at the same time — the official client warns this causes conflicts. Give the container its own dedicated path.
- With end-to-end encryption,
obdecrypts locally and writes plaintext to/vault(and therefore to backups). Choose your storage accordingly. - Do not expose the web UI to the internet. It holds your Obsidian credentials.
How the proprietary client is handled
obsidian-headless is proprietary (published UNLICENSED). This image does not
bundle it. The container installs it from the official npm registry on first
start, into the /config volume. Only this project's own MIT-licensed code is
distributed. See NOTICE.md.
License
apps.marketingCta.appInstallTitle
apps.marketingCta.appInstallDescription
apps.detail.sections.categories
apps.detail.sections.related
apps.detail.related.exploreCategories
apps.detail.related.exploreAllapps.detail.sections.links
apps.detail.sections.details
ghcr.io/benjaminmue/obsidian-sync-station:latestapps.detail.sections.runtime
- apps.detail.details.webui
http://[IP]:[PORT:8080]/- apps.detail.details.network
bridge- apps.detail.details.shell
sh- apps.detail.details.privileged
- false
apps.detail.sections.configuration
Host port for the web UI (container listens on 8080). 8080 is often taken on Unraid, so this defaults to 8484 — change it if needed. LAN-only, do not expose to the internet.
- apps.detail.config.target
- 8080
- apps.detail.config.default
- 8484
- apps.detail.config.value
- 8484
Persistent state: settings, GUI password, Obsidian login + the ob client install.
- apps.detail.config.target
- /config
- apps.detail.config.default
- /mnt/user/appdata/obsidian-sync-station
- apps.detail.config.value
- /mnt/user/appdata/obsidian-sync-station
The synced vault files. Use a DEDICATED path only this container writes to. Never point the Obsidian desktop app here at the same time.
- apps.detail.config.target
- /vault
- apps.detail.config.default
- /mnt/user/obsidian-vault
- apps.detail.config.value
- /mnt/user/obsidian-vault
Snapshot target on your server storage. Only used when Enable Backup is true.
- apps.detail.config.target
- /backup
- apps.detail.config.default
- /mnt/user/backups/obsidian
- apps.detail.config.value
- /mnt/user/backups/obsidian
Set to true to enable scheduled backups and show backup options in the web UI.
- apps.detail.config.target
- BACKUP
- apps.detail.config.default
- false
- apps.detail.config.value
- false
Label shown in the Obsidian Sync history.
- apps.detail.config.target
- DEVICE_NAME
- apps.detail.config.default
- unraid-sync-station
- apps.detail.config.value
- unraid-sync-station
Container timezone. Drives the backup schedule and all timestamps shown in the web UI.
- apps.detail.config.target
- TZ
- apps.detail.config.default
- Europe/Zurich
- apps.detail.config.value
- Europe/Zurich
Set to true to also copy each snapshot to a second destination (map the Mirror path).
- apps.detail.config.target
- MIRROR
- apps.detail.config.default
- false
- apps.detail.config.value
- false
Second snapshot destination. Only used when Mirror snapshots is true. Point this at another disk/share for an off-box copy.
- apps.detail.config.target
- /mirror
- apps.detail.config.default
- /mnt/user/backups/obsidian-mirror
- apps.detail.config.value
- /mnt/user/backups/obsidian-mirror
Optional ntfy topic URL for push notifications (also settable in the web UI), e.g. https://ntfy.example.com/obsidian.
- apps.detail.config.target
- NTFY_URL
Optional ntfy access token (Bearer) for auth-protected ntfy servers. Leave empty for public topics. Also settable in the web UI.
- apps.detail.config.target
- NTFY_TOKEN
Optional off-site backup via restic. Local example: /backup/restic. Cloud: s3:s3.amazonaws.com/bucket, b2:bucket:path, sftp:user@host:/path. For cloud, add the backend credential variables too (AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, B2_ACCOUNT_ID/B2_ACCOUNT_KEY, ...) via Add another Path/Port/Variable.
- apps.detail.config.target
- RESTIC_REPOSITORY
Encryption password for the restic repository. Required when restic repository is set. Keep it safe — the repo cannot be read without it.
- apps.detail.config.target
- RESTIC_PASSWORD