Deploying

~5 min read

Deploying

Three supported targets, plus “anywhere you can host static files”.

The simplest path — and on the Mac it’s guided: the inspector’s Publish Site tab is a four-step checklist (account → repository → content → deploy) that shows live where you stand. Connect GitHub, type ANY owner/repo name — it doesn’t have to exist; the first deploy creates it (public — free Pages needs that) and enables Pages — publish at least one note or artifact, and deploy. Tick “Deploy automatically when published notes change” and that first deploy is the last one you trigger by hand.

From the CLI, pre-requisites:

One command does it all — build, push, enable Pages, print the live URL:

lettuce deploy --repo DrBaher/my-notes --token "$GITHUB_TOKEN"

Useful flags: --branch (default gh-pages), --message (commit message, default Publish from Lettuce), --private (create the repo private rather than public), --all (include unpublished notes), and --contact (owner email for the footer). The vault is the first positional argument (defaults to the current directory).

Within ~30 seconds GitHub Pages serves it at https://<user>.github.io/<repo>/. The command prints the live URL when it finishes.

Custom domain

Pass the absolute URL with --base-url so OpenGraph tags and the sitemap point at your domain:

lettuce deploy --repo DrBaher/my-notes --token "$GITHUB_TOKEN" \
  --base-url "https://notes.baher.com/"

Then set your domain’s DNS to CNAME → <user>.github.io and enable HTTPS in the GitHub repo settings. (Lettuce does not manage DNS or write a CNAME file — configure the custom domain in the GitHub repo’s Pages settings.)

Vercel

Vercel is what powers https://lettuce.drbaher.com. Vercel auto-deploys from a git repo, so the flow is “build locally, push to git, Vercel picks it up”:

  1. Make a Vercel project pointing at your vault’s repo.

  2. Set its Root Directory to _site/ (the default lettuce publish output, so Vercel ignores your sources).

  3. Set the Build Command to empty and Output Directory to . (Vercel serves the static files as-is).

  4. Run lettuce publish before each commit (or use a git pre-commit hook).

For per-commit auto-publish, add to your repo root:

# .git/hooks/pre-commit
#!/usr/bin/env bash
lettuce publish
git add _site/

S3 + CloudFront (or any static host)

lettuce publish
aws s3 sync _site/ s3://my-bucket/ --delete --cache-control "max-age=300"
aws cloudfront create-invalidation --distribution-id E123 --paths "/*"

Lettuce doesn’t ship S3 integration directly — but lettuce publish produces a plain directory you can sync anywhere.

Self-hosting

Just rsync _site/ to a server with any HTTP daemon (nginx, caddy, Apache).

Caddy is the easiest:

notes.baher.com {
    root * /var/www/notes
    file_server
    try_files {path} {path}/ /404.html
}

What gets pushed

The lettuce deploy command:

  1. Builds the site into a temporary directory (the same build as lettuce publish).

  2. With --repo + a token: creates the GitHub repo if needed, force-pushes the built tree to the target branch, enables Pages, and prints the live URL.

  3. With --remote URL instead: force-pushes the built tree to that bare git remote on the target branch.

Your source vault is not pushed — only the built site.

Continuous deploy

lettuce deploy is a one-shot command, so the simplest “every night” setup is your operating system’s own scheduler — a cron entry or a launchd job — that runs the command on a timer:

# crontab: rebuild and deploy at 04:00 daily
0 4 * * * cd /path/to/vault && lettuce deploy --repo DrBaher/my-notes --token "$GITHUB_TOKEN"

Lettuce’s own .lettuce/schedules.yml is for agent work, not shell commands: each rule fires a maintenance action: (such as maintain or lint-semantic) or splices a > [!ai] <prompt> directive into a target note for a connected MCP agent to pick up — it cannot run lettuce publish or lettuce deploy itself. Use the OS scheduler above for unattended deploys.

Deploy automatically

Turn on “Deploy automatically when published notes change” in the publish sheet and Lettuce watches your published surface — the notes and HTML artifacts that are on the site, plus anything newly marked published — and pushes on its own: a few minutes after you stop editing (so a writing session becomes one deploy, not twenty), never more than every 15 minutes, and only when something on the site actually changed. Unpublishing or deleting a published note also triggers a deploy, so pages come down automatically too.

It runs from the Mac that holds your deploy token (the app, or its always-on background server — whichever is up; they coordinate so nothing is pushed twice). A failed push (offline, revoked token) is retried on the next cycle. Combined with the artifact recipe, “make this dashboard public” becomes: Claude writes the tagged artifact → Lettuce notices → your GitHub Pages site updates — no manual step anywhere.