Deploy
Self-host Graft — one container, split compose, or handlers embedded in your app.
Self-host Graft
Graft is self-hostable. You run the source against your Postgres and your object store. There is no required Graft cloud.
Container quick path
docker run -p 3903:3903 graft
# migrate → compile → serve
# GET /healthz reports branch + git SHA
Mount your project at /project. Boot also creates a hardened runtime role and
serves under it, because this container owns its database. Full recipes live
under deploy/ in the repo.
Topologies
Runtime work is a stateless Request → Response handler. Two wrappers, same bytes:
-
Embedded — your app (Next, Astro, SvelteKit, TanStack Start, React Router) mounts the handlers as routes.
-
Headless —
graft servebinds those handlers to a Node HTTP server:POST /api/fn/<name>,POST /api/mcp,GET /api/content/v1/documents,GET /api/content/v1/search,GET /healthz. A frontend can read authored content through@usegraft/content-apiwithout a database of its own. See Reading content.The two content routes authenticate nobody, and they publish every collection in your config. They carry a 60-per-60s rate limit under
graft serve, which is a backstop against exhaustion, not an access control. Anything reachable beyond a trusted proxy should be fronted by one that checks. See The content API.
Adapters
| Adapter | Topology | Runs |
|---|---|---|
| Railway | container | deploy/docker image + managed Postgres / R2 |
| Fly.io | container | Same image on Fly Machines; Tigris or R2 for assets |
| VPS | container | Split compose (db / storage / graft) behind a reverse proxy |
| Vercel | embedded | App mounts handlers; compile is a build step |
This docs site uses the Vercel embedded path from Astro.
Credential recipe
Give the deployment a runtime credential that can execute mounted endpoints but cannot decide approvals:
CREATE ROLE graft_runtime LOGIN PASSWORD '…';
graft harden graft_runtime
# deploy with the runtime role URL as DATABASE_URL
# keep the operator URL for compile / migrate / approve
The container automates this. All-in-one does it by default; GRAFT_MODE=serve
waits for GRAFT_HARDEN=1 or GRAFT_RUNTIME_PASSWORD, because there the
database is yours.
| Task | Credential |
|---|---|
graft compile, migrate --apply, merge, approve | operator |
| Functions, MCP (incl. content writes), reads, approval request/consume | runtime |
Hardened deployments are functions-and-reads first: the runtime role cannot project content, so remote MCP writes need the operator path (CI or a writable self-host mount).
Next steps
- Configuration — env vars for serve, auth, and assets
- Audit & approvals — how the human gate survives deploy
- What is Graft — product overview