Deploy Costpilot
The API image serves the built dashboard itself, so one service with a volume hosts everything. Runtime config comes from environment variables (see Environment variables). SQLite lives on the volume: the data volume per team is small, one file needs no managed database, and serverless hosts have no persistent filesystem.
Railway
One click
The template creates one service built from apps/api/Dockerfile, a volume
mounted at /data, and DEMO_MODE=1. Add a custom domain under
Settings → Networking.
Manual setup
railway.json at the repo root selects the Dockerfile and the /health
check.
- New project → deploy from the GitHub repo. Keep the root directory at the
repo root (the build needs
packages/sharedandapps/web); leave the start command empty (the Dockerfile has it). - Add a volume and mount it at
/data. - Set
DEMO_MODE=1for a demo, orCOSTPILOT_PUBLIC_URLandCOSTPILOT_INGEST_TOKENfor a team instance (see Secure your instance).PORT,AI_COST_DATA_DIRandCOSTPILOT_STATIC_DIRcome from Railway and the Dockerfile.CORS_ORIGINSis only needed if a dashboard on another origin should call the API. - Generate a public domain or add a custom one via CNAME. Dashboard and API share it.
- Optional, for a public demo: add a cron service that pings the API
shortly after midnight UTC, so the daily reseed (a few seconds) happens
then and not on a visitor's first request. New service → Docker image
curlimages/curl, start commandcurl -fsS https://<your-domain>/api/meta, cron schedule5 0 * * *(Settings → Cron Schedule). If the ping ever fails, the first visitor of the day triggers the reseed instead.
Publishing or updating the template
From a working project: Project → Settings → "Generate Template from
Project". In the composer, check the service: the repo on main, root
directory empty, volume at /data, variable DEMO_MODE=1. Publish and copy
the template code from the resulting railway.com/new/template/<code> URL
into the deploy button. The repo must be readable by the people deploying
it.
Docker on any host
docker build -f apps/api/Dockerfile -t costpilot .
docker run -p 8080:8080 -v costpilot-data:/data -e DEMO_MODE=1 costpilot
Mount a volume at /data; that is where the SQLite file and the price cache
live.
Fly.io
apps/api/fly.toml mirrors the Railway setup:
fly launch --no-deploy --copy-config --config apps/api/fly.toml
fly volumes create costpilot_data --size 1
fly deploy --config apps/api/fly.toml --dockerfile apps/api/Dockerfile
Split hosting (optional)
The dashboard can be deployed separately, for example to Vercel via the
root vercel.json. Set VITE_API_URL to the API origin at build time and
restrict CORS_ORIGINS in the API to that domain (explicit origins also
allow the session cookie). Sign-in then needs both on the same site, e.g.
app.example.com and api.example.com, because the cookie is SameSite=Lax.
Upgrading
Redeploy from the new commit. The existing SQLite file is reused; schema
changes are additive, with one exception: an issues table in the old PoC
shape (free-text project column) is dropped and recreated. Back up the
volume before major upgrades.