← Blog

Obsidian to VPS Pipeline: Sync, Pull, and Redeploy

This is the full pipeline to publish posts from Obsidian without opening VS Code and without manually logging into the server for each deploy.

Goal

You write posts locally in Obsidian.
One local script should then:

  1. update the repository on the VPS (git pull --ff-only)
  2. sync your Markdown posts from macOS to the VPS (rsync)
  3. rebuild and restart the Astro container (podman-compose)

Local source folder

/Users/adrian/Library/Mobile Documents/iCloud~md~obsidian/Documents/Web/adrian-altner-com/posts/

VPS target folder

/opt/websites/www.adrian-altner.de/src/content/posts

Prerequisites

1) SSH config alias

Use an alias in ~/.ssh/config so you can run ssh hetzner:

Host hetzner
  HostName <your-vps-ip-or-host>
  User root
  IdentityFile ~/.ssh/<your-key>

2) Tools available

  • macOS: rsync, ssh
  • VPS: git, podman, podman-compose

Optional: keep one tracked seed post in Git

If you want only hello-world.md tracked in Git while all other posts stay ignored:

src/content/posts/*
!src/content/posts/hello-world.md

Then commit hello-world.md once:

git add src/content/posts/hello-world.md
git commit -m "add hello-world seed post"
git push

One-command publish script (macOS)

Save as ~/bin/publish-posts.sh:

#!/usr/bin/env bash
set -euo pipefail

SRC='/Users/adrian/Library/Mobile Documents/iCloud~md~obsidian/Documents/Web/adrian-altner-com/posts/'
VPS="${1:-hetzner}"
REMOTE_BASE='/opt/websites/www.adrian-altner.de'
REMOTE_POSTS="${REMOTE_BASE}/src/content/posts"

# 1) Update code on VPS
ssh "$VPS" "cd '$REMOTE_BASE' && git pull --ff-only"

# 2) Sync posts from Obsidian to VPS
# The explicit exclude keeps a Git-tracked seed file safe from --delete.
ssh "$VPS" "mkdir -p '$REMOTE_POSTS'"
rsync -az --delete \
  --exclude='hello-world.md' \
  --include='*.md' --exclude='*' \
  "$SRC" "$VPS:$REMOTE_POSTS/"

# 3) Rebuild and replace running container
ssh "$VPS" "cd '$REMOTE_BASE' && podman-compose -f compose.yml up --build -d --force-recreate"

echo "Repo pulled + posts synced + redeploy done via $VPS."

Make it executable:

chmod +x ~/bin/publish-posts.sh

Run it:

~/bin/publish-posts.sh

Verification checklist

ssh hetzner "podman ps --filter name=www.adrian-altner.de"
ssh hetzner "podman logs --tail 100 www.adrian-altner.de"

Open the new post URL in the browser and confirm:

  • route resolves
  • title and publish date render correctly
  • no container startup errors

Troubleshooting

Container name conflict

If you get “name is already in use”:

ssh hetzner "cd /opt/websites/www.adrian-altner.de && podman-compose -f compose.yml up --build -d --force-recreate"

or, if needed:

ssh hetzner "podman rm -f www.adrian-altner.de"

git pull blocked by local changes on VPS

Check state:

ssh hetzner "cd /opt/websites/www.adrian-altner.de && git status"

Keep the VPS working tree clean for predictable deploys.

Why this pipeline works well

  • no manual SSH session for daily publishing
  • no Markdown commit required for every post
  • deterministic redeploy path with one command
  • easy to automate further later (cron, launchd, CI)
← Blog