Skip to content

Functions, templates & VMs

A Railway.Function is an Effect program on Railway’s canvas Function runtime: a single TypeScript file on Bun. No GitHub repo. No Docker. No registry. Distinct from Effect-native Service({ main }), which uploads a generated Dockerfile. Cap is 96KB once encoded.

Marketplace Templates deploy a serialized config into a Project. Sandbox is an ephemeral Linux VM. CloudAgent is a coding-agent VM.

A Function is a class. Props describe the canvas Function. The Effect is the program that runs in it.

src/ping.ts
import * as Railway from "alchemy/Railway";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
import { Site } from "./project.ts";
export default class Ping extends Railway.Function<Ping>()(
"Ping",
{
project: Site,
main: import.meta.url,
},
Effect.gen(function* () {
return {
fetch: Effect.succeed(HttpServerResponse.text("ok")),
};
}),
) {}

main: import.meta.url is the bundle entrypoint. Alchemy bundles this file with Rolldown into one JS file and writes it as the Function’s startCommand. Railway does not pull a registry image.

Return methods next to fetch. Call enableRailwayRpc() in init (the canvas start command is capped at 96KB, so the dispatcher is opt-in). Another Function or Service binds the class and calls them over {name}.railway.internal with a shared token. Public *.up.railway.app traffic to /__rpc__/* gets 401 even with the token — this is not a public API.

export default class Query extends Railway.Function<Query>()(
"Query",
{ project: Site, main: import.meta.url },
Effect.gen(function* () {
Railway.enableRailwayRpc();
return {
greet: (name: string) => Effect.succeed(`hello ${name}`),
};
}),
) {}
export default class Api extends Railway.Service<Api>()(
"Api",
{ project: Site, main: import.meta.url },
Effect.gen(function* () {
const query = yield* Railway.bindFunction(Query);
return {
fetch: query
.greet("sam")
.pipe(Effect.map((greeting) => HttpServerResponse.text(greeting))),
};
}),
) {}

bindService is the same stub for a tagged Service. Calls are POST http://{dnsName}:{port}/__rpc__/{method} on the private mesh. Do not send rpcToken to the browser.

Pass inline source (or path to a .ts file) instead of main when the Function is not an Effect class. Alchemy generates a *.up.railway.app domain unless http: false or a cron schedule is set.

const site = yield* Railway.Project("Site");
const ping = yield* Railway.Function("Ping", {
project: site,
source: `
Bun.serve({
hostname: "0.0.0.0",
port: Number(process.env.PORT ?? 3000),
fetch() { return new Response("ok"); },
});
`,
});

path is the CLI --path equivalent. The file is read at plan/reconcile.

const ping = yield* Railway.Function("Ping", {
project: site,
path: "./fn.ts",
});

cronSchedule runs the Function on a cron expression (--cron). HTTP domains are skipped unless http: true is set explicitly.

const job = yield* Railway.Function("Cleanup", {
project: site,
source: `console.log("tick");`,
cronSchedule: "0 * * * *",
});

templateId is a marketplace UUID or code (postgres). Pass a Project to deploy into it, or omit project and Alchemy creates one.

const site = yield* Railway.Project("Site");
const db = yield* Railway.Template("Postgres", {
templateId: "postgres",
project: site,
});

An ephemeral Linux VM. Create it, execSandbox commands, snapshot with checkpoints, destroy it when the task is done. Sandboxes are Priority Boarding.

const box = yield* Railway.Sandbox("Box", {
environment: site,
idleTimeoutMinutes: 5,
});
yield* Railway.execSandbox({
sandboxId: box.sandboxId,
environmentId: box.environmentId,
command: "echo hello",
});

A persistent coding-agent VM. Sleep keeps the disk; wake re-runs the entrypoint. Variables are create-only and may use Railway.ref.

const agent = yield* Railway.CloudAgent("Coder", {
environment: site,
variables: {
DATABASE_URL: Railway.ref(db, "DATABASE_URL"),
},
});

Services is the container when you need Docker (main). Functions stay on the canvas runtime. The Function, Template, Sandbox, and CloudAgent references list every prop.