A minimal Docker image for continuously syncing an Obsidian vault via obsidian-headless — the official headless client for Obsidian Sync released February 2026.
The container authenticates with a single environment variable token and runs ob sync --continuous to keep your vault in sync indefinitely.
Requirements: An active Obsidian Sync subscription.
Pull the image and run the interactive login helper. It will prompt for your Obsidian email, password, and MFA code (if enabled), then print your token to the terminal.
# Docker
docker run --rm -it ghcr.io/crosbyh/obsidian-headless-sync-docker:latest get-token
# Podman
podman run --rm -it ghcr.io/crosbyh/obsidian-headless-sync-docker:latest get-tokenCopy the printed OBSIDIAN_AUTH_TOKEN value — you'll need it in step 3.
Note: The token persists until you explicitly log out or revoke it from your Obsidian account. You only need to run this once per machine (or per token rotation).
List the vaults available on your Obsidian Sync account:
# Docker
docker run --rm \
-e OBSIDIAN_AUTH_TOKEN=your-token-here \
ghcr.io/crosbyh/obsidian-headless-sync-docker:latest \
ob sync-list-remote
# Podman
podman run --rm \
-e OBSIDIAN_AUTH_TOKEN=your-token-here \
ghcr.io/crosbyh/obsidian-headless-sync-docker:latest \
ob sync-list-remoteNote the exact vault name — you'll use it in VAULT_NAME.
cp .env.example .envEdit .env and fill in at minimum:
OBSIDIAN_AUTH_TOKEN=<token from step 1>
VAULT_NAME=My Vault
VAULT_HOST_PATH=./vaultSee Environment Variables for all options.
docker compose up -dOn first run the container performs a one-time ob sync-setup to link the local directory to your remote vault, then enters continuous sync mode. Subsequent restarts skip the setup and go straight to syncing.
Watch logs:
docker compose logs -f| Variable | Required | Default | Description |
|---|---|---|---|
OBSIDIAN_AUTH_TOKEN |
Yes | — | Auth token from get-token |
VAULT_NAME |
Yes (first run) | — | Exact name of the remote Obsidian Sync vault |
VAULT_HOST_PATH |
Yes | ./vault |
Host path where vault files will be written |
VAULT_PASSWORD |
If E2E enabled | — | Vault end-to-end encryption password (see below) |
PUID |
No | 1000 |
UID that will own synced files on the host (see below) |
PGID |
No | 1000 |
GID that will own synced files on the host (see below) |
VAULT_PATH |
No | /vault |
In-container mount path (advanced) |
DEVICE_NAME |
No | obsidian-docker |
Label shown in Obsidian Sync history |
CONFLICT_STRATEGY |
No | merge |
merge or conflict |
EXCLUDED_FOLDERS |
No | — | Comma-separated vault folders to skip |
FILE_TYPES |
No | — | Extra types to sync: image,audio,video,pdf,unsupported |
GHCR_REPO |
No | — | Override image repository when self-building |
By default the container process drops to UID/GID 1000:1000 before writing any files, so vault files on the host are owned by that user. Set PUID and PGID in .env to match whichever host user should own the files.
Regular Docker (daemon runs as root):
# Find your UID and GID
id
# uid=1000(you) gid=1000(you) ...PUID=1000
PGID=1000Rootless Docker (daemon runs as your user):
In rootless mode, container UID 0 already maps to the host user running the daemon — so files written by "root" inside the container land as your user on the host. Set both to 0:
PUID=0
PGID=0Setting any other UID in rootless mode will map to a sub-UID from /etc/subuid (typically a high number like 100999), which is almost certainly not what you want.
Obsidian Sync supports optional end-to-end encryption with a separate vault password. If your vault has this enabled, ob sync-setup will fail to authenticate until the password is provided.
To check: In the Obsidian desktop app, go to Settings → Sync and look for an "Encryption password" field — if it's present and set, E2E is active.
Add the password to your .env:
VAULT_PASSWORD=your-vault-encryption-passwordNote:
VAULT_PASSWORDis the vault encryption password you chose in Obsidian, not your Obsidian account password. They are separate credentials.
Images are published to the GitHub Container Registry on every push to main and on version tags.
# docker-compose.yml already points to:
image: ghcr.io/crosbyh/obsidian-headless-sync-docker:latestdocker build -t obsidian-headless-sync-docker .Then update docker-compose.yml to use image: obsidian-headless-sync-docker.
A ready-made quadlet unit file (obsidian-sync.container) is included for running the container as a systemd service under rootless Podman.
# Copy the quadlet into the user systemd search path
mkdir -p ~/.config/containers/systemd
cp obsidian-sync.container ~/.config/containers/systemd/
# Create a secrets file (mode 600 keeps your token private)
mkdir -p ~/.config/obsidian-sync
install -m 600 /dev/null ~/.config/obsidian-sync/obsidian-sync.envPopulate ~/.config/obsidian-sync/obsidian-sync.env with at minimum:
OBSIDIAN_AUTH_TOKEN=<token from get-token>
VAULT_NAME=My VaultOptional keys (defaults are set in the unit file):
VAULT_PASSWORD=
PUID=0
PGID=0
DEVICE_NAME=obsidian-podman
CONFLICT_STRATEGY=merge
EXCLUDED_FOLDERS=
FILE_TYPES=systemctl --user daemon-reload
systemctl --user start obsidian-sync
systemctl --user status obsidian-syncWatch logs:
journalctl --user -u obsidian-sync -fEnable the built-in Podman auto-update timer to pull new images from ghcr on a schedule:
systemctl --user enable --now podman-auto-update.timerThe unit also sets Pull=newer, so it will fetch a newer image from ghcr.io each time the service restarts.
The unit defaults to PUID=0 / PGID=0, which is correct for rootless Podman — container root maps to your host user. For system-wide (root) Podman, override these in the env file with the UID/GID of the vault owner.
By default the vault is stored at ~/obsidian-vault. To use a different path, edit the Volume= line in the unit file before copying it:
Volume=/path/to/your/vault:/vault:zdocker compose pull
docker compose up -ddocker compose downYour vault files remain on disk at VAULT_HOST_PATH.
Container exits immediately
- Check that
OBSIDIAN_AUTH_TOKENandVAULT_NAMEare set:docker compose config
"Vault not found" error on setup
- Confirm the vault name matches exactly (case-sensitive): run
ob sync-list-remoteas shown in Step 2.
"Failed to validate password" on setup
- Your vault has end-to-end encryption enabled. Set
VAULT_PASSWORDin.envto the encryption password from Obsidian → Settings → Sync. This is distinct from your Obsidian account password.
Vault files owned by wrong user / permission denied
- Set
PUIDandPGIDin.envto the UID/GID of the host user who should own the files (idwill show your current values). - For rootless Docker, set both to
0.
Sync stops after a while
- The
restart: unless-stoppedpolicy indocker-compose.ymlwill restart the container automatically.
Token expired / login required
- Re-run the
get-tokenstep, updateOBSIDIAN_AUTH_TOKENin.env, and restart:docker compose up -d
MIT