From d5c95dca135412bd01aba3a5280802fe9928ff94 Mon Sep 17 00:00:00 2001 From: RainbowBird Date: Tue, 11 Aug 2026 14:09:00 +0800 Subject: [PATCH] chore(server): define Railway service deployment config --- server/README.md | 40 +++++++++++++++++++++++++++++++++++ server/apps/api/README.md | 17 +++++++++++++-- server/apps/api/railway.toml | 12 +++++++++-- server/apps/auth/README.md | 15 +++++++++++++ server/apps/auth/railway.toml | 17 +++++++++++++++ 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 server/apps/auth/railway.toml diff --git a/server/README.md b/server/README.md index f85f3628c..064582af7 100644 --- a/server/README.md +++ b/server/README.md @@ -24,6 +24,46 @@ pnpm dev:backend The command uses `server/docker-compose.yaml` and exposes only Caddy at `http://localhost:6112`. +## Railway deployment + +API and Auth are separate long-running Railway services built from the same +repository. Keep each service's **Root Directory** at the repository root: +both Dockerfiles copy workspace manifests and shared packages from that build +context. In each Railway service, configure the Config File Path explicitly: + +| Service | Config File Path | Public role | Private dependency | +| --- | --- | --- | --- | +| Resource API | `/server/apps/api/railway.toml` | Product and resource API | Auth issuer and JWKS | +| Auth | `/server/apps/auth/railway.toml` | Better Auth and OIDC issuer | Resource API deletion endpoint | + +Each config pins its own Dockerfile, start command, `/readyz` healthcheck, and +watch patterns. A change only deploys a service when it changes that service, +one of its copied shared packages, or a copied root build input. + +### Service-to-service contract + +Share database, Redis, and observability variables using Railway reference +variables rather than copying secret values between services. Configure the +two directional private links as follows: + +| Consumer | Variable | Value source | Purpose | +| --- | --- | --- | --- | +| Resource API | `AUTH_SERVER_URL` | Auth's canonical public issuer URL | JWT issuer, audience, and public JWKS identity | +| Resource API | `AUTH_SERVER_INTERNAL_URL` | Auth's Railway private domain | Private JWKS fetch; it does not change issuer validation | +| Auth | `PUBLIC_URL` | Auth's canonical public issuer URL | Better Auth and OIDC issuer URL; must equal API `AUTH_SERVER_URL` | +| Auth | `RESOURCE_SERVER_URL` | API's Railway private domain | Private call before deleting a user's business data | + +Set `RATE_LIMIT_TRUSTED_PROXY=railway` only for services directly receiving +Railway proxy traffic. Keep `/internal/*` private: Auth calls the API over its +private domain, and public routing must not expose the API's internal Auth +routes. + +The API remains the shared database migration owner. Do not add a Railway +pre-deploy migration command to Auth, and do not make Auth startup run shared +migrations. After either service deploys, Railway must receive `200` from that +service's `/readyz`; deployment success alone is not sufficient evidence that +the service can reach its required dependencies. + ## Not included Frontend applications remain under `apps/`. Cross-runtime server SDK and diff --git a/server/apps/api/README.md b/server/apps/api/README.md index 2ab46218a..b1ff13582 100644 --- a/server/apps/api/README.md +++ b/server/apps/api/README.md @@ -35,8 +35,8 @@ the API and Auth container ports private. ## Service boundaries -- `AUTH_SERVER_URL` is the public issuer origin used for JWKS, issuer, and - audience validation. With Caddy routing, it remains `https://api.airi.build`. +- `AUTH_SERVER_URL` is Auth's canonical public issuer origin used for JWKS, + issuer, and audience validation. It must exactly equal Auth's `PUBLIC_URL`. - `/internal/auth/*` is reachable only on the deployment's trusted private network. The public edge must reject `/internal/*` and the API service must not have its own public ingress. @@ -46,3 +46,16 @@ the API and Auth container ports private. under `server/apps/auth` is imported. - `ADMIN_UI_URL` controls the standalone admin UI redirect and defaults to `https://admin.airi.build`. + +## Railway + +Deploy this as the Resource API Railway service with Config File Path +`/server/apps/api/railway.toml`; keep the service Root Directory at the +repository root because the Dockerfile copies shared workspace packages. The +config owns its Dockerfile, start command, `/readyz` healthcheck, and the +watch patterns for every copied build input. + +Set `AUTH_SERVER_INTERNAL_URL` from Auth's Railway private domain. It is only +the private JWKS route; `AUTH_SERVER_URL` remains the public Auth issuer URL. +See [`server/README.md`](../../README.md#railway-deployment) for the complete +cross-service variable and migration contract. diff --git a/server/apps/api/railway.toml b/server/apps/api/railway.toml index 8e183abc2..fbf429979 100644 --- a/server/apps/api/railway.toml +++ b/server/apps/api/railway.toml @@ -3,9 +3,17 @@ builder = "DOCKERFILE" dockerfilePath = "/server/apps/api/production/railway/Dockerfile" watchPatterns = [ "server/apps/api/**", - "server/packages/**", - "pnpm-lock.yaml" + "server/packages/auth-shared/**", + "server/packages/drizzle-migration/**", + "packages/server-sdk-shared/**", + "package.json", + "pnpm-lock.yaml", + "pnpm-workspace.yaml", + "tsconfig.json", + "patches/**" ] [deploy] startCommand = "pnpm -F @proj-airi/api-server start" +healthcheckPath = "/readyz" +healthcheckTimeout = 100 diff --git a/server/apps/auth/README.md b/server/apps/auth/README.md index c60744222..d9d9b6c8b 100644 --- a/server/apps/auth/README.md +++ b/server/apps/auth/README.md @@ -42,6 +42,21 @@ pnpm dev:backend stay on its private network. The internal `/internal/*` boundary has no application token, and Caddy rejects that path at the public edge. +## Railway + +Deploy this as the Auth Railway service with Config File Path +`/server/apps/auth/railway.toml`; keep the service Root Directory at the +repository root because the Dockerfile copies workspace manifests and +`server/packages/auth-shared`. The config owns its Dockerfile, start command, +`/readyz` healthcheck, and the watch patterns for each copied build input. + +Set `PUBLIC_URL` to this service's canonical public issuer URL, and make the +Resource API's `AUTH_SERVER_URL` exactly the same value. Set +`RESOURCE_SERVER_URL` from the Resource API's Railway private domain; do not +run shared database migrations from Auth. See +[`server/README.md`](../../README.md#railway-deployment) for the complete +service contract. + ## Do not use it for - Product APIs, billing, model routing, chat, or WebSocket business state. diff --git a/server/apps/auth/railway.toml b/server/apps/auth/railway.toml new file mode 100644 index 000000000..d796d5b7f --- /dev/null +++ b/server/apps/auth/railway.toml @@ -0,0 +1,17 @@ +[build] +builder = "DOCKERFILE" +dockerfilePath = "/server/apps/auth/Dockerfile" +watchPatterns = [ + "server/apps/auth/**", + "server/packages/auth-shared/**", + "package.json", + "pnpm-lock.yaml", + "pnpm-workspace.yaml", + "tsconfig.json", + "patches/**" +] + +[deploy] +startCommand = "pnpm -F @proj-airi/auth-server start" +healthcheckPath = "/readyz" +healthcheckTimeout = 100