Skip to content

Repository files navigation

obsidian-sync-git — every note, every version

CI Latest release Container image on GHCR License: MIT OR Apache-2.0

Automatically keep a full, versioned copy of your Obsidian vault in a GitHub repository.

 your devices  ⇄  Obsidian Sync  ⇄  [ this bridge ]  ⇄  GitHub
 ────────────     ─────────────     ───────────────     ──────
 Obsidian on      Obsidian's        Docker, on a        your private
 phone, laptop    servers           computer that       repo — every
 and tablet                         stays on            version, forever

A small Docker container checks Obsidian Sync every 15 minutes, commits whatever changed to GitHub, and sends anything merged on GitHub back out to your devices.

  • Backup and history — every version of every note, browsable on GitHub.
  • Safe automation — tools like Claude Code can propose note edits as pull requests; nothing reaches your vault until you merge.

No git knowledge needed. Setup is about 15 minutes of copy-paste.

What you'll need

  • A computer that stays on (home server, NAS, Raspberry Pi, …) with Docker (Docker Compose is included)
  • An Obsidian account with a Sync subscription
  • A GitHub account

Setup

1. Create an empty GitHub repository

Private is recommended — these are your notes. Leave it completely empty (no README, no .gitignore, no license) or the first sync will fail. Note its SSH address: git@github.com:YOUR-USERNAME/YOUR-REPO.git.

2. Get your Obsidian token

The bridge signs in with a token instead of your password. This one-off command logs in and prints it — nothing to install:

docker run -it --rm --entrypoint bash ghcr.io/puradox/obsidian-sync-git:latest \
  -c 'ob login && ob sync-list-remote && cat "${XDG_CONFIG_HOME:-$HOME/.config}/obsidian-headless/auth_token" && echo'

Enter your Obsidian email, password, and 2FA code if you use one. It prints your vault names and your token — copy the exact vault name and the token for step 4. (If Docker says the pull was denied, see Troubleshooting.)

3. Create a deploy key

This key lets the bridge write to your repository — and nothing else. Make a folder for the bridge and create the key inside it:

mkdir obsidian-bridge && cd obsidian-bridge
ssh-keygen -t ed25519 -N '' -C 'obsidian-bridge' -f ./deploy_key

Copy the output of cat deploy_key.pub into GitHub → your repository → Settings → Deploy keys → Add deploy key, and tick Allow write access.

4. Create the Compose file

Save this as docker-compose.yml in the same folder, and fill in the three values at the top. The optional settings are listed below them, commented out — uncomment the ones you want and delete the rest.

