Deploy
Run Graft on your own infrastructure. There is no Graft cloud to sign up for.
How you deploy depends on your storage engine:
- Static projects deploy like any other site. There is no Graft server.
- Postgres projects also need the database, file storage if you use assets, and somewhere for Graft's endpoints to run.
Deploy a static project
Compile in your build command, so the index is built from the files in the commit being deployed:
graft compile && next build
Then make sure .graft/index.db ships with your app. Most hosts include every
file in the build output. Vercel traces imports, not data files, so name it
explicitly if a server function reads it (this site does this, in
astro.config.mjs):
adapter: vercel({ includeFiles: [".graft/index.db"] }),
That is the whole deployment. Pages that prerender at build time do not need the file at runtime at all.
Choose where Graft runs on Postgres
Graft's endpoints (typed functions, the MCP endpoint for agents, the Content API, and a health check) can run in two places. They are the same code either way.
| Inside your app | As its own server (graft serve) | |
|---|---|---|
| How | Your framework mounts Graft's handlers as routes | A Node server runs the endpoints for you |
| Choose it when | You already deploy a Next.js, Astro, SvelteKit, TanStack Start, or React Router app | You want a separate content backend, or your frontend cannot open the index |
| Setup | Your framework guide | One command, or the container below |
graft serve exposes:
| Route | What it is |
|---|---|
POST /api/fn/<name> | Typed functions |
POST /api/mcp | The agent endpoint |
GET /api/content/v1/documents, GET /api/content/v1/search | The Content API, for frontends with no index of their own |
GET /healthz | Health, with the branch and Git commit being served |
Run the container
The quickest way to self-host is the Graft container. It bundles Postgres and file storage, runs migrations, compiles your content, and serves. During the beta the image is not published, so build it from the repository first:
docker build -f deploy/docker/Dockerfile -t graft .
Then run it with your project mounted at /project, and a named volume each for
the database and the uploaded files, so both survive a new container:
docker run --init -p 3903:3903 -v ./my-site:/project -v graft_pg:/var/lib/postgresql -v graft_minio:/data graft
GET /healthz reports the branch and Git commit it is serving. The container also creates a hardened
database role at start and serves with it. Full recipes, including split
db / storage / graft compose files, are under deploy/ in the repository.
Where to host it
| Host | How | Notes |
|---|---|---|
| Railway | The container | With managed Postgres and R2 for assets |
| Fly.io | The container on Fly Machines | Tigris or R2 for assets |
| A VPS | Split compose (db, storage, graft) | Behind your own reverse proxy |
| Vercel | Inside your app | Compile is a build step. This docs site runs this way. |
Set up database credentials
A Postgres deployment should use two database logins:
| Login | Where it lives | Used for |
|---|---|---|
| Operator | Your machine or CI | graft compile, graft migrate --apply, graft merge, graft approve |
| Runtime | The deployment's DATABASE_URL | Serving reads, running functions, agent content writes, requesting approvals |
The runtime login cannot approve destructive actions, so a leaked app password
cannot approve its own deletes. Hardening sets it up in four
steps. The container does it for you in all-in-one mode, and in
GRAFT_MODE=serve once you set GRAFT_HARDEN=1 or GRAFT_RUNTIME_PASSWORD.
Next steps
- Hardening sets up the two database logins.
- Configuration lists every environment variable.
- Observability covers health checks and tracing calls.
- Host Studio for your team lets editors sign in and publish from a browser, with every change committed to GitHub.