# Deploy on push

Connect a repository and Readsmith becomes a deployment pipeline: a push to the
connected branch fetches that exact commit, compiles it into an immutable
bundle, and atomically flips the site to it. Nothing is half-published, a stale
build can never overwrite a newer one, and rolling back is repointing to a
previous deployment, not rebuilding anything.

Git deploys need the database (deployments live there), so this page assumes
the [Docker Compose deployment](/docs/self-host/deploy) or an equivalent with
`DATABASE_URL` set.

## Two ways to authenticate

The two-variable path. Create a
[fine-grained personal access token](https://github.com/settings/personal-access-tokens/new)
scoped to the one docs repository with read-only **Contents** permission,
then set:

```bash
GITHUB_PAT=github_pat_...
GITHUB_REPO=owner/docs-repo
```

Restart, and the first build enqueues automatically. Give the token an
expiry and rotate it like any credential; it lives only in your
environment, never in the database.

An App you own, with short-lived per-repository tokens and the webhook
managed by GitHub. Visit `/_readsmith/setup/github` on your instance: one
click creates the App with read-only `contents` + `metadata` permissions,
the `push` event, and your webhook URL already configured, then shows you
the three variables to set:

```bash
GITHUB_APP_ID=...
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..."
GITHUB_WEBHOOK_SECRET=...
GITHUB_REPO=owner/docs-repo
```

Install the App on the docs repository, restart, and you are connected.
The setup pages disappear as soon as credentials are configured.

Create a GitHub App under **Settings → Developer settings** with:
repository permissions **Contents: read-only** and **Metadata: read-only**;
the **Push** event subscribed; the webhook URL set to
`https://your-docs-host/_readsmith/api/git/webhook` with a secret you
generate. Save the App id, generate a private key, and land all three in
the environment variables above.

Configure exactly one mode. Setting both the App pair and a PAT fails fast at
boot with a message naming the conflict.

## How updates arrive

With an App, GitHub delivers pushes automatically. In PAT mode, add a plain
repository webhook pointing at
`https://your-docs-host/_readsmith/api/git/webhook` with content type
`application/json` and the secret you set in `GITHUB_WEBHOOK_SECRET`.
Every delivery is verified against that secret before anything is parsed.

If GitHub cannot reach your instance at all, set
`GITHUB_POLL_INTERVAL=120` (seconds). Each tick sweeps every connected
site with one API call per connection, comparing the branch head to the
last built commit, and builds on change. Polling and webhooks can coexist
safely; duplicate triggers deduplicate.

Pushes to other branches and repositories are ignored. Set `GITHUB_BRANCH` to
build something other than the repository's default branch.

Since 0.6, both paths route by repository: a push (or poll tick) triggers a
build for **every** site connected to that repository and branch. A
single-site install behaves exactly as before; installs serving several
sites from one repository fan out automatically.

## Deployments, atomicity, and rollback

Every build is an immutable, content-addressed snapshot. Publishing means
flipping a pointer once the artifact is fully verified, so readers only ever
see the old site or the whole new one. If two pushes race, the newer commit
wins even when its build finishes first; the stale build is recorded as
superseded and never served.

```bash
# Build and publish right now (branch head, a named branch, or an exact commit)
pnpm site:build
pnpm site:build main
pnpm site:build 3f9c2e1a...

# The deployment history, newest first (* marks what is serving)
pnpm site:rollback --list

# Repoint to any prior ready deployment: instant, no rebuild
pnpm site:rollback dep:default:41
```

A running server reflects a publish or rollback within about a minute (pages
regenerate on a short cadence); a restart reflects it immediately. Search and
Ask-AI reindex automatically after every change of what is being served.

Rebuilds are incremental: a persisted render cache means a one-page edit
re-renders one page, and a shared-snippet edit re-renders exactly the pages
that embed it. History keeps the last 20 non-current snapshots by default
(`READSMITH_KEEP_DEPLOYMENTS`); older ones are pruned after each publish, and
the currently-served deployment is never touched.

By default a page with a broken component or bad syntax builds anyway, with the
problem reported as a diagnostic: one bad page should not take your docs down.
Set `READSMITH_FAIL_ON_ERROR=true` to invert that for CI-style strictness: any
page error fails the whole build, and the previous deployment keeps serving.

## What the build will not do

The build fetches one tarball of your repository over HTTPS from GitHub, with
hard limits on size and file count, and compiles it. It runs no code from the
repository, materializes no symlinks, follows no submodules, and writes nothing
outside its scratch directory, which is deleted after every build, success or
failure. Tokens are held in memory and never logged.
