About genesis-railway
A deployment skill for shipping a Genesis monorepo to Railway, aimed at projects with a Go API, React web app, and Postgres. It prescribes Railway MCP tools for infrastructure changes and verifies configuration and deployments.
Key features
- 01Ordered Railway service setup
- 02Environment variable discovery and wiring
- 03Service build and health-check configuration
- 04Deployment and endpoint verification
- 05Postgres template and pgvector guidance
Use cases
- 01Deploy a Genesis monorepo with API, web, and Postgres
- 02Configure Railway services and private networking
- 03Diagnose failed builds or health checks
MCP support
Requires Railway MCP server tools for Railway mutations, configuration, and verification. Railway CLI is limited to login, linking, project initialization, and adding the Postgres template.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ lwlee2608/ genesis --skill genesis-railwayAfter installing, type/genesis-railwayin Claude Code to confirm it loaded.
Details
From the project READMEgenesis-railway
Deploys a genesis monorepo to Railway using the Railway MCP tools. The layout it expects: services/<app>-server (Go API with a Dockerfile and /health), services/<app>-web (nginx-served React app with a Dockerfile), and Postgres.
Railway project: <app>
├── Postgres # Railway Postgres template
├── <app>-server # builds services/<app>-server/Dockerfile
└── <app>-web # builds services/<app>-web/Dockerfile, gets the public domain
web ──▶ server ──▶ Postgres (private networking)
Prerequisites
Railway MCP server (mcp__railway__* tools). If they are not available, stop and tell the user — do not fall back to guessing CLI commands.
Railway CLI (npm i -g @railway/cli or brew install railway), used only for railway login, railway init, railway add, railway link, and reading resolved variables.
Rules
-
Use MCP tools for every Railway mutation. Settings, variables, volumes, sources, and domains go through
mcp__railway__*. The CLI is for login, link, andrailway add --database postgresonly. -
Never add
railway.json/railway.toml. Config-as-code is deprecated and stops working 2026-12-01, and no MCP tool writes it. All build/deploy settings are set on the service viaupdate-service(step 3). Its successor,.railway/railway.ts(railway config pull/plan/apply, CLI ≥ 5), is out of scope. -
Create services in dependency order. A
${{Service.VAR}}reference is empty until the referenced service exists: Postgres, then server, then web. -
Find required variables before creating anything. Grep
cmd/<app>/main.gofor config checks thatos.Exit— each needs a variable. Config keys map to env with.→_(http.port→HTTP_PORT). -
Use the Railway Postgres template. It ships with a volume, the Database tab, backups, and
DATABASE_URL/PG*variables. Only if a migration runsCREATE EXTENSION vectorfall back to apgvector/pgvectorimage service (see pgvector exception) — the template image lacks it. -
Resolve the project and environment IDs first. If the repo root is linked, read
~/.railway/config.json(.projects["<abs repo path>"].project). Otherwiselist-projects, orrailway init --name <app>from the repo root. Resolve the target environment withlist-services; use explicit project, environment, and service IDs for configuration and verification.
Steps
- Create services —
railway add --database postgresfrom the linked repo root (the remote MCP has no template tool; the CLI names the servicePostgres), thencreate-service<app>-server, then<app>-web(no image). - Variables via
set-variableswithskipDeploys: true:- Server:
PORT=8080,HTTP_PORT=${{PORT}}(both — the server readsHTTP_PORT; without it the healthcheck probes the wrong port),DB_URL=${{Postgres.DATABASE_URL}}?sslmode=disable,LOG_LEVEL=info, plus anything found in rule 4 (generate secrets, report them to the user). - Web:
BACKEND_URL=http://${{<app>-server.RAILWAY_PRIVATE_DOMAIN}}:8080
- Server:
- Settings via
update-serviceon server and web:rootDirectory: services/<app>-{server,web},dockerfilePath: Dockerfile,watchPatterns: ["services/<app>-{server,web}/**"](repo-root relative),restartPolicyType: ON_FAILURE,restartPolicyMaxRetries: 1. Server also:healthcheckPath: /health,healthcheckTimeout: 60(startup only; justify any increase). - Read back both services' live
configviaget-service-config; confirm step 3 and serverPORT=HTTP_PORT=8080vialist-variables. If values are hidden, userailway variables --json --project <project-id> --environment <environment-id> --service <server-id>without exposing secrets. Fix and recheck mismatches; stop if unverifiable. - Source — after steps 2–4,
connect-service-sourceon server, then web:repo: <owner>/<repo>,branch: main. This starts builds and GitHub auto-deploy. Record triggered deployment IDs from the response or scopedlist-deployments, matching source and trigger time; report ambiguity rather than guess. - Domain —
generate-domainon web. - Link subdirs so the CLI works from inside them (absolute paths for
cd):cd <abs>/services/<app>-server && railway link -p <app> -e production -s <app>-server cd <abs>/services/<app>-web && railway link -p <app> -e production -s <app>-web
Additional infra
If docker-compose.yml has more infra than Postgres (Redis, MinIO, etc.), create each from the official Railway template, not a plain image, and do it before the server so its variables can be referenced. Fall back to create-service with image: only when no template exists.
pgvector exception
Replaces step 1's Postgres and adds what the template would have provided:
create-servicePostgreswithimage: pgvector/pgvector:<tag from docker-compose.yml>.set-variables(skipDeploys: true):POSTGRES_DB=<app_underscore>,POSTGRES_USER=postgres,POSTGRES_PASSWORD=<openssl rand -hex 24>,PGDATA=/var/lib/postgresql/data/pgdata(volume root isn't empty),PGHOST=${{RAILWAY_PRIVATE_DOMAIN}},PGPORT=5432,PGUSER=${{POSTGRES_USER}},PGPASSWORD=${{POSTGRES_PASSWORD}},PGDATABASE=${{POSTGRES_DB}}(thePG*set turns on the dashboard's Database tab),DATABASE_URL=postgres://${{POSTGRES_USER}}:${{POSTGRES_PASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:5432/${{POSTGRES_DB}}.create-volumeon Postgres,mountPath: /var/lib/postgresql/data.- No Database tab in the dashboard; that only comes with the template.
Verification procedure
- Poll scoped
list-deploymentsfor each recorded ID untilSUCCESS; never substitute another deployment. OnFAILED/CRASHED, fetch itsget-logswithtypes: ["build", "deploy"]and report the cause. If blocked or polling stops, report the ID, state, and next action as unverified. Check Postgres withenvironment-status. curl --fail --max-time 10 https://<web domain>/healthmust return the expected backend health response; HTTP errors or unexpected bodies fail verification.- For proxy failures, inspect resolved
BACKEND_URLvia MCP or scoped CLI. Ifhttp://:8080,redeployweb and repeat verification with the new deployment ID.
Common mistakes to watch for
- Setting only
PORT. The server readsHTTP_PORT; without it the healthcheck probes the wrong port and every deploy fails. - Skipping
PGDATAon a pgvector image service. The volume root isn't empty, so Postgres refuses to init without a subdirectory. - Connecting the source before variables/settings. The first build fires immediately and runs with missing config.
- Using a plain image service when the template would do. You lose the Database tab and backups for nothing.
- Using the placeholder
project-00inwatchPatternsor service names. References like${{project-00-server.RAILWAY_PRIVATE_DOMAIN}}resolve to empty.