Railway.Service reference
Service
Section titled “Service”Source:
src/Railway/Service.ts
A Railway.Service is a container in a Project. Point it at a public
image (hashicorp/http-echo) or an Effect program (main). Alchemy
stamps the name, creates a *.up.railway.app domain via
serviceDomainCreate, and deploys.
Service: Image service
Section titled “Service: Image service”Pass image without main. Railway pulls the image and runs it.
url is the generated *.up.railway.app hostname.
const site = yield* Railway.Project("Site");const api = yield* Railway.Service("Api", { project: site, image: "hashicorp/http-echo", port: 5678,});Service: Effect-native Service
Section titled “Service: Effect-native Service”A Service is a class. main: import.meta.url is the bundle
entrypoint. Alchemy bundles this file with Rolldown, generates a
Dockerfile (FROM node:26-slim), and uploads the context. Railway
builds the image. build.install: ["pg"] ships pg unbundled.
export default class Api extends Railway.Service<Api>()( "Api", { project: Site, main: import.meta.url, build: { install: ["pg"] }, }, Effect.gen(function* () { return { fetch: Effect.succeed(HttpServerResponse.text("hello")), }; }),) {}Service: Local Docker context
Section titled “Service: Local Docker context”context is a directory Railway builds with up. Mutually exclusive
with image (without main) and repo. Docker ignore files apply.
const api = yield* Railway.Service("Api", { project: site, context: "./api", port: 80,});Service: The public URL
Section titled “Service: The public URL”Yield the Service in the Stack. api.url is
https://{name}.up.railway.app.
export default Alchemy.Stack( "MyApp", { providers: Railway.providers(), state: Alchemy.localState() }, Effect.gen(function* () { const api = yield* Api; return { url: api.url }; }),);Service: Private service
Section titled “Service: Private service”publicDomain: false skips the generated *.up.railway.app hostname.
url / domain stay unset. Reach it on the private mesh at
{name}.railway.internal. Unowned generated or custom domains are
left alone.
const worker = yield* Railway.Service("Worker", { project: site, image: "hashicorp/http-echo", port: 5678, publicDomain: false,});Service: Pin a region
Section titled “Service: Pin a region”Omit region to leave placement alone. On first create that is the
workspace default. Updating region moves the replicas in place.
const api = yield* Railway.Service("Api", { project: site, image: "hashicorp/http-echo", port: 5678, region: "us-west2",});Service: Healthcheck
Section titled “Service: Healthcheck”healthcheckPath (or healthcheck, matching Railway IaC) is the
HTTP path Railway probes. Railway load-balances public traffic
across whatever replicas are running. Alchemy does not pin a count.
const api = yield* Railway.Service("Api", { project: site, image: "hashicorp/http-echo", port: 5678, healthcheck: "/health",});Service: GitHub source
Section titled “Service: GitHub source”repo + branch is the third source, next to image and main.
Railway must have GitHub connected to the account.
const api = yield* Railway.Service("Api", { project: site, repo: "acme/web", branch: "main", rootDirectory: "apps/api", buildCommand: "pnpm build", startCommand: "pnpm start",});Service: Pre-deploy
Section titled “Service: Pre-deploy”Railway runs preDeploy.command after the image build and before
start — the same setting as the dashboard Pre-deploy Command.
const api = yield* Railway.Service("Api", { project: site, image: "hashicorp/http-echo", preDeploy: { command: "bun --cwd apps/api migrate" },});Service: Cron
Section titled “Service: Cron”cronSchedule runs the service on a cron expression.
const worker = yield* Railway.Service("Worker", { project: site, image: "hashicorp/http-echo", cronSchedule: "0 * * * *",});Service: Mount a disk
Section titled “Service: Mount a disk”Bind MountVolume inside init. Provide MountVolumeLive.
export default class Api extends Railway.Service<Api>()( "Api", { project: Site, image: "hashicorp/http-echo", port: 5678 }, Effect.gen(function* () { const disk = yield* Railway.MountVolume(Data, { path: "/data" }); return { fetch: Effect.succeed(HttpServerResponse.text(disk.path)), }; }).pipe(Effect.provide(Railway.MountVolumeLive)),) {}Service: Schemaless RPC
Section titled “Service: Schemaless RPC”Return methods next to fetch. Another Service or Function binds
this class and calls them over {name}.railway.internal with a
shared token. Public *.up.railway.app requests to /__rpc__/*
get 401.
export default class Query extends Railway.Service<Query>()( "Query", { project: Site, main: import.meta.url }, Effect.gen(function* () { return { greet: (name: string) => Effect.succeed(`hello ${name}`), }; }),) {}
export default class Api extends Railway.Function<Api>()( "Api", { project: Site, main: import.meta.url }, Effect.gen(function* () { const query = yield* Railway.bindService(Query); return { fetch: query .greet("sam") .pipe(Effect.map((greeting) => HttpServerResponse.text(greeting))), }; }),) {}Service: Module-scope declarations
Section titled “Service: Module-scope declarations”Declare the Project once. Pass it into every child. Resource-valued props accept the resource or an Effect producing it.
import * as Railway from "alchemy/Railway";
export const Site = Railway.Project("Site");export const Api = Railway.Service("Api", { project: Site, image: "hashicorp/http-echo", port: 5678,});Variable
Section titled “Variable”Source:
src/Railway/Variable.ts
A Railway.Variable is a project- or service-scoped env var. Railway injects it into services in that environment. The plaintext is never stored in attributes.
Variable: Create a Variable
Section titled “Variable: Create a Variable”Wrap the value with Redacted.make so it is never logged. Omit name
and Alchemy generates an ownership-stamped name. Shared (no service)
variables apply to every service in the environment.
const site = yield* Railway.Project("Site");const dbUrl = yield* Railway.Variable("DatabaseUrl", { project: site, value: Redacted.make("postgres://…"),});Variable: Env-var name
Section titled “Variable: Env-var name”name is the env-var services see. It is stored as-is
(case-sensitive).
export const ApiToken = Railway.Variable("ApiToken", { project: Site, name: "API_TOKEN", value: Redacted.make("sk_live_…"),});Variable: Environment
Section titled “Variable: Environment”Defaults to the Project’s primary environment. Pass a
Railway.Environment (or { environmentId }) to target another one.
const staging = yield* Railway.Environment("Staging", { project: site });const token = yield* Railway.Variable("StagingToken", { project: site, environment: staging, value: Redacted.make("sk_staging_…"),});Variable: Service-scoped
Section titled “Variable: Service-scoped”Pass service to attach the variable to one service instead of the
shared project set.
const api = yield* Railway.Service("Api", { project: site, image: "hashicorp/http-echo",});const token = yield* Railway.Variable("ApiToken", { project: site, service: api, name: "API_TOKEN", value: Redacted.make("sk_live_…"),});Variable: Variable references
Section titled “Variable: Variable references”value may be a Railway.ref(resource, key) template instead of a
plaintext secret. Upsert stores ${{LogicalName.KEY}} (or
${{shared.NAME}}) — not a resolved URI. Railway interpolates it.
Next to ConnectPostgres: use ConnectPostgres for a typed
client inside an Effect-native Service; use Railway.ref when you
want Railway’s own ${{Db.DATABASE_URL}} interpolation (IaC
db.env.DATABASE_URL).
Reference Postgres DATABASE_URL
const db = yield* Railway.Postgres("Db", { project: site });const api = yield* Railway.Service("Api", { project: site, image: "hashicorp/http-echo",});const databaseUrl = yield* Railway.Variable("DatabaseUrl", { project: site, service: api, name: "DATABASE_URL", value: Railway.ref(db, "DATABASE_URL"),});Shared variable
yield* Railway.Variable("SentryDsn", { project: site, name: "SENTRY_DSN", value: Redacted.make("https://…"),});yield* Railway.Variable("ApiSentry", { project: site, service: api, name: "SENTRY_DSN", value: Railway.ref("shared", "SENTRY_DSN"),});Variable: Rotate the value
Section titled “Variable: Rotate the value”Updating value is in place via variableUpsert. Deploys are skipped
(skipDeploys: true); the Service resource owns deploys.
export const ApiToken = Railway.Variable("ApiToken", { project: Site, name: "API_TOKEN", value: Redacted.make("sk_live_rotated"),});Variable: Module-scope declarations
Section titled “Variable: Module-scope declarations”Declare the Project once. Pass it into every child. Resource-valued props accept the resource or an Effect producing it.
import * as Railway from "alchemy/Railway";import * as Redacted from "effect/Redacted";
export const Site = Railway.Project("Site");export const ApiToken = Railway.Variable("ApiToken", { project: Site, name: "API_TOKEN", value: Redacted.make("sk_live_…"),});