1---
2title: Octane
3description: Deploy an OctaneJS app to Railway with Railway.Website.Octane — SSR plus client assets on one Service, and Octane's own Vite dev server under alchemy dev.
4sidebar:
5 order: 14
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12`Railway.Website.Octane` deploys an [OctaneJS](https://octanejs.dev)
13fullstack app to Railway. Octane wraps Vite, so the resource is
14deliberately thin: it runs your project's own `vite build` — Octane's
15plugin builds the client bundle and the self-contained SSR server
16bundle, and the Node deploy target's finishing pass wraps its fetch
17handler as a Node HTTP program. The server plus client assets run on
18one `Railway.Service` from a container image. The live URL is the generated
19`https://{name}.up.railway.app`. No Dockerfile, no build command to
20run.
21
22## Install
23
24The build integration is not bundled with alchemy. Install
25`@alchemy.run/frontend-frameworks`; the resource loads the package's
26`/octane` and `/octane/node` exports from your project at deploy
27time. It is only used at build time, so a dev dependency is enough:
28
29<Tabs syncKey="pkgManager">
30 <TabItem label="bun" icon="bun">
31 <Code code={`bun add -d ${devDeps}`} lang="sh" />
32 </TabItem>
33 <TabItem label="npm" icon="npm">
34 <Code code={`npm install -D ${devDeps}`} lang="sh" />
35 </TabItem>
36 <TabItem label="pnpm" icon="pnpm">
37 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
38 </TabItem>
39 <TabItem label="yarn" icon="seti:yarn">
40 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
41 </TabItem>
42</Tabs>
43
44## Configure Octane
45
46Keep native compiler and route settings in `octane.config.ts`, without
47an adapter:
48
49```typescript
50// octane.config.ts
51import { defineConfig, RenderRoute } from "@octanejs/vite-plugin";
52
53export default defineConfig({
54 router: {
55 routes: [new RenderRoute({ path: "/", entry: ["App", "/src/App.tsx"] })],
56 },
57});
58```
59
60`Railway.Website.Octane` selects hosting and automatically wraps Octane's
61default native Node output as an HTTP server. The legacy Node marker
62adapter remains optional for existing projects.
63
64Vite plugins like Tailwind go in `vite.config.ts` next to it,
65alongside the Octane plugin:
66
67```typescript
68// vite.config.ts
69import { octane } from "@octanejs/vite-plugin";
70import tailwindcss from "@tailwindcss/vite";
71import { defineConfig } from "vite";
72
73export default defineConfig({
74 plugins: [octane(), tailwindcss()],
75});
76```
77
78## Declare the Website
79
80Declare the site as a module-level const (rather than inline in the
81Stack):
82
83```typescript
84// alchemy.run.ts
85import * as Railway from "alchemy/Railway";
86
87export const Website = Railway.Website.Octane("Website");
88```
89
90Omit `project` and Alchemy creates a `Railway.Project` under the
91site's namespace. Pass an existing Project to put the site in it:
92
93```typescript
94export const Site = Railway.Project("Site");
95
96export const Website = Railway.Website.Octane("Website", {
97 project: Site,
98});
99```
100
101## Add it to the Stack
102
103Yield the site from your Stack and return its URL:
104
105```typescript
106// alchemy.run.ts
107import * as Alchemy from "alchemy";
108import * as Effect from "effect/Effect";
109
110export default Alchemy.Stack(
111 "MyOctaneSite",
112 {
113 providers: Railway.providers(),
114 state: Alchemy.localState(),
115 },
116 Effect.gen(function* () {
117 const site = yield* Website;
118 return { url: site.url };
119 }),
120);
121```
122
123On deploy, `url` is the Service's `https://{name}.up.railway.app`.
124The resolved site also exposes `service` and `project` (both
125`undefined` under `alchemy dev`).
126
127## Add environment variables
128
129Process environment is configured under `env` — plain strings, or
130`Redacted` values. They are copied onto `process.env` before the
131build and onto the Service at deploy time:
132
133```diff lang="typescript"
134// alchemy.run.ts
135export const Website = Railway.Website.Octane("Website", {
136+ env: {
137+ GREETING: "Hello from alchemy",
138+ API_BASE: "https://api.example.com",
139+ },
140});
141```
142
143The same values are injected into the dev server's process
144environment under `alchemy dev`, so server code reads them in both
145modes.
146
147## Read the environment in server code
148
149Octane's server runs in Node on the Service, so middleware and
150`ServerRoute` handlers read the environment from `process.env`:
151
152```typescript
153// octane.config.ts
154new ServerRoute({
155 path: "/api/greeting",
156 methods: ["GET"],
157 handler: async () => {
158 return Response.json({ greeting: process.env.GREETING ?? "hello" });
159 },
160});
161```
162
163## Asset routing
164
165Octane's intended setup is asset-first with SSR on miss: exact files
166in `dist/client` are served by the Node process without invoking the
167Octane handler, and every miss streams from Octane SSR. That is the
168default — there is nothing to configure. The Service healthcheck is
169`GET /health` on port 3000.
170
171## Local dev
172
173```sh
174bun alchemy dev
175```
176
177`alchemy dev` runs Octane's own Vite dev server — the plugin's
178in-process SSR middleware serves rendering, server routes, and RPC
179with full HMR. `site.url` is the local address and no Railway
180resources are created (`service` and `project` are `undefined`;
181 Wrap the site in `Alchemy.remote()` to deploy
182the real infrastructure even during dev.
183
184## Custom domain
185
186```typescript
187const site = yield* Railway.Website.Octane("Web", {
188 domain: "app.example.com",
189});
190```
191
192Alchemy attaches a `Railway.CustomDomain` (`targetPort` 3000) and
193`url` becomes `https://{domain}`. Point DNS at Railway's
194verification records — see
195[custom domains](/railway/networking).
196
197## Where next
198
199- [`Railway.Website.Octane` reference](/providers/railway/website/octane)
200- [Projects](/railway/compute/projects) and
201 [Services](/railway/compute/services)
202- [Custom domains](/railway/networking)