Deploy

Run Graft on your own infrastructure. There is no Graft cloud to sign up for.

Deploy & operate3 min read

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 appAs its own server (graft serve)
HowYour framework mounts Graft's handlers as routesA Node server runs the endpoints for you
Choose it whenYou already deploy a Next.js, Astro, SvelteKit, TanStack Start, or React Router appYou want a separate content backend, or your frontend cannot open the index
SetupYour framework guideOne command, or the container below

graft serve exposes:

RouteWhat it is
POST /api/fn/<name>Typed functions
POST /api/mcpThe agent endpoint
GET /api/content/v1/documents, GET /api/content/v1/searchThe Content API, for frontends with no index of their own
GET /healthzHealth, 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

HostHowNotes
RailwayThe containerWith managed Postgres and R2 for assets
Fly.ioThe container on Fly MachinesTigris or R2 for assets
A VPSSplit compose (db, storage, graft)Behind your own reverse proxy
VercelInside your appCompile is a build step. This docs site runs this way.

Set up database credentials

A Postgres deployment should use two database logins:

LoginWhere it livesUsed for
OperatorYour machine or CIgraft compile, graft migrate --apply, graft merge, graft approve
RuntimeThe deployment's DATABASE_URLServing 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