Railway Platform Build Configuration
Version: 1.2
Date: April 2026
Owner: AI Team
1. Purpose
Optimize Railway build times and PR preview environments for the brainforge-platform monorepo by:
- rootDirectory — Build only the app subtree (e.g.
apps/platform) instead of the full monorepo (smaller context, faster upload) - Railpack — Use Railpack (default) as the builder for Node services where applicable (better caching)
- legacy-peer-deps — Resolve @smithy peer dependency conflicts via
.npmrc - watchPatterns (config as code + dashboard) — Gitignore-style paths from the repository root (Railway often stores them with a leading
/, e.g./apps/platform/**) so Focused PR environments deploy only services touched by the PR diff - PR template hygiene — Keep the environment PRs clone from as small as practical (see §7)
2. Config Files (in repo)
| File | Purpose |
|---|---|
apps/platform/.npmrc | Sets legacy-peer-deps=true so Railpack’s auto-detected npm ci resolves peer deps |
apps/platform/nixpacks.toml | Nixpacks fallback config (only used if builder switches from Railpack) |
apps/platform/railway.toml | build.watchPatterns + RAILPACK for platform web |
apps/slack-apps/brainforge-assistant/railway.toml | watchPatterns for the Slack assistant service |
apps/opencode-worker/railway.toml | Dockerfile build + watchPatterns for the worker |
apps/marketing-site/railway.toml | watchPatterns only (builder follows dashboard / Railpack + nixpacks.toml) |
Do NOT add a railpack.json — Railpack auto-detects Node/npm from package.json and package-lock.json. Custom step overrides in railpack.json can break the BuildKit dependency graph, causing source files to be unavailable during install.
Config as code precedence: Values in railway.toml / railway.json override the dashboard for that deployment. See Config as Code.
If a service does not pick up railway.toml: In Railway Service → Settings, set the config file path to the repo-root path (e.g. apps/platform/railway.toml). The monorepo note that the config file does not follow Root Directory applies to discovery in some setups—explicit path fixes it.
3. Railway Service Config (one-time)
Run these commands after linking to the project. Replace <service> with the Railway service name (see railway service list).
# 1. Link to project (if not already)
railway link --project brainforge-platform
# 2. Platform web: root directory + Railpack (watch patterns also in apps/platform/railway.toml)
railway environment edit --service-config <platform-service> source.rootDirectory "apps/platform"
railway environment edit --service-config <platform-service> build.builder RAILPACK
# 3. Optional CLI mirror of watch patterns (usually redundant if railway.toml is loaded)
railway environment edit --service-config <platform-service> build.watchPatterns '["/apps/platform/**"]'Non-interactive batch update (recommended in CI / headless shells): railway environment edit --service-config can block waiting for a TTY. Pipe a single JSON patch on stdin instead (resolve service UUIDs from railway status --json first; keys must be IDs, not names):
railway link --project brainforge-platform --environment production
railway environment edit -m "chore: monorepo watch patterns for focused PR" --json <<'EOF'
{
"services": {
"<web-service-uuid>": { "build": { "watchPatterns": ["/apps/platform/**"] } }
}
}
EOFOther services in the same project should set source.rootDirectory to their app folder (e.g. apps/slack-apps/brainforge-assistant, apps/opencode-worker) and use the matching build.watchPatterns JSON array, or rely on the committed railway.toml in that folder.
Watch pattern reference (paths from repo root, leading slash matches production):
| App | Pattern |
|---|---|
Platform (web) | /apps/platform/** |
| Slack assistant | /apps/slack-apps/brainforge-assistant/** |
| OpenCode worker | /apps/opencode-worker/** |
| Marketing site | /apps/marketing-site/** |
| Files proxy | /apps/files-proxy/** |
| HubSpot lead nudge | /apps/hubspot-lead-nudge/** |
| Paperclip | /apps/paperclip/** |
Add patterns for any shared paths that must trigger a rebuild (e.g. a future packages/shared/**). If a PR changes only knowledge/ or unrelated apps, Focused PR should skip services whose patterns do not match. Postgres and other plugins typically have no repo watch paths—leave them unset.
4. Why .npmrc (not railpack.json or env vars)
railpack.jsoncustom step overrides break the BuildKit layer dependency chain, causing ENOENT errors duringnpm installRAILPACK_INSTALL_CMDenv var works but must be set per-environment;.npmrcis committed and consistent everywhere.npmrcwithlegacy-peer-deps=truelets Railpack use its defaultnpm ciwhile resolving the @smithy peer dep conflicts
5. Before pushing (catch Railway failures early)
Run this before pushing platform changes to avoid Railway build failures:
cd apps/platform && npm run build:verifyThis runs the build with SLACK_SUPABASE_URL and SLACK_SUPABASE_SERVICE_KEY unset, simulating Railway PR environments. If it passes locally, Railway will pass.
6. PR preview environments (Railway UI)
- Project → Settings → Environments: enable PR environments and Focused PR environments (docs).
- Watch paths /
watchPatterns: Focused PR uses changed files vs watch paths or service root directory—committedrailway.tomlkeeps this in git. - Dependencies: Services referenced via
${{otherService.*}}are pulled in when the affected service deploys; trim unnecessary references in PR templates if previews do not need them. - Domains: PR previews need a Railway-generated domain on the base service for automatic preview URLs (docs).
7. Slim the environment PRs clone from
PR environments copy from a base environment (confirm which one in Project → Settings → Environments).
| Goal | Action |
|---|---|
| Fewer cold starts | Remove services from the base that previews never need (extra workers, internal tools). |
| Less provisioning | Prefer one database (or none) for previews if the app allows it; avoid cloning full production topologies. |
| Clearer graph | Put rarely-previewed apps in a separate Railway project (e.g. marketing already uses its own project + workflow promotion). |
| Staging as template | Maintain a staging (or preview-base) env that mirrors production only for “web + deps reviewers need,” and use it as the PR source if Railway allows selecting it. |
8. Triage a slow deployment (build vs healthcheck vs runtime)
Use the deployment timeline in the Railway UI (per deployment, not only the service card). Typical phases: build → image/push → deploy/start → health check.
| Phase dominates | Likely cause | Levers |
|---|---|---|
| Build | npm ci, Docker layers, cold cache | rootDirectory, .dockerignore, fewer deps, watchPatterns, Railpack cache |
| Deploy / start | Migrations, heavy boot | Faster readiness path, defer non-critical init |
| Health check | App not listening yet; wrong path | Correct healthcheckPath, fix slow startup; adjust timeout only if truly needed (slow deployments) |
CLI:
railway logs --build --environment <env> --lines 300
railway logs --environment <env> --lines 200If build logs finish quickly but the deployment stays “in progress,” investigate runtime and healthcheck, not install.
9. Related
- Railway CLI Setup
- GitHub + Railway without UI
- Root
nixpacks.toml— legacy fallback, not used whenrootDirectoryis set with Railpack for platform