> ## Documentation Index
> Fetch the complete documentation index at: https://margin.manicule.dev/llms.txt
> Use this file to discover all available pages before exploring further.
> For every page in one file, fetch https://margin.manicule.dev/llms-full.txt (large).
> To search and read these docs as tools, connect the MCP server at https://margin.manicule.dev/api/mcp.
> Every agent surface: https://margin.manicule.dev/.well-known/llms-manifest.json

# Deploy (/deploy)

<!-- agent-signals: reading_time_min: 4 · est_tokens: 1492 · updated: 2026-10-27 -->
Related: [Margin](/index.md), [Configuration](/configuration.md), [Extensions](/extensions.md), [Agents and discovery](/agents.md), [Link relations](/link-relations.md)

> Publish on Vercel with Neon or run a Node server with Postgres.

The repository README includes a Vercel Deploy Button. It clones the source,
creates a project, and requests a Neon Postgres store through the Marketplace.
The store supplies pooled `DATABASE_URL` and direct `DATABASE_URL_UNPOOLED`
connections. Keep its environment-variable prefix unchanged.

## Vercel setup

1. Use the Deploy Button or import your fork into Vercel.
2. Connect Neon to the project for production and preview environments.
3. Set `SITE_NAME` if you want to override the content's display name. It is read at build time, so redeploy after changing it.
4. Keep the repository's install and build commands: `bun install` and
   `bun run vercel-build`.
5. Edit the docs folder and push.

The button follows Vercel's current
[Deploy Button source reference](https://vercel.com/docs/deploy-button/source).
The storage parameter is `stores`, a URI-encoded JSON array:

```json
[
  {
    "type": "integration",
    "integrationSlug": "neon",
    "productSlug": "neon",
    "protocol": "storage"
  }
]
```

Vercel derives `SITE_ORIGIN` from the production project URL or deployment URL.
Set it explicitly for a custom canonical origin. Production requests redirect
to its host unless `DOCS_CANONICAL_REDIRECT=0`; previews do not redirect.

## Build-time ingest

`bun run vercel-build` resolves a content key once, builds icons, runs database
migrations, builds the server, checks the bundle budget, and then ingests
content. Validation failures stop deployment. The server bundle contains the
same `DOCS_CONTENT_SHA` that ingest used. Ingest runs last so a build that fails
never advances the production pointer: retention protects the version that
pointer named, which is therefore always one a successful build published.

The key resolves from `DOCS_CONTENT_SHA`, then `VERCEL_GIT_COMMIT_SHA`, then
local git HEAD, then a generated development key. Each site, SHA, and kind
identifies a content version. A ready version is reused unless `DOCS_INGEST=force`
or the ingest CLI's `--force` is set. Force replaces content under the same key;
use a new key for a normal immutable publish.

Production ingest advances the site's current pointer. Preview ingest writes
`preview` versions without changing that pointer. A deployed server selects
its baked commit, so a shared database does not make a preview replace the
production content. Local development serves the current pointer.

## Retention and rollback

After ingest, collection protects the new version, the prior production
pointer (the last successful deploy build), and the current pointer. It also keeps the newest `DOCS_KEEP_VERSIONS`
other production versions (default 20) and production versions younger than
30 days. Production versions beyond both limits and previews or failed
attempts older than 30 days may be deleted. Watcher-created development versions
can be collected sooner beyond the count. Versions with Blob references stay
when their blobs cannot be deleted safely.

Age is measured from the content row's `created_at`, not from the deployment's
creation time. Redeploying a ready SHA returns “already ingested”; its creation
time and sequence do not change. A brand-new deployment reusing an old row can
lose that content during a later ingest if the row falls outside the keep count
and is not one of the protected pointers. `DOCS_KEEP_VERSIONS=1` makes this easy
to encounter; a recent deployment is not a new 30-day guarantee.

To refresh an existing content key, check out its commit and run:

```bash
bun scripts/ingest.ts --dir docs --slug docs --sha <commit-sha> --kind production --force
```

Alternatively, use `DOCS_INGEST=force` for that commit's deployment build.
Forced ingest atomically replaces the row with a new creation time and sequence.
It changes what deployments pinned to that key read; production ingest also
advances the current pointer. If the row was already collected, an ordinary
redeploy ingests it again. Count retention can preserve rows longer than the
age window, but it does not extend preview retention. Back up Postgres and any
Blob store.

## Self-hosting

Use Node.js 24 or newer and a reachable Postgres database. Set `DATABASE_URL`,
`SITE_ORIGIN`, and `NITRO_PRESET=node-server`, then build through the same
migration and ingest sequence:

```bash
NITRO_PRESET=node-server bun run vercel-build
node .output/server/index.mjs
```

Export the variables to both the build and the running server. Supply
`DATABASE_URL_UNPOOLED` when the main connection uses a transaction-mode pooler.
If Postgres uses a private CA, export `NODE_EXTRA_CA_CERTS=/path/to/ca.pem`
**before process startup** for the build, migrations, ingest, and server.
This adds the trusted CA while preserving certificate verification.

## Optional integrations

Sentry is off without `SENTRY_DSN`; source-map uploads also require org,
project, and auth token. Local development stays silent even with a DSN unless
`SENTRY_ENVIRONMENT` is set (for example `SENTRY_ENVIRONMENT=development`). Agent-journey analytics are off without `POSTHOG_KEY`.
The assistant needs AI Gateway access: use `AI_GATEWAY_API_KEY` outside Vercel
or enable Gateway access with the deployment's OIDC identity on Vercel.
`x-margin.search.mode: "static"` explicitly selects static search.

No Blob store is needed for ordinary docs. Without `BLOB_READ_WRITE_TOKEN`,
assets and the gzipped search corpus stay in Postgres within ingest size caps.
With it, large assets and the corpus use Vercel Blob. One Blob store can serve
production, every preview branch database and local development: each database
writes under its own namespace (minted per database address, so a branched copy
gets a new one) and deletes only its own blobs. Pointing a database at a new
address starts a new namespace and leaves the old one's blobs in the store.
Hugeicons Pro is a
build-time opt-in through `HUGEICONS_PRO_TOKEN`; Free icons are the default.
