Deploy Grist | (Just Updated) Self-Hosted Spreadsheet Database With Python Formulas and API, Locked With a Generated Password, Data Survives Redeploys

Grist self-hosted spreadsheet database, Python formulas, REST API, locked

Deploy Grist | (Just Updated) Self-Hosted Spreadsheet Database With Python Formulas and API, Locked With a Generated Password, Data Survives Redeploys

Just deployed

/persist

Deploy and Host Grist on Railway

Built for an LLM agent to deploy and use. The deploy form has no fields to fill: the whole app sits behind a generated password, Railway's port is bound, the Python formula sandbox is enabled and the data volume is mounted. Grist is an open-source relational spreadsheet: tables with typed columns, linked records, Python formulas and a REST API, so an agent can create a document, add a table and read computed values back with plain HTTP requests.

About Hosting Grist

This template runs one service, grist (the web app and its REST API on one public domain) with a volume at /persist that holds the document files and the home database, so a redeploy loses nothing.

Locked down from boot. grist-core has no password login: a stock container either needs an external identity provider or, in its default mode, makes everyone who reaches the site the owner. This template puts an nginx gateway in front of Grist: every path except /health requires HTTP Basic auth with user admin and the password in GRIST_PASSWORD, a 32-character value generated per deploy, and the gateway tells Grist who the caller is with a header it overwrites on every request, so a caller cannot choose an identity. The wrapper refuses to start without a password of at least 16 alphanumeric characters. Grist itself listens on loopback only. Formulas run in Grist's pyodide sandbox, because the default gVisor sandbox needs privileges Railway does not grant. Upstream is Grist v1.7.22.

Using it. Open https://YOUR_DOMAIN, sign in as admin with GRIST_PASSWORD (a browser prompts for it), and create documents. Hand the password only to people who should have access.

Agent contract

  • Public URL: https://RAILWAY_PUBLIC_DOMAIN (the generated domain of the grist service; the REST API is under /api)
  • Private URL: http://grist.railway.internal:8080
  • Credentials: the variable GRIST_PASSWORD on the grist service; the user name is always admin
  • Auth: HTTP Basic, curl -u "admin:$GRIST_PASSWORD". Without credentials, or with a wrong password, every path except /health answers 401. GET /api/orgs lists the site (use its id), POST /api/orgs/ID/workspaces creates a workspace, POST /api/workspaces/ID/docs a document, POST /api/docs/ID/tables and /api/docs/ID/tables/TABLE/records fill it.
  • Health: GET /health returns 200 (no credentials needed)
#!/bin/sh
# needs: RAILWAY_PUBLIC_DOMAIN, GRIST_PASSWORD (variables of the grist service); also curl. Creates one workspace named "agent-check"
# with one document in it; delete it from the UI or with DELETE /api/workspaces/ID afterwards.
B="https://$RAILWAY_PUBLIC_DOMAIN"
A="admin:$GRIST_PASSWORD"
J="Content-Type: application/json"
curl -s -o /dev/null -w "health: %{http_code}\n" "$B/health"
curl -s -o /dev/null -w "read without key: %{http_code}\n" "$B/api/orgs"
curl -s -o /dev/null -w "read with key: %{http_code}\n" -u "$A" "$B/api/orgs"
ORG=$(curl -s -u "$A" "$B/api/orgs" | sed -n 's/.*"id":\([0-9][0-9]*\),"domain".*/\1/p' | head -1)
WS=$(curl -s -u "$A" -X POST -H "$J" -d '{"name":"agent-check"}' "$B/api/orgs/$ORG/workspaces" | tr -d '"')
DOC=$(curl -s -u "$A" -X POST -H "$J" -d '{"name":"check"}' "$B/api/workspaces/$WS/docs" | tr -d '"')
curl -s -o /dev/null -u "$A" -X POST -H "$J" -d '{"tables":[{"id":"Prices","columns":[{"id":"Qty","fields":{"type":"Numeric"}},{"id":"Total","fields":{"type":"Numeric","isFormula":true,"formula":"$Qty * 2"}}]}]}' "$B/api/docs/$DOC/tables"
curl -s -o /dev/null -u "$A" -X POST -H "$J" -d '{"records":[{"fields":{"Qty":21}}]}' "$B/api/docs/$DOC/tables/Prices/records"
if curl -s -u "$A" "$B/api/docs/$DOC/tables/Prices/records" | grep -q '"Total":42'; then echo "python formula: ok"; else echo "python formula: FAILED"; fi
curl -s -o /dev/null -w "write without key: %{http_code}\n" -X POST -H "$J" -d '{"name":"intruder"}' "$B/api/orgs/$ORG/workspaces"
curl -s -o /dev/null -w "write with wrong key: %{http_code}\n" -u "admin:wrong" -X POST -H "$J" -d '{"name":"intruder"}' "$B/api/orgs/$ORG/workspaces"
curl -s -o /dev/null -w "spoofed identity header: %{http_code}\n" -H "X-Forwarded-User: admin@example.com" "$B/api/orgs"

A fresh deploy prints health: 200, read without key: 401, read with key: 200, python formula: ok, write without key: 401, write with wrong key: 401 and spoofed identity header: 401.

Why Deploy Grist on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying Grist on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.

  • Agent-ready - no deployer fields, a contract block and one script that proves a document with a computed Python formula and the refusal of requests with no or a wrong password.
  • Not an open spreadsheet server - grist-core has no login of its own; the gateway adds one before anything is reachable.
  • Data survives redeploys - documents and the home database live on a volume at /persist.
  • Formulas work out of the box - the pyodide sandbox is enabled, so Python formula columns calculate without extra privileges.
  • Pinned - the upstream image is pinned by digest and the wrapper by tag and digest.

Common Use Cases

  • Team database and tracker - linked tables, views, filters and forms instead of a shared spreadsheet that breaks.
  • Agent-driven data store - give an agent one authenticated REST API for structured records with computed columns.
  • Internal tools and dashboards - inventory, CRM, content calendars and reports built from tables and Python formulas.
  • Spreadsheet replacement - import CSV or Excel files and keep the data on infrastructure you control.

Dependencies for Grist Hosting

  • The grist service created by the template, with its volume at /persist. A custom domain is optional.

Deployment Dependencies


Template Content

More templates in this category

View Template
Rocky Linux
Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀

codestorm
51
View Template
Foundry Virtual Tabletop
A Self-Hosted & Modern Roleplaying Platform

Lucas
71
View Template
Letta Code Remote
Run a Letta Code agent 24/7. No inbound ports, just deploy.

Letta
52