services:
  obsidian-bridge:
    image: ghcr.io/puradox/obsidian-sync-git:latest
    restart: unless-stopped
    volumes:
      - ./deploy_key:/keys/deploy_key:ro
      - vault:/vault
      - config:/config
    environment:
      # ---- fill in these three ----
      VAULT_NAME: "My Vault"
      GIT_REMOTE_URL: "git@github.com:YOUR-USERNAME/YOUR-REPO.git"
      OBSIDIAN_AUTH_TOKEN: "paste-the-token-from-step-2"

      # Where the deploy key is mounted (matches the volumes line above).
      GIT_DEPLOY_KEY_FILE: "/keys/deploy_key"

      # ---- optional: uncomment what you need, delete the rest ----
      # Vault encryption password — only for end-to-end encrypted vaults.
      # OBSIDIAN_VAULT_PASSWORD: "your-encryption-password"

      # How often to sync, in cron syntax (https://crontab.guru).
      # The default means every 15 minutes.
      # CRON_SCHEDULE: "*/15 * * * *"

      # Name and email shown on the commits.
      # GIT_AUTHOR_NAME: "Obsidian Bridge"
      # GIT_AUTHOR_EMAIL: "obsidian-bridge@localhost"

      # Keep the vault in a subfolder of the repository — see
      # "Keeping the vault in a subfolder of the repo" in the README.
      # VAULT_SUBDIR: "vault"

      # Deploy key for a folder shared as a git submodule (one per submodule,
      # named after it) — see "Sharing a folder as a git submodule".
      # GIT_SUBMODULE_DEPLOY_KEY_FILE_VAULT_COVE_QMS: "/keys/cove_qms_key"

      # AI commit messages, option A: any OpenAI-compatible provider
      # (Groq, Gemini, Ollama, …) — see "AI commit messages" in the README.
      # LLM_API_BASE: "https://api.groq.com/openai/v1"
      # LLM_MODEL: "llama-3.1-8b-instant"
      # LLM_API_KEY: "your-api-key"
      # LLM_MAX_TOKENS: "4096"

      # AI commit messages, option B: Anthropic (used only if option A is unset).
      # ANTHROPIC_API_KEY: "your-api-key"
      # ANTHROPIC_MODEL: "claude-haiku-4-5"

      # Report every cycle to an uptime monitor, which tells you when syncing
      # breaks — or stops entirely. See "Monitoring with Uptime Kuma".
      # HEARTBEAT_URL: "https://kuma.example.com/api/push/AbCdEf1234"

      # Seconds without a successful sync before Docker reports "unhealthy".
      # Set to ~2x your interval if CRON_SCHEDULE isn't every-N-minutes.
      # HEALTH_STALE_SECONDS: "1800"

      # Sync once immediately when the container starts.
      # RUN_ON_START: "true"

      # Kill a single sync that stalls this long (seconds) and retry next tick —
      # guards against the sync engine hanging on a half-open connection. Raise
      # only for very large vaults — and then also extend the healthcheck
      # start_period so a long first sync isn't flagged unhealthy.
      # OB_SYNC_TIMEOUT: "300"

      # Only if your vault uses a non-default config folder.
      # OBSIDIAN_CONFIG_DIR: ".obsidian"

      # Device name shown in Obsidian Sync's version history.
      # OBSIDIAN_DEVICE_NAME: "git-bridge"

volumes:
  vault:
  config:

Vault end-to-end encrypted? Uncomment OBSIDIAN_VAULT_PASSWORD. Not sure? Skip it — the bridge will tell you at startup if it's needed.

This file now contains your token, so keep it private and don't commit it to version control. To keep secrets out of the file entirely, see Security: Docker secrets.

5. Start the bridge

docker compose up -d

It runs in the background, restarts after reboots, and keeps your vault in Docker volumes so nothing is lost across updates.

6. Check that it's working

docker compose logs -f

Within a minute or two you should see cycle complete (press Ctrl+C to stop watching). Open your GitHub repository — your notes are there.

Done. It now syncs on its own every 15 minutes. Something wrong? See Troubleshooting.

Good to know

  • One rule: only the bridge may write to its vault volume. Make changes in Obsidian, or on GitHub via pull requests — nowhere else.
  • Clones and vaults stay separate. If you also clone the repository to work on it, never open that clone in Obsidian — why.
  • Conflicts are safe. If a merged pull request clashes with a note you edited, your vault wins automatically, sync keeps going, nothing is lost, and the logs alert you. Details: How syncing works.
  • You don't have to watch the logs. Point the bridge at an uptime monitor and it reports every cycle, so a broken — or stopped — sync finds you: Monitoring with Uptime Kuma.
  • Changing a setting: edit docker-compose.yml, then run docker compose up -d again.
  • Updating: docker compose pull && docker compose up -d.

All settings

Everything is configured in docker-compose.yml (step 4).

