From 85adb06e2104c526e6979d9256758dd90ad8da5a Mon Sep 17 00:00:00 2001 From: Anmol Baranwal Date: Wed, 28 Oct 2026 20:33:32 +0530 Subject: [PATCH 1/3] chore(railway): add railway.ts for all services Railway stops reading railway.toml files on Dec 1, 2026. Add .railway/railway.ts with the same build and deploy settings for all 8 Outpost services, plus the railway SDK it needs. --- .railway/railway.ts | 197 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 13 +++ 3 files changed, 211 insertions(+) create mode 100644 .railway/railway.ts diff --git a/.railway/railway.ts b/.railway/railway.ts new file mode 100644 index 0000000..11241fc --- /dev/null +++ b/.railway/railway.ts @@ -0,0 +1,197 @@ +// Railway Infrastructure as Code - Outpost services +// https://docs.railway.com/infrastructure-as-code +// +// Takes over from the per-service railway.toml files (Config as Code), which +// Railway stops reading on 2026-12-01 (same page). Unlike Config as Code, this +// file is NOT read at deploy time: changes reach Railway only through +// `railway config apply`, so editing a setting here and merging does nothing +// until someone applies it. Needs Railway CLI 5.42.1 or newer. +// +// Written by hand, not with `railway config migrate`. As of CLI 5.58 the +// migrator drops dockerfilePath, builder and restartPolicyType for a monorepo +// like this one (railwayapp/cli#1199), so every service would fall back to +// auto-detect and fail to build. +// +// `partial` limits what this file owns to the services declared below. A +// whole-project file deletes every resource it omits on apply, and this file +// does not declare the Postgres databases, so without it an apply would plan to +// delete them. +// +// Cutover, per environment (staging first, then production): +// 1. `railway config pull` and copy that environment's variables in as +// preserve(), along with its domains (custom and *.up.railway.app: the +// webhook services' URLs are registered with GitHub, Linear and Teams). +// Then add the environment to VARIABLES_IMPORTED and merge. +// 2. In one window, with no deploys in between: +// a. Delete the root railway.toml from that environment's deploy branch +// (`staging`, then `main`). A service with no config path falls back +// to it, and it builds web, so it has to go before any path is cleared. +// b. Clear every service's "Config file path" in the dashboard. IaC +// cannot manage a service that a railway.toml still manages. +// c. `railway config plan`. It may change only the build and deploy +// settings this file sets. Stop if it removes or changes anything else: +// variables, domains, replicas, dashboard-only settings, the deploy +// trigger of slack-bot, teams-bot or linear-sync, or (in staging) an +// outpost-release-bot service. +// d. `railway config apply`. +// Run production's cutover before staging is next merged into main: that merge +// carries step 2a's deletion to main, and production web needs the root file +// until its own step 2. The per-service railway.toml files under apps/ can be +// deleted once both environments are applied; nothing reads them after 2b. +// +// Build context is the repo ROOT for every service (the Dockerfiles run +// `turbo prune`), so no service sets a root directory. + +import { defineRailway, github, service } from 'railway/iac'; + +export const partial = 'outpost-services'; + +// Environments whose variables from `railway config pull` are in this file. +// Each one is listed separately because staging and production hold different +// values: importing staging's does nothing to protect production's. +const VARIABLES_IMPORTED: Environment[] = []; + +// Each environment deploys from its own branch: staging from `staging`, +// production from `main`. Anything else is an error rather than a silent +// fallback to `main`. +const BRANCHES = { + production: 'main', + staging: 'staging', +} as const; + +type Environment = keyof typeof BRANCHES; + +const isEnvironment = (name: string): name is Environment => Object.hasOwn(BRANCHES, name); + +export default defineRailway((ctx, project) => { + const environment = ctx.environmentName ?? ctx.environment; + if (!environment) { + throw new Error( + 'No Railway environment selected. Link one with `railway environment` first.', + ); + } + if (!isEnvironment(environment)) { + throw new Error( + `No branch mapped for Railway environment "${environment}". ` + + `Known environments: ${Object.keys(BRANCHES).join(', ')}.`, + ); + } + const branch = BRANCHES[environment]; + + // Railway does not document which command it passes here, so a run without + // one is treated as read-only. That + // makes this guard catch an apply only when the CLI says `apply`; the plan + // check in cutover step 2c stays the real safety net. + const readOnly = ctx.command === undefined || ctx.command === 'plan' || ctx.command === 'pull'; + if (!VARIABLES_IMPORTED.includes(environment) && !readOnly) { + throw new Error( + `Variables for "${environment}" are not declared in .railway/railway.ts yet, so an apply would delete them. ` + + 'Import them with `railway config pull` first (see the cutover steps at the top of the file).', + ); + } + + // checkSuites turns on "wait for CI": a push deploys only after the CI check + // suite passes on that commit, as both environments do today. + const deployed = github('CopilotKit/outpost', { branch, checkSuites: true }); + + // slack-bot, teams-bot and linear-sync are optional integrations, deployed + // by hand and left offline until their credentials are configured, so this + // file does not set a branch for them. github() defaults the branch to + // `main`, which would add a deploy trigger, so it is passed as null; the SDK + // then drops the key, leaving the branch out of the plan. Cutover step 2c + // checks that Railway reads that as "no change". + const manual = github('CopilotKit/outpost', { branch: null }); + + const dockerfile = (app: string) => ({ + builder: 'DOCKERFILE' as const, + dockerfilePath: `apps/${app}/Dockerfile`, + }); + + // web's healthcheck was set in the dashboard, not in its railway.toml. It is + // declared here so it is version-controlled like the other services'. Its + // other dashboard-only settings are left out; cutover step 2c checks that + // the plan leaves them alone. + const web = service('outpost-web', { + source: deployed, + build: dockerfile('web'), + deploy: { healthcheckPath: '/api/health' }, + }); + + const discordBot = service('outpost-discord-bot', { + source: deployed, + build: dockerfile('discord-bot'), + deploy: { restartPolicyType: 'ALWAYS' }, + }); + + const githubApp = service('outpost-github-app', { + source: deployed, + build: dockerfile('github-app'), + deploy: { restartPolicyType: 'ALWAYS', healthcheckPath: '/health' }, + }); + + const linearSync = service('outpost-linear-sync', { + source: manual, + build: dockerfile('linear-sync'), + deploy: { restartPolicyType: 'ALWAYS', healthcheckPath: '/health' }, + }); + + const slackBot = service('outpost-slack-bot', { + source: manual, + build: dockerfile('slack-bot'), + deploy: { restartPolicyType: 'ALWAYS' }, + }); + + const teamsBot = service('outpost-teams-bot', { + source: manual, + build: dockerfile('teams-bot'), + deploy: { restartPolicyType: 'ALWAYS', healthcheckPath: '/health' }, + }); + + const worker = service('outpost-worker', { + source: deployed, + build: dockerfile('worker'), + deploy: { restartPolicyType: 'ALWAYS', healthcheckPath: '/health' }, + }); + + const resources = [web, discordBot, githubApp, linearSync, slackBot, teamsBot, worker]; + + // The release bot runs in production only: it posts to the community + // Discord channels, so a staging copy would announce the same releases twice. + if (environment === 'production') { + // A cron job, not a long-running process: it announces whatever is new + // and exits. restartPolicyType NEVER because a completed run exiting 0 + // is success, not a crash; ALWAYS would restart it in a loop and run the + // job back to back. + // + // watchPatterns keeps unrelated pushes from redeploying it. Every entry + // is anchored with a leading slash: these are gitignore-style patterns, + // so an unanchored `package.json` would match every package.json in the + // repo. If the release bot ever depends on a workspace package, add + // `/packages/**` here, or changes to that package will not redeploy it. + const releaseBot = service('outpost-release-bot', { + source: deployed, + build: { + ...dockerfile('release-bot'), + watchPatterns: [ + '/apps/release-bot/**', + '!/apps/release-bot/README.md', + '!/apps/release-bot/.env.example', + '!/apps/release-bot/src/__tests__/**', + '!/apps/release-bot/vitest.config.ts', + '!/apps/release-bot/tsconfig.test.json', + '/package.json', + '/pnpm-lock.yaml', + '/pnpm-workspace.yaml', + '/.npmrc', + '/tsconfig.json', + '/turbo.json', + '/.dockerignore', + ], + }, + deploy: { restartPolicyType: 'NEVER', cronSchedule: '0 * * * *' }, + }); + resources.push(releaseBot); + } + + return project('outpost', { resources }); +}); diff --git a/package.json b/package.json index c5e0e73..cc5618f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@typescript-eslint/parser": "^8.58.2", "eslint": "^9.16.0", "prettier": "^3.2.0", + "railway": "3.16.0", "tsx": "^4.21.0", "turbo": "^2.3.0", "typescript": "^5.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64b58cf..db78581 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: prettier: specifier: ^3.2.0 version: 3.8.3 + railway: + specifier: 3.16.0 + version: 3.16.0 tsx: specifier: ^4.21.0 version: 4.21.0 @@ -4015,6 +4018,11 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + railway@3.16.0: + resolution: {integrity: sha512-//vKXubZpO07iJgNyoS+9Ee3pKk+3wE+0KAJPI28GuSKqXG30UeWT4xBkkd6TYuZ4HNh7QPvkVR72Swbmq5kXw==} + engines: {node: '>=22'} + hasBin: true + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -8950,6 +8958,11 @@ snapshots: queue-microtask@1.2.3: {} + railway@3.16.0: + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2) + graphql: 16.13.2 + range-parser@1.2.1: {} raw-body@3.0.2: From 484b42f02eca19aa503671497c0c3e1138bcc6e2 Mon Sep 17 00:00:00 2001 From: Anmol Baranwal Date: Wed, 28 Oct 2026 20:49:53 +0530 Subject: [PATCH 2/3] chore(railway): remove railway.toml files Settings now live in .railway/railway.ts. Update the deployment docs and the release bot README to match. --- .railway/railway.ts | 36 +++++++-------- apps/discord-bot/railway.toml | 10 ---- apps/docs/deployment/index.html | 2 +- apps/github-app/railway.toml | 11 ----- apps/linear-sync/railway.toml | 11 ----- apps/release-bot/README.md | 16 +++---- apps/release-bot/railway.toml | 81 --------------------------------- apps/slack-bot/railway.toml | 10 ---- apps/teams-bot/railway.toml | 11 ----- apps/worker/railway.toml | 11 ----- docs/deployment.md | 4 +- railway.toml | 32 ------------- 12 files changed, 27 insertions(+), 208 deletions(-) delete mode 100644 apps/discord-bot/railway.toml delete mode 100644 apps/github-app/railway.toml delete mode 100644 apps/linear-sync/railway.toml delete mode 100644 apps/release-bot/railway.toml delete mode 100644 apps/slack-bot/railway.toml delete mode 100644 apps/teams-bot/railway.toml delete mode 100644 apps/worker/railway.toml delete mode 100644 railway.toml diff --git a/.railway/railway.ts b/.railway/railway.ts index 11241fc..ef7155f 100644 --- a/.railway/railway.ts +++ b/.railway/railway.ts @@ -17,27 +17,23 @@ // does not declare the Postgres databases, so without it an apply would plan to // delete them. // -// Cutover, per environment (staging first, then production): -// 1. `railway config pull` and copy that environment's variables in as -// preserve(), along with its domains (custom and *.up.railway.app: the -// webhook services' URLs are registered with GitHub, Linear and Teams). -// Then add the environment to VARIABLES_IMPORTED and merge. -// 2. In one window, with no deploys in between: -// a. Delete the root railway.toml from that environment's deploy branch -// (`staging`, then `main`). A service with no config path falls back -// to it, and it builds web, so it has to go before any path is cleared. -// b. Clear every service's "Config file path" in the dashboard. IaC -// cannot manage a service that a railway.toml still manages. -// c. `railway config plan`. It may change only the build and deploy +// Cutover. The change that adds this file also deletes the railway.toml files, +// so a service still pointed at one has no build config until it is applied. +// 1. Before merging: `railway config pull` in each environment and copy its +// variables in as preserve(), along with its domains (custom and +// *.up.railway.app: the webhook services' URLs are registered with +// GitHub, Linear and Teams). Then add the environment to +// VARIABLES_IMPORTED. +// 2. When the change reaches an environment's deploy branch (`main` for +// production, `staging` for staging), in the same sitting: +// a. Clear every service's "Config file path" in the dashboard. IaC +// cannot manage a service that Config as Code still manages. +// b. `railway config plan`. It may change only the build and deploy // settings this file sets. Stop if it removes or changes anything else: // variables, domains, replicas, dashboard-only settings, the deploy // trigger of slack-bot, teams-bot or linear-sync, or (in staging) an // outpost-release-bot service. -// d. `railway config apply`. -// Run production's cutover before staging is next merged into main: that merge -// carries step 2a's deletion to main, and production web needs the root file -// until its own step 2. The per-service railway.toml files under apps/ can be -// deleted once both environments are applied; nothing reads them after 2b. +// c. `railway config apply`. // // Build context is the repo ROOT for every service (the Dockerfiles run // `turbo prune`), so no service sets a root directory. @@ -81,7 +77,7 @@ export default defineRailway((ctx, project) => { // Railway does not document which command it passes here, so a run without // one is treated as read-only. That // makes this guard catch an apply only when the CLI says `apply`; the plan - // check in cutover step 2c stays the real safety net. + // check in cutover step 2b stays the real safety net. const readOnly = ctx.command === undefined || ctx.command === 'plan' || ctx.command === 'pull'; if (!VARIABLES_IMPORTED.includes(environment) && !readOnly) { throw new Error( @@ -98,7 +94,7 @@ export default defineRailway((ctx, project) => { // by hand and left offline until their credentials are configured, so this // file does not set a branch for them. github() defaults the branch to // `main`, which would add a deploy trigger, so it is passed as null; the SDK - // then drops the key, leaving the branch out of the plan. Cutover step 2c + // then drops the key, leaving the branch out of the plan. Cutover step 2b // checks that Railway reads that as "no change". const manual = github('CopilotKit/outpost', { branch: null }); @@ -109,7 +105,7 @@ export default defineRailway((ctx, project) => { // web's healthcheck was set in the dashboard, not in its railway.toml. It is // declared here so it is version-controlled like the other services'. Its - // other dashboard-only settings are left out; cutover step 2c checks that + // other dashboard-only settings are left out; cutover step 2b checks that // the plan leaves them alone. const web = service('outpost-web', { source: deployed, diff --git a/apps/discord-bot/railway.toml b/apps/discord-bot/railway.toml deleted file mode 100644 index 885ea08..0000000 --- a/apps/discord-bot/railway.toml +++ /dev/null @@ -1,10 +0,0 @@ -# Railway config — outpost-discord-bot (background worker, no public port) -# In Railway: set this service's "Config file path" to apps/discord-bot/railway.toml -# and leave Root Directory empty (build context must be the repo root). - -[build] -builder = "DOCKERFILE" -dockerfilePath = "apps/discord-bot/Dockerfile" - -[deploy] -restartPolicyType = "ALWAYS" diff --git a/apps/docs/deployment/index.html b/apps/docs/deployment/index.html index 4c3bf6b..c4c333f 100644 --- a/apps/docs/deployment/index.html +++ b/apps/docs/deployment/index.html @@ -134,7 +134,7 @@

