| 1 | --- |
| 2 | title: Functions, templates & VMs |
| 3 | description: Effect-native canvas Functions, marketplace templates, sandboxes, and cloud agents on Railway. |
| 4 | --- |
| 5 | |
| 6 | A [`Railway.Function`](/providers/railway/function) is an Effect |
| 7 | program on Railway's canvas Function runtime: a **single TypeScript |
| 8 | file** on Bun. No GitHub repo. No Docker. No registry. Distinct from |
| 9 | Effect-native [`Service({ main })`](/railway/compute/services), |
| 10 | which uploads a generated Dockerfile. Cap is 96KB once encoded. |
| 11 | |
| 12 | Marketplace [`Template`](/providers/railway/template)s deploy a |
| 13 | serialized config into a Project. [`Sandbox`](/providers/railway/sandbox) |
| 14 | is an ephemeral Linux VM. [`CloudAgent`](/providers/railway/cloudagent) |
| 15 | is a coding-agent VM. |
| 16 | |
| 17 | ## Declare an Effect-native Function |
| 18 | |
| 19 | A Function is a class. Props describe the canvas Function. The Effect |
| 20 | is the program that runs in it. |
| 21 | |
| 22 | ```typescript |
| 23 | // src/ping.ts |
| 24 | import * as Railway from "alchemy/Railway"; |
| 25 | import * as Effect from "effect/Effect"; |
| 26 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 27 | import { Site } from "./project.ts"; |
| 28 | |
| 29 | export default class Ping extends Railway.Function<Ping>()( |
| 30 | "Ping", |
| 31 | { |
| 32 | project: Site, |
| 33 | main: import.meta.url, |
| 34 | }, |
| 35 | Effect.gen(function* () { |
| 36 | return { |
| 37 | fetch: Effect.succeed(HttpServerResponse.text("ok")), |
| 38 | }; |
| 39 | }), |
| 40 | ) {} |
| 41 | ``` |
| 42 | |
| 43 | `main: import.meta.url` is the bundle entrypoint. Alchemy bundles |
| 44 | this file with Rolldown into one JS file and writes it as the |
| 45 | Function's `startCommand`. Railway does **not** pull a registry |
| 46 | image. |
| 47 | |
| 48 | ## Schemaless RPC |
| 49 | |
| 50 | Return methods next to `fetch`. Call `enableRailwayRpc()` in the constructor |
| 51 | (the canvas start command is capped at 96KB, so the dispatcher is |
| 52 | opt-in). Another Function or Service binds the class and calls |
| 53 | them over `{name}.railway.internal` with a shared token. Public |
| 54 | `*.up.railway.app` traffic to `/__rpc__/*` gets 401 even with the |
| 55 | token — this is not a public API. |
| 56 | |
| 57 | ```typescript |
| 58 | export default class Query extends Railway.Function<Query>()( |
| 59 | "Query", |
| 60 | { project: Site, main: import.meta.url }, |
| 61 | Effect.gen(function* () { |
| 62 | Railway.enableRailwayRpc(); |
| 63 | return { |
| 64 | greet: (name: string) => Effect.succeed(`hello ${name}`), |
| 65 | }; |
| 66 | }), |
| 67 | ) {} |
| 68 | ``` |
| 69 | |
| 70 | ```typescript |
| 71 | export default class Api extends Railway.Service<Api>()( |
| 72 | "Api", |
| 73 | { project: Site, main: import.meta.url }, |
| 74 | Effect.gen(function* () { |
| 75 | const query = yield* Railway.bindFunction(Query); |
| 76 | return { |
| 77 | fetch: query |
| 78 | .greet("sam") |
| 79 | .pipe(Effect.map((greeting) => HttpServerResponse.text(greeting))), |
| 80 | }; |
| 81 | }), |
| 82 | ) {} |
| 83 | ``` |
| 84 | |
| 85 | `bindService` is the same stub for a tagged Service. Calls are |
| 86 | `POST http://{dnsName}:{port}/__rpc__/{method}` on the private mesh. |
| 87 | Do not send `rpcToken` to the browser. |
| 88 | |
| 89 | ## Inline source |
| 90 | |
| 91 | Pass inline `source` (or `path` to a `.ts` file) instead of `main` |
| 92 | when the Function is not an Effect class. Alchemy generates a |
| 93 | `*.up.railway.app` domain unless `http: false` or a cron schedule is |
| 94 | set. |
| 95 | |
| 96 | ```typescript |
| 97 | const site = yield* Railway.Project("Site"); |
| 98 | const ping = yield* Railway.Function("Ping", { |
| 99 | project: site, |
| 100 | source: ` |
| 101 | Bun.serve({ |
| 102 | hostname: "0.0.0.0", |
| 103 | port: Number(process.env.PORT ?? 3000), |
| 104 | fetch() { return new Response("ok"); }, |
| 105 | }); |
| 106 | `, |
| 107 | }); |
| 108 | ``` |
| 109 | |
| 110 | `path` is the CLI `--path` equivalent. The file is read at |
| 111 | plan/reconcile. |
| 112 | |
| 113 | ```typescript |
| 114 | const ping = yield* Railway.Function("Ping", { |
| 115 | project: site, |
| 116 | path: "./fn.ts", |
| 117 | }); |
| 118 | ``` |
| 119 | |
| 120 | ## Cron Function |
| 121 | |
| 122 | `cronSchedule` runs the Function on a cron expression (`--cron`). |
| 123 | HTTP domains are skipped unless `http: true` is set explicitly. |
| 124 | |
| 125 | ```typescript |
| 126 | const job = yield* Railway.Function("Cleanup", { |
| 127 | project: site, |
| 128 | source: `console.log("tick");`, |
| 129 | cronSchedule: "0 * * * *", |
| 130 | }); |
| 131 | ``` |
| 132 | |
| 133 | ## Marketplace templates |
| 134 | |
| 135 | `templateId` is a marketplace UUID or code (`postgres`). Pass a |
| 136 | Project to deploy into it, or omit `project` and Alchemy creates one. |
| 137 | |
| 138 | ```typescript |
| 139 | const site = yield* Railway.Project("Site"); |
| 140 | const db = yield* Railway.Template("Postgres", { |
| 141 | templateId: "postgres", |
| 142 | project: site, |
| 143 | }); |
| 144 | ``` |
| 145 | |
| 146 | ## Sandboxes |
| 147 | |
| 148 | An ephemeral Linux VM. Create it, `execSandbox` commands, snapshot |
| 149 | with checkpoints, destroy it when the task is done. Sandboxes are |
| 150 | Priority Boarding. |
| 151 | |
| 152 | ```typescript |
| 153 | const box = yield* Railway.Sandbox("Box", { |
| 154 | environment: site, |
| 155 | idleTimeoutMinutes: 5, |
| 156 | }); |
| 157 | yield* Railway.execSandbox({ |
| 158 | sandboxId: box.sandboxId, |
| 159 | environmentId: box.environmentId, |
| 160 | command: "echo hello", |
| 161 | }); |
| 162 | ``` |
| 163 | |
| 164 | ## Cloud agents |
| 165 | |
| 166 | A persistent coding-agent VM. Sleep keeps the disk; wake re-runs the |
| 167 | entrypoint. Variables are create-only and may use |
| 168 | [`Railway.ref`](/providers/railway/ref). |
| 169 | |
| 170 | ```typescript |
| 171 | const agent = yield* Railway.CloudAgent("Coder", { |
| 172 | environment: site, |
| 173 | variables: { |
| 174 | DATABASE_URL: Railway.ref(db, "DATABASE_URL"), |
| 175 | }, |
| 176 | }); |
| 177 | ``` |
| 178 | |
| 179 | ## Where next |
| 180 | |
| 181 | [Services](/railway/compute/services) is the container when you need |
| 182 | Docker (`main`). Functions stay on the canvas runtime. |
| 183 | The [`Function`](/providers/railway/function), |
| 184 | [`Template`](/providers/railway/template), |
| 185 | [`Sandbox`](/providers/railway/sandbox), and |
| 186 | [`CloudAgent`](/providers/railway/cloudagent) references list every |
| 187 | prop. |