Setting Required Default What it does
VAULT_NAME Your vault's exact name from step 2. Fixed once the bridge has run.
GIT_REMOTE_URL Your repository's SSH address: git@github.com:owner/repo.git.
OBSIDIAN_AUTH_TOKEN The Obsidian token from step 2.
GIT_DEPLOY_KEY_FILE Path of the mounted deploy key (/keys/deploy_key in the example).
OBSIDIAN_VAULT_PASSWORD E2EE vaults only The vault's encryption password. Ignored for normal vaults.
CRON_SCHEDULE */15 * * * * How often to sync, in cron syntax — the default means every 15 minutes.
GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL Obsidian Bridge / obsidian-bridge@localhost Name and email shown on the commits.
VAULT_SUBDIR — (vault = repo root) Keep the vault in a subfolder of the repository (details). Fixed once the bridge has run.
GIT_SUBMODULE_DEPLOY_KEY_FILE_<NAME> per submodule Deploy key for a folder shared as a git submodule; <NAME> is derived from .gitmodules (details).
LLM_API_BASE / LLM_MODEL / LLM_API_KEY AI commit messages via an OpenAI-compatible provider (details).
LLM_MAX_TOKENS 4096 Completion budget for that provider. On reasoning models this also covers the hidden "thinking" tokens — raise it if commits fall back to the plain label.
ANTHROPIC_API_KEY AI commit messages via Anthropic instead (details).
ANTHROPIC_MODEL claude-haiku-4-5 Model for the Anthropic option.
HEARTBEAT_URL Report each cycle to an uptime monitor's push URL (details). Its token is in the URL, so it's a secret and is never logged.
HEARTBEAT_TIMEOUT 10 Seconds to wait for that monitor. A monitor that is slow or down never affects the sync cycle.
HEALTH_STALE_SECONDS 2× the sync interval Seconds without a successful sync before Docker reports unhealthy. Set it (≈2× your interval) if CRON_SCHEDULE isn't every-N-minutes.
RUN_ON_START true Sync once immediately when the container starts.
OB_SYNC_TIMEOUT 300 Seconds a single ob sync may run before it's killed and retried next tick. Guards against the sync engine stalling on a half-open connection. Raise it only for very large vaults.
BRIDGE_CYCLE_TIMEOUT 2×OB_SYNC_TIMEOUT + 300 Outer cap, in seconds, on a whole sync cycle. Safety net above OB_SYNC_TIMEOUT; keep it comfortably larger. If you raise either timeout, also extend the healthcheck start_period (16m by default) in compose so a long first sync isn't flagged unhealthy.
OBSIDIAN_CONFIG_DIR .obsidian Only if your vault uses a different config folder.
OBSIDIAN_DEVICE_NAME Device name shown in Obsidian Sync's version history.