Platform-Specific Guides

Railway (Recommended)

Railway auto-deploys from GitHub and natively supports Docker-based services. - The repo includes a railway.toml config for all five services. + The repo includes a .railway/railway.ts config for all services, applied with railway config apply.

  1. Fork or push the repo to GitHub
  2. diff --git a/apps/github-app/railway.toml b/apps/github-app/railway.toml deleted file mode 100644 index ff6ec70..0000000 --- a/apps/github-app/railway.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Railway config — outpost-github-app (webhook receiver, public port) -# In Railway: set this service's "Config file path" to apps/github-app/railway.toml -# and leave Root Directory empty (build context must be the repo root). - -[build] -builder = "DOCKERFILE" -dockerfilePath = "apps/github-app/Dockerfile" - -[deploy] -restartPolicyType = "ALWAYS" -healthcheckPath = "/health" diff --git a/apps/linear-sync/railway.toml b/apps/linear-sync/railway.toml deleted file mode 100644 index 64201e2..0000000 --- a/apps/linear-sync/railway.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Railway config — outpost-linear-sync (webhook receiver, public port) -# In Railway: set this service's "Config file path" to apps/linear-sync/railway.toml -# and leave Root Directory empty (build context must be the repo root). - -[build] -builder = "DOCKERFILE" -dockerfilePath = "apps/linear-sync/Dockerfile" - -[deploy] -restartPolicyType = "ALWAYS" -healthcheckPath = "/health" diff --git a/apps/release-bot/README.md b/apps/release-bot/README.md index 45493cd..cc519aa 100644 --- a/apps/release-bot/README.md +++ b/apps/release-bot/README.md @@ -321,7 +321,7 @@ summary written to replace them. ## Deployment -A Railway cron service. `railway.toml` carries the build config, the restart +A Railway cron service. Its entry in the repo's `.railway/railway.ts` carries the build config, the restart policy, the schedule and the watch patterns, so a recreated service is still scheduled rather than running once at deploy and never again, and an unrelated push elsewhere in the monorepo does not trigger an extra run. @@ -364,8 +364,8 @@ channel at once could both decide the same release is unannounced and both post it. It also means the interval is not a safety limit: Railway allows anything down to 5 minutes, and hourly is a choice about cost and channel noise. -To change it, edit `cronSchedule` in `railway.toml` and redeploy. Railway only -applies the schedule from that file on a deploy. +To change it, edit `cronSchedule` in `.railway/railway.ts` and run +`railway config apply`. Railway does not read that file on a deploy. ### YouTube API key @@ -397,11 +397,11 @@ allowance of 10,000 a day. 1. In the Outpost Railway project, add a service from the `CopilotKit/outpost` repo, named `outpost-release-bot`. -2. Under Settings -> Config-as-code, set the config file path to - `apps/release-bot/railway.toml`. Leave Root Directory empty: the build context - has to be the repo root, because the Dockerfile runs `turbo prune` across the - monorepo. The file sets the Dockerfile build, the hourly schedule, the restart - policy and the watch patterns, so none of those need setting in the dashboard. +2. Leave Root Directory and the config file path empty: the build context has + to be the repo root, because the Dockerfile runs `turbo prune` across the + monorepo. Run `railway config apply`: `.railway/railway.ts` sets the + Dockerfile build, the hourly schedule, the restart policy and the watch + patterns, so none of those need setting in the dashboard. 3. Set the variables from the table above under Variables. At minimum `DISCORD_BOT_TOKEN`, plus a channel id per source you want running. Release sources also need `OPENAI_API_KEY` and `GITHUB_TOKEN`. diff --git a/apps/release-bot/railway.toml b/apps/release-bot/railway.toml deleted file mode 100644 index a61292f..0000000 --- a/apps/release-bot/railway.toml +++ /dev/null @@ -1,81 +0,0 @@ -# Railway config - outpost-release-bot (scheduled community announcements) -# In Railway: set this service's "Config file path" to apps/release-bot/railway.toml -# and leave Root Directory empty (build context must be the repo root). -# -# This service is a CRON JOB, not a long-running process: it announces whatever is -# new and exits. -# -# cronSchedule is set here rather than in the dashboard so a recreated service is -# still scheduled. Without it, the service runs once at deploy and then never -# again, with no error anywhere to say so. -# -# restartPolicyType = "NEVER" because a completed run exiting 0 is success, not a -# crash. ALWAYS would restart it in a loop, running the job back to back. - -# watchPatterns keeps unrelated pushes from redeploying this service. A deploy -# can start a run outside the schedule (Railway's docs do not say either way), -# so without it every merge anywhere in the monorepo could fire an extra run. -# -# Every entry is anchored with a leading slash. These are gitignore-style -# patterns, so an unanchored `package.json` matches all 12 in the repo and an -# unanchored `tsconfig.json` matches all 15 - a Renovate bump on apps/web would -# have redeployed this service, so the list was causing the redeploys it exists -# to prevent. -# -# It narrows the problem rather than removing it. /pnpm-lock.yaml is watched -# unconditionally, and every dependency bump anywhere in the monorepo rewrites -# that file, so those still redeploy this service even though `turbo prune` -# produces a byte-identical context. Dropping it is worse: a turbo or typescript -# bump does change the image, and watchPatterns cannot express "only if the -# pruned lockfile changed". -# -# It has to name every root file that changes this image, not just the app's own -# directory: a pattern missing here means the image silently goes stale instead -# of rebuilding. It stays correct only while this package has no workspace -# dependencies; the first one means adding /packages/** too. -# -# package.json carries the root devDependencies and the packageManager field the -# pruner and installer stages both pin against, and .dockerignore decides what -# the build context contains at all - a change to either produces a different -# image from identical app sources. -# -# The negations are the same argument from the other side: docs and tests do not -# reach the image - tsconfig.json excludes tests from emit - so without them, -# editing this README or a test file redeploys the service and fires an -# announcement run off schedule. -# -# This file is deliberately NOT negated. It does not change the image either, but -# it carries cronSchedule and restartPolicyType, and only a redeploy applies -# those. Excluded, a schedule change would sit committed and inert until some -# unrelated watched file happened to move. -[build] -builder = "DOCKERFILE" -dockerfilePath = "apps/release-bot/Dockerfile" -watchPatterns = [ - "/apps/release-bot/**", - "!/apps/release-bot/README.md", - "!/apps/release-bot/.env.example", - "!/apps/release-bot/src/__tests__/**", - "!/apps/release-bot/vitest.config.ts", - "!/apps/release-bot/tsconfig.test.json", - "/package.json", - "/pnpm-lock.yaml", - "/pnpm-workspace.yaml", - "/.npmrc", - "/tsconfig.json", - "/turbo.json", - "/.dockerignore", -] - -[deploy] -restartPolicyType = "NEVER" -# Hourly, so a release or video is posted within the hour rather than up to a -# day late. A run with nothing new to post almost never calls OpenAI; it reads -# channel history back 30 days (usually one page), the latest releases per -# source, and one YouTube API request when the key is set. -# -# Runs normally take seconds. Railway does not start a scheduled run while the -# previous one is still active, so scheduled runs never overlap. Railway's -# minimum interval is 5 minutes; hourly is a choice about cost and noise, not a -# safety limit. See the README's Schedule section. -cronSchedule = "0 * * * *" diff --git a/apps/slack-bot/railway.toml b/apps/slack-bot/railway.toml deleted file mode 100644 index 8fbe6a6..0000000 --- a/apps/slack-bot/railway.toml +++ /dev/null @@ -1,10 +0,0 @@ -# Railway config — outpost-slack-bot (background worker, no public port) -# In Railway: set this service's "Config file path" to apps/slack-bot/railway.toml -# and leave Root Directory empty (build context must be the repo root). - -[build] -builder = "DOCKERFILE" -dockerfilePath = "apps/slack-bot/Dockerfile" - -[deploy] -restartPolicyType = "ALWAYS" diff --git a/apps/teams-bot/railway.toml b/apps/teams-bot/railway.toml deleted file mode 100644 index eecaa9d..0000000 --- a/apps/teams-bot/railway.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Railway config — outpost-teams-bot (webhook receiver, public port) -# In Railway: set this service's "Config file path" to apps/teams-bot/railway.toml -# and leave Root Directory empty (build context must be the repo root). - -[build] -builder = "DOCKERFILE" -dockerfilePath = "apps/teams-bot/Dockerfile" - -[deploy] -restartPolicyType = "ALWAYS" -healthcheckPath = "/health" diff --git a/apps/worker/railway.toml b/apps/worker/railway.toml deleted file mode 100644 index d99b38b..0000000 --- a/apps/worker/railway.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Railway config — outpost-worker (background job processor) -# In Railway: set this service's "Config file path" to apps/worker/railway.toml -# and leave Root Directory empty (build context must be the repo root). - -[build] -builder = "DOCKERFILE" -dockerfilePath = "apps/worker/Dockerfile" - -[deploy] -restartPolicyType = "ALWAYS" -healthcheckPath = "/health" diff --git a/docs/deployment.md b/docs/deployment.md index 094ecc9..84f9872 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -17,7 +17,7 @@ Railway auto-deploys from GitHub and natively supports Docker-based services. 2. Create a new project on [Railway](https://railway.app) 3. Add a **PostgreSQL** service (Railway has native Postgres with pgvector support) 4. Enable pgvector: connect to the database and run `CREATE EXTENSION IF NOT EXISTS vector;` -5. Add seven services from the repo, each pointing to its Dockerfile and `railway.toml` (set each service's Config file path to `apps//railway.toml`, Root Directory empty — build context must be repo root): +5. Add seven services from the repo, each pointing to its Dockerfile. Their build and deploy settings live in `.railway/railway.ts` and reach Railway through `railway config apply` (leave each service's Config file path and Root Directory empty — build context must be repo root): - **outpost-web** — `apps/web/Dockerfile` (web service, port 3000, health check `/api/health`) - **outpost-discord-bot** — `apps/discord-bot/Dockerfile` (background worker, no public URL needed — gateway connects outbound) - **outpost-github-app** — `apps/github-app/Dockerfile` (web service, port 3200, needs public URL for webhooks) @@ -127,7 +127,7 @@ Outpost's GitHub integration (`apps/github-app`) responds to issues and discussi Optional: `GITHUB_TEAM_LOGINS` (comma-separated GitHub logins treated as internal team members, used by triage logic). -5. **Deploy.** `outpost-github-app` is already defined as a Railway service (see the Quick Start section above) — point its Config file path at `apps/github-app/railway.toml`, leave Root Directory empty, add the env vars, deploy. Health check hits `GET /health`. +5. **Deploy.** `outpost-github-app` is already defined as a Railway service (see the Quick Start section above) — its build and deploy settings come from `.railway/railway.ts` (run `railway config apply`), leave Root Directory empty, add the env vars, deploy. Health check hits `GET /health`. 6. **Verify.** Open an issue on the installed repo. The agent should reply with an AI-generated answer plus a "Was this helpful? 👍/👎" prompt. React to it, then either wait for the next 24h poll or trigger `GITHUB_REACTION_POLL` manually to confirm the reaction lands as `feedback` on the `Message` row. diff --git a/railway.toml b/railway.toml deleted file mode 100644 index a4c2b9f..0000000 --- a/railway.toml +++ /dev/null @@ -1,32 +0,0 @@ -# Railway configuration — outpost-web (root / default config) -# https://docs.railway.com/reference/config-as-code -# -# Railway config-as-code is SINGLE-SERVICE per file (top-level [build]/[deploy]). -# There is NO [services.] multi-service syntax. The previous version of this -# file used [services.*] blocks, which Railway silently IGNORED — every service then -# fell back to the auto-detect (Railpack) builder and failed to build with -# "No start command detected". -# -# Monorepo layout — each service has its own single-service config file: -# outpost-web -> railway.toml (this file, the default root config) -# outpost-discord-bot -> apps/discord-bot/railway.toml -# outpost-github-app -> apps/github-app/railway.toml -# outpost-slack-bot -> apps/slack-bot/railway.toml -# outpost-teams-bot -> apps/teams-bot/railway.toml -# outpost-linear-sync -> apps/linear-sync/railway.toml -# outpost-worker -> apps/worker/railway.toml -# outpost-release-bot -> apps/release-bot/railway.toml -# -# Each non-web service must point its "Config file path" (Railway dashboard -> -# service -> Settings -> Config-as-code) at its file above. Build context is the -# repo ROOT for every service (the Dockerfiles run `turbo prune`), so leave each -# service's Root Directory empty. -# -# NOTE: web's [deploy] settings (healthcheck, etc.) stay managed in the Railway -# dashboard and are intentionally NOT overridden here, to avoid changing the live -# service. Add a [deploy] section if/when you want them version-controlled -# (web's health endpoint is /api/health). - -[build] -builder = "DOCKERFILE" -dockerfilePath = "apps/web/Dockerfile" From aa0c42d9adc2dda7470f63f8021d7df191d0a674 Mon Sep 17 00:00:00 2001 From: Anmol Baranwal Date: Wed, 28 Oct 2026 20:52:39 +0530 Subject: [PATCH 3/3] docs(railway): drop outdated migrate bug note --- .railway/railway.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.railway/railway.ts b/.railway/railway.ts index ef7155f..de2d77c 100644 --- a/.railway/railway.ts +++ b/.railway/railway.ts @@ -7,11 +7,6 @@ // `railway config apply`, so editing a setting here and merging does nothing // until someone applies it. Needs Railway CLI 5.42.1 or newer. // -// Written by hand, not with `railway config migrate`. As of CLI 5.58 the -// migrator drops dockerfilePath, builder and restartPolicyType for a monorepo -// like this one (railwayapp/cli#1199), so every service would fall back to -// auto-detect and fail to build. -// // `partial` limits what this file owns to the services declared below. A // whole-project file deletes every resource it omits on apply, and this file // does not declare the Postgres databases, so without it an apply would plan to