Railway
Deploy Grovs on Railway from the template, or from the repository's infrastructure definition with the CLI.
When to use
You want a managed platform with no server to maintain. Railway runs the API, dashboard, two workers and three private database services with persistent volumes.
There are two flows. Use the template for a guided setup, or the CLI to manage configuration in files. The CLI flow also declares the custom domains for you. Pick one; do not deploy a second copy.
Prerequisites
| Requirement | Notes |
|---|---|
| Railway plan | Capacity for seven services and the custom domains below. Compute, volumes, network usage and your object storage are billable. |
| Private S3 bucket | Bucket-scoped credentials that can list, read, write and delete Grovs objects. The API and workers share the bucket; they cannot share a Railway volume (Railway volumes). |
| Domains you control | App, production links and test links |
| CLI flow only | Node.js 22 or later, the Railway CLI and OpenSSL |
1. Prepare DNS and proxy
Choose three domains. This guide uses:
| Variable | Example |
|---|---|
SERVER_HOST | grovs.example.com |
DOMAIN_LIVE | links.example.com |
DOMAIN_TEST | test.links.example.com |
Railway issues the certificates. You add the DNS and verification records it gives you in step 4. Project links use *.links.example.com and *.test.links.example.com, and each needs its own domain and certificate setup (Railway wildcard domains).
2. Create the app
The template stores image references apart from runtime variables. Check the image sources of the application services. To follow the latest stable GHCR images, use:
| Services | Image source |
|---|---|
web, worker-1, worker-2 | ghcr.io/grovs-io/backend:latest |
dashboard | ghcr.io/grovs-io/dashboard:latest |
For a fixed pair, replace latest on all four services with the version from the latest completed release. A GROVS_VERSION runtime variable alone does not change a template service's image source. Leave the database image versions as supplied.
Open Deploy on Railway and choose your workspace.
Clone the repository and generate the private configuration:
git clone https://github.com/grovs-io/self-host.git
cd self-host
npm --prefix .railway ci
GROVS_RELEASE_URL="$(curl -fsSL -o /dev/null -w '%{url_effective}' https://github.com/grovs-io/self-host/releases/latest)"
GROVS_VERSION="${GROVS_RELEASE_URL##*/}" \
GROVS_DOMAIN=grovs.example.com GROVS_LINKS_DOMAIN=links.example.com \
[email protected] \
./scripts/prepare-railway.sh "$HOME/grovs-railway"
export GROVS_CONFIG_FILE="$HOME/grovs-railway/.env"The file holds generated secrets and your first admin login. Keep it outside Git and back it up. A fresh setup selects the latest completed release; later runs preserve the file. The CLI definition requires a numbered GROVS_VERSION.
Create a new empty Railway project and environment. Keep all services and their volumes in one region.
3. Configure environment
Fill in the eight required variables on web:
SERVER_HOST, DOMAIN_LIVE, DOMAIN_TEST, BOOTSTRAP_ADMIN_EMAIL, AWS_S3_KEY_ID, AWS_S3_ACCESS_KEY, AWS_S3_REGION, AWS_S3_BUCKET
For S3-compatible storage, also set S3_ENDPOINT and S3_FORCE_PATH_STYLE as needed. Railway generates the database passwords, application secrets and admin password. Keep these values when you redeploy or upgrade.
Add the bucket to the same file by uncommenting its example lines:
AWS_S3_KEY_ID=your-access-key
AWS_S3_ACCESS_KEY=your-secret-key
AWS_S3_REGION=your-bucket-region
AWS_S3_BUCKET=your-bucket-nameS3-compatible storage also accepts S3_ENDPOINT and S3_FORCE_PATH_STYLE. This file is the source for managed variables; change settings there.
4. Deploy and verify
-
Review the services, storage and estimated usage, then deploy.
-
In Networking, add the custom domains, all targeting port 3000:
Service Custom domains dashboarddashboard.grovs.example.comwebapi.grovs.example.com,sdk.grovs.example.com,mcp.grovs.example.com,go.grovs.example.com,preview.grovs.example.com,*.links.example.com,*.test.links.example.com -
Add the DNS and certificate-verification records Railway provides.
-
Sign in at your dashboard domain with the admin email and the generated
BOOTSTRAP_ADMIN_PASSWORDfromweb's variables.
Link the directory to the new project, then review and apply:
railway login
railway link
railway config plan
railway config applyCheck the linked project and environment and the proposed resources. apply asks for confirmation. This uses Railway's current project-level IaC, not the deprecated per-service railway.json format.
The definition requests the dashboard host on dashboard, and the API, SDK, MCP, go and preview hosts plus both wildcard domains on web, all on port 3000. In each service's Networking settings, add the exact DNS records Railway supplies, including certificate-verification CNAMEs.
Open https://dashboard.grovs.example.com and use the login printed by setup.
Then verify:
https://api.grovs.example.com/upanswers.- A project's production and test links open, and analytics arrive.
- An uploaded image survives an API redeployment.
Upgrades
- Back up the databases, uploads and the private configuration. See Backups.
- Stop both workers.
worker-1must have exactly one running scheduler, including during rollouts. - Select the release:
- CLI: look up the latest release, change
GROVS_VERSIONin the private file, then plan and apply. - Template: update or pull the service image sources listed in step 2.
- CLI: look up the latest release, change
- Deploy the API and confirm migrations succeed.
- Restart the workers and dashboard on the matching release.
Moving :latest tags take effect on an image pull and deployment, not on running containers.
Platform-specific gotchas
- The API initializes PostgreSQL and ClickHouse before starting (in the CLI flow, as a pre-deploy command). If a datastore is still starting on the first attempt, wait until it is ready and redeploy
web. Check the logs to tell readiness failures from migration errors. - Workers wait for API health. Keep
worker-1at exactly one instance, and keep application services always running: no serverless sleeping for workers. - Volumes mount at PostgreSQL's
/var/lib/postgresql/data, Redis's/dataand ClickHouse's/var/lib/clickhouse. Redis uses AOF andnoeviction. Databases have no public domains or TCP proxies. - Avoid
--show-values; it can print secrets. - If you use Cloudflare DNS, keep the verification records unproxied.
- Deleting services or volumes can delete data. Export your databases and review the deletion plan first. Removing a Railway project does not remove an external S3 bucket or its charges.