The secret settings — OBSIDIAN_AUTH_TOKEN, OBSIDIAN_VAULT_PASSWORD, LLM_API_KEY, ANTHROPIC_API_KEY, HEARTBEAT_URL — each also accept a _FILE variant that reads the value from a file instead: see Security: Docker secrets. (GIT_DEPLOY_KEY and GIT_SUBMODULE_DEPLOY_KEY_<NAME> with the key's contents also exist, but mounting the file is safer.)

Volumes

The bridge keeps its data in two named Docker volumes, so nothing is lost when you update or recreate the container:

Volume Holds
vault Your notes, as a git repository. Only the bridge writes here.
config The bridge's Obsidian login and sync state.

Security: Docker secrets

The compose file from step 4 holds your secrets in plain text. On a machine only you use, that's acceptable — but anything that can talk to Docker can read environment variables with docker inspect, and a compose file full of secrets is easy to leak by accident.

Docker secrets keep the values in separate files, out of the compose file and out of docker inspect. Every secret setting accepts a _FILE variant that reads its value from a file — point those at /run/secrets/…:

services:
  obsidian-bridge:
    image: ghcr.io/puradox/obsidian-sync-git:latest
    restart: unless-stopped
    environment:
      VAULT_NAME: "My Vault"
      GIT_REMOTE_URL: "git@github.com:YOUR-USERNAME/YOUR-REPO.git"
      OBSIDIAN_AUTH_TOKEN_FILE: "/run/secrets/obsidian_auth_token"
      GIT_DEPLOY_KEY_FILE: "/run/secrets/deploy_key"
    secrets:
      - obsidian_auth_token
      - deploy_key
    volumes:
      - vault:/vault
      - config:/config

secrets:
  obsidian_auth_token:
    file: ./obsidian_auth_token.txt   # a file containing just the token
  deploy_key:
    file: ./deploy_key

volumes:
  vault:
  config:

The deploy key becomes a secret too, replacing the ./deploy_key:/keys/… mount from step 4. The same pattern works for OBSIDIAN_VAULT_PASSWORD_FILE, LLM_API_KEY_FILE, ANTHROPIC_API_KEY_FILE, and HEARTBEAT_URL_FILE.

To rotate a secret, update the file (for the deploy key, also swap the public key on GitHub), then run docker compose restart. Secrets are never logged.

Monitoring with Uptime Kuma

Problems show up in docker compose logs, which means you find them when you go looking. Point the bridge at an uptime monitor and it tells you instead.

In Uptime Kuma: Add New Monitor → Monitor Type: Push, copy the Push URL it shows you, and attach your Pushover (or any other) notification to it. Then:

      HEARTBEAT_URL: "https://kuma.example.com/api/push/AbCdEf1234"

That URL contains the monitor's token, so it's a secret: it also takes the _FILE variant from Docker secrets. The bridge never writes it to the logs — not in an error, not from a response body — and never follows a redirect away from it, which would hand the token to whichever host the redirect names. Prefer https; over plain http the token crosses the network in the clear, and the bridge says so at startup.

The notification names the actual problem

The bridge pushes up after a completed cycle and down after a failed one — and sends the error along with it, so the alert says what broke rather than just turning red:

[Obsidian Bridge] [🔴 Down] ob sync (device pull) timed out after 300s (stalled sync-server connection?) — killed; deferring to next tick

Cycle outcome Reported The message says
Completed up cycle complete, plus what was committed
Conflict auto-resolved (your vault won) up which note overrode a merged change — nothing is broken, but you may want to re-apply it upstream
Submodule with no deploy key up that it syncs to your devices but isn't being pushed
Network or sync-engine failure down the step that failed and its error
A conflict that needs you down what couldn't be auto-resolved
Killed by BRIDGE_CYCLE_TIMEOUT, or the container stops nothing — the monitor notices the missing beat, which is what a heartbeat is for
Tick skipped (previous cycle still running) nothing — the cycle that IS running reports for itself

The cycle's duration is sent as the monitor's "ping" value, so Kuma graphs how long syncing takes. That's a useful early warning well before anything fails.

The monitor decides what's worth waking you for

The bridge deliberately has no notification settings of its own — Kuma already does that job, and does it in a UI:

  • Retries is your failure threshold. 0 pages you on the very first failed cycle; 2 waits for three in a row. A single failure is often a network blip the next tick fixes, so 1 or 2 is usually the kinder setting.
  • Resend Interval decides whether a problem that stays broken keeps reminding you. Kuma only notifies on changes (up→down, down→up), so without this you get exactly one notification per outage.
  • Heartbeat Interval should be comfortably longer than CRON_SCHEDULE, or a healthy-but-idle bridge looks overdue. Roughly 2× your sync interval.

It also catches the bridge not running at all

This is the real gain over reading logs. If the container is stopped, the Docker daemon is down, or the machine is off, the pushes simply stop and Kuma alerts on the silence — something nothing inside the container could ever report.

One limit worth knowing: if Kuma runs on the same server as the bridge, it can't tell you that server is off, because it's off too. Closing that last gap needs the monitor to live somewhere else.

Alternative: the Docker Container monitor

Kuma can instead watch the container's own healthcheck, which is green only if a cycle succeeded within HEALTH_STALE_SECONDS — no HEARTBEAT_URL needed. The trade-offs: Kuma needs Docker socket access (effectively root on the host), it takes up to ~45 minutes to notice with the default settings, and it can't tell you why. Worth it if you'd rather not give the bridge an outbound URL.

AI commit messages

Out of the box, each commit is labeled vault auto-commit: N files changed (<timestamp>). Give the bridge an AI provider and it writes a short summary of what actually changed instead. In docker-compose.yml, uncomment one of the two options:

Option A — any OpenAI-compatible provider (LLM_API_BASE + LLM_MODEL + LLM_API_KEY):

Provider LLM_API_BASE LLM_MODEL (example)
Groq https://api.groq.com/openai/v1 llama-3.1-8b-instant
Gemini https://generativelanguage.googleapis.com/v1beta/openai gemini-3.1-flash-lite
Ollama (local) http://host.docker.internal:11434/v1 qwen2.5:3b

Option B — Anthropic (ANTHROPIC_API_KEY; used only when option A isn't set).

Good to know:

  • Privacy: the diff of your notes is sent to the provider, and free tiers commonly use submitted data to improve their models. For fully private summaries, run a local Ollama — the diff never leaves your machine.
  • It can never break a sync. If the provider is down, slow, or misconfigured, the bridge just falls back to the plain label.
  • Reasoning models (gpt-oss, DeepSeek-R1, …) spend part of LLM_MAX_TOKENS on hidden "thinking" tokens before the message itself. If commits come out as the plain label while your provider dashboard shows token use, raise LLM_MAX_TOKENS (or pick a non-reasoning model).
  • LLM_API_BASE must start with http:// or https://.

Keeping the vault in a subfolder of the repo

By default your vault is the repository: every file in it syncs to your devices. If you want the repository to also hold things that should not show up in Obsidian — a Quartz site that publishes the vault, Claude Code skills, build scripts — set VAULT_SUBDIR:

      VAULT_SUBDIR: "vault"

giving a layout like:

your-repo/
├── vault/            ← synced with your Obsidian devices
├── quartz/           ← on GitHub, invisible to Obsidian
├── .claude/skills/   ← on GitHub, invisible to Obsidian
└── README.md
  • Only files inside vault/ reach your devices; the bridge still commits and pushes the whole repository.
  • Work on the other folders in a separate clone of the repository — never inside the bridge's volume, and never in a clone you've opened in Obsidian (why) — and merge changes through GitHub.
  • Decide up front: like VAULT_NAME, this can't be changed once the bridge has run — you'd have to start over with fresh volumes.

Sharing a folder as a git submodule

One folder of your vault can be its own git repository — a submodule — so you can share just that folder with someone who isn't on Obsidian Sync. They work in a clone of the folder's repository; you keep editing the notes in Obsidian; the bridge keeps the two in step with the same vault-wins rules as the rest of the vault. This works with or without VAULT_SUBDIR.

1. Add the submodule in a separate clone of your repository (never inside the bridge's volume) and merge it through GitHub. The folder's repository may already contain notes, or be empty:

git submodule add git@github.com:coveqms/notes.git "vault/Cove QMS"
git commit -m "Share Cove QMS as a submodule" && git push

2. Give the bridge a deploy key for the folder's repository. A GitHub deploy key only ever opens one repository, so each submodule needs its own, passed in a setting named after the submodule:

  • Take the submodule's name from .gitmodules — the [submodule "…"] header, which is the path you added it at unless you chose otherwise: vault/Cove QMS.
  • Upper-case it and replace every character that isn't A–Z or 0–9 with _: VAULT_COVE_QMS.
  • That gives GIT_SUBMODULE_DEPLOY_KEY_FILE_VAULT_COVE_QMS. Create a key as in step 3, add it to the folder's repository with Allow write access, and mount it:
    volumes:
      - ./cove_qms_key:/keys/cove_qms_key:ro
    environment:
      GIT_SUBMODULE_DEPLOY_KEY_FILE_VAULT_COVE_QMS: "/keys/cove_qms_key"

Restart the bridge (docker compose up -d). If a key is missing, every cycle logs an ALERT naming the exact setting it expects. The _FILE variant works as a Docker secret like the others; GIT_SUBMODULE_DEPLOY_KEY_<NAME> with the key's contents also exists.

Good to know:

  • The folder's repository is pushed first. Each cycle commits, rebases and pushes every submodule in its own repository before the outer one, so your main repository never points at a commit the folder's repository doesn't have. A submodule problem (network, a clash it can't resolve) is reported like any other failed cycle, but the rest of the vault still syncs.
  • Your vault still wins. Your collaborator's changes are pulled into the folder on every cycle; if one clashes with a note you edited, your note wins and an ALERT names the file, exactly as for pull requests. If the folder already had notes on your devices when you added the submodule, the two are merged the same way, your notes winning any clash; an empty folder simply adopts the repository's content.
  • The pointer follows your notes. The commit your main repository records for the folder always tracks the bridge's checkout. If a merged pull request moves that pointer ahead, the folder fast-forwards to it; if the pointer and your notes have diverged, your notes are re-applied on top (vault wins, with an ALERT); it is never moved backwards.
  • Without a key the folder still syncs to your devices and is committed locally, but nothing is pushed to its repository, the pointer stays where it was, and an ALERT is logged each cycle. The cycle still succeeds.
  • Only folders inside the vault are handled; submodules elsewhere in the repository are ignored. The branch is main unless .gitmodules sets branch = … (branch = . also means main). Only git@host:… and ssh:// URLs can be routed to a key — an https:// submodule is treated like one without a key (committed locally, alerted, never pushed), so use the SSH address — and, as for the main repository, only github.com's host key is pinned.
  • .gitmodules stays canonical. Inside the container, each key is routed through an SSH host alias (bridge-submodule-…) set on the folder's own origin remote; other clones see the ordinary URL.
  • Removing the submodule (a merged pull request that drops it) detaches the folder on the bridge: its notes stay in your vault as plain files and nothing is deleted from your devices.
  • The folder's .git never reaches your devices. Obsidian Sync skips every file and folder whose name starts with a dot (apart from .obsidian), so the small .git file the bridge keeps in the folder stays on the bridge. Verified in the sync engine the bridge runs (obsidian-headless 0.0.12): the local scan skips dot-entries at every depth, and the file filter rejects dot-paths before the "sync all other file types" setting is consulted.
  • Your collaborator should treat their clone like any git clone of a vault — don't open it in Obsidian (why).

Protecting main on GitHub

If automation (or other people) opens pull requests against your vault, enable branch protection on main with "Require branches to be up to date before merging." It forces every pull request to be updated with your latest notes before it can merge, so conflicts surface on GitHub — where you can see and fix them — instead of inside the bridge afterwards.

⚠️ Don't enable "Require a pull request before merging" or any other rule that blocks direct pushes to main — the bridge pushes to main directly, so every sync would be rejected. If you need that rule, use a repository ruleset instead and add the bridge's deploy key to its bypass list.

How syncing works

Every 15 minutes (or your CRON_SCHEDULE) the bridge runs one cycle:

  1. Pull note changes from your devices (Obsidian Sync).
  2. Commit them.
  3. Pull anything merged on GitHub.
  4. Push everything to GitHub.
  5. Send the merged changes back out to your devices (Obsidian Sync).

Two rules keep this safe:

  • Your vault always wins. Edits made in Obsidian take priority over anything merged on GitHub — including when they conflict.
  • Conflicts resolve automatically, in your vault's favour. If a merged pull request clashes with a note you edited, the bridge keeps your note's version of the conflicting lines, still merges in every non-conflicting change from the pull request, pushes the result, and prints an ALERT naming the files so you can see what happened. The pull request's overridden version stays in the repository's history, so nothing is lost — re-apply it with a fresh pull request if you actually wanted it. Sync never halts waiting for you. (The rare conflict that can't be resolved even in your favour — e.g. a note you edited was deleted on GitHub — still stops and alerts for you to sort out upstream.)

The first sync. If the GitHub repository is empty, the first cycle fills it from your vault. If your vault is empty and the repository already has notes, the bridge adopts the repository and syncs it out to your devices. If both sides already have content, they're merged with your vault winning any clash (and an ALERT logged) — so still start from an empty repository unless you specifically want your vault to override the repository's notes.

Don't open a git clone as an Obsidian vault

If you clone the repository to work on it, keep that clone out of Obsidian. A folder that both Obsidian Sync and git write to always looks dirty: git status lists notes you never edited, and git pull refuses with "Your local changes to the following files would be overwritten by merge."

Use two folders — a plain vault folder for Obsidian, a clone for git — and the problem disappears. It costs one extra copy of your notes on disk, nothing else.

Not everything in git reaches your devices

Obsidian Sync carries Markdown plus common images, audio and video. Other extensions — .txt, .json, … — sync only when "Sync all other file types" is enabled in Obsidian's Settings → Sync; files and folders whose name starts with a dot (.gitignore, .git) never sync at all, apart from .obsidian; and every file has a maximum size that depends on your plan.

Git has neither limit, so a file added by a pull request can sit in your repository and never reach a single device. The bridge can't warn you — from its side the sync succeeded. To list what's affected:

comm -23 <(cd ~/code/my-vault/vault && find . -type f | sort) \
         <(cd ~/Obsidian && find . -type f | sort)

Troubleshooting

Watch what's happening with docker compose logs -f; docker compose ps shows the health status.

Symptom Fix
denied when pulling the image The image package may be private. Sign in with docker login ghcr.io (username + a GitHub token with read:packages).
Startup error "vault '…' not found" VAULT_NAME doesn't exactly match — use the vault name printed in step 2.
Startup error about end-to-end encryption or a rejected password Set OBSIDIAN_VAULT_PASSWORD to your vault's correct encryption password.
Startup error "No SSH deploy key provided" The key isn't mounted — check the ./deploy_key:/keys/deploy_key:ro line and GIT_DEPLOY_KEY_FILE.
Permission denied (publickey) when pushing The deploy key wasn't added to the repository, or Allow write access wasn't ticked.
Permission denied (publickey) running git yourself in docker compose exec The deploy key is now wired into the repo's git config, so plain git -C /vault fetch/push works. On an older image, first run export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/id_deploy -o IdentitiesOnly=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts" in the shell.
dubious ownership in repository Only with bind mounts: chown -R 1000:1000 the folder (the bridge runs as user 1000). Named volumes (the default) are unaffected.
ALERT: rebase conflict … auto-resolved in favour of the vault Normal and handled — a merged pull request clashed with a note edit, so your note won and sync carried on; the PR's version is still in git history. See How syncing works.
ALERT: rebase conflict … could NOT be auto-resolved (or it repeats every cycle) A structural conflict the bridge won't auto-resolve (e.g. modify-vs-delete), or — on the very first sync — the repository wasn't empty. Resolve it on GitHub.
ALERT: submodule … no deploy key A folder shared as a submodule has no key, so it isn't being pushed. Set the setting the alert names — see Sharing a folder as a git submodule.
ALERT: submodule … listed in .gitmodules but … is not a submodule pointer .gitmodules was edited by hand. Add the folder with git submodule add in a clone and merge that.
ALERT: submodule … origin/main moved its pointer A merged pull request pointed the folder at commits your notes didn't have; they were combined with your notes winning. Normal and handled.
heartbeat rejected ... (HTTP 404 ...) The push URL is wrong, or its monitor was deleted or paused — a paused monitor looks identical to a healthy quiet one.
heartbeat to ... failed The bridge can't reach your monitor. Syncing is unaffected; only the reporting is.
Uptime Kuma flaps between up and down Its Heartbeat Interval is too close to CRON_SCHEDULE — a healthy bridge is idle between cycles. Use roughly 2× your sync interval.
Container shows as unhealthy No sync succeeded recently — check docker compose logs for the error.
A file is on GitHub but never appears in Obsidian Sync doesn't carry every file type or size — see Not everything in git reaches your devices.
git pull in your own clone says "local changes would be overwritten" You've opened that clone in Obsidian, so Sync and git are both writing it — see Don't open a git clone as an Obsidian vault.

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

Automatically back up your Obsidian vault to GitHub, and sync changes merged on GitHub back to your devices.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages