How Railcode works

A proxy in front of every deployment, a zero-config SDK in every backend function, and a format any JavaScript stack can target. The ten-minute technical tour.

Railcode is a deployment platform for internal software. You deploy JavaScript apps to us: a frontend and a backend function. On paid plans, you can also define managed agents in YAML, and we run them for you.

Around those we provide everything you need to build complete apps: data and file storage, an LLM gateway, managed connectors to your databases and SaaS tools, and a mail service. This page is the ten-minute tour of how the pieces fit.

The Railcode proxy

We run a proxy in front of every deployment. That one decision does most of the work. Every project on Railcode is behind company auth by default. Every access to an external resource (an LLM, a connector, a data source) is permissioned and logged in one place.

Here is what happens when someone opens the finance-tracking app. The request passes two gates before the app is served. Neither gate is a formality: passing the first buys you nothing at the second.

Gate 1: belonging. Is this person a member of the org? The proxy resolves the request to an org before it resolves it to an app. Someone outside the org never reaches the second question.

Gate 2: this resource. Do they have access to this app? Membership alone opens nothing. They need access to this app, either granted to them directly or carried by one of their roles.

Gate 1 is about the org; gate 2 is about the app. Here are the same two gates deciding four requests:

Fig. 2Four requests · same path
WhoGate 1 · member of this org?Gate 2 · access to this app?Lands
MayaFinance · Northwind

member

her Finance role carries it

finance-tracking
SamContractor · Northwind

member

granted to them directly, no role

finance-tracking
AliEngineering · Northwind

member

no grant, no role that carries one

Access denied
RitaMeridian · another org

not a member here

never asked

Access denied
Rita's second cell is dashed on purpose: gate 2 is never evaluated for her. The gates short-circuit, so a non-member learns nothing about which apps exist.

The difference between Maya and Sam is the whole of gate 2: a role that carries access, or a grant made directly to them. Either route is enough on its own. Ali shows that membership is not access. And Rita never reaches gate 2 at all. The gates stop at the first failure, so someone from another org learns nothing about which apps exist.

A user who fails either gate sees a 404 rather than a 403. A 403 would confirm that the app exists.

You set who passes gate 2 per app (organization, private, or restricted). You set roles once for the whole org, and every app reads the same ones. See app access and roles and grants.

The Magic SDK

Because apps run behind the proxy, every user who lands on an app has the right to be there. Two useful things follow.

First, your frontend can call your backend function without an API key. There is nothing to leak, because there is nothing to hold.

Second, your backend function receives context about the caller: who they are, whether they are an admin, and which roles they hold. We call it ctx.user. The proxy verified it, so app code cannot fake it.

On top of that, every backend function gets a zero-config SDK. Import what you need from @railcode/sdk, and everything Railcode manages is there. You never set up a connection string, a credential in an environment variable, or a provider SDK.

Here is a route that triages a Sentry issue. Only Engineering may call it. It reads the issue through a governed connector, asks the LLM gateway for a priority, stores the answer, and returns the queue:

import { Hono } from "hono";
import { ctx, db, connector, llm } from "@railcode/sdk";

const app = new Hono();

app.post("/api/triage", async (c) => {
  const user = ctx.user;
  if (!user?.roles.some((r) => r.name === "Engineering")) {
    return c.json({ error: "You must be part of the Engineering team to call this endpoint" }, 403);
  }

  const { issueId } = (await c.req.json()) as { issueId: string };

  // The connector holds the Sentry credential. This backend function never sees it.
  const issue = await (await connector("sentry").fetch(`/issues/${issueId}/`)).json();

  const result = await llm.generate({
    messages: [{
      role: "user",
      content: `Assign a priority (P0-P4) to this Sentry issue. Reply with the level only.\n\n${JSON.stringify(issue)}`,
    }],
  });
  const priority = result.text.match(/P[0-4]/)?.[0] ?? "P4";

  await db.collection("issues").put(issueId, { issueId, priority, triaged_by: user.id });

  const queue = await db.collection("issues").query().order("priority", "asc").page(1, 100);
  return c.json(queue);
});

export default app;

The route never handles a Sentry token, an OpenAI key, or a database URL. There is no auth middleware to write. The role check is the only line about who is allowed, and it is written against a caller the platform vouched for.

Everything that route does runs under the app's declared authority. Its manifest.yaml names the LLM and the sentry connector endpoints it uses. A deploy can never grant itself more than the person deploying it already holds. See the manifest.

Basically, we take care of the annoying bits so you can focus on the fun part: building.

Apps

We offer Hono + Vite and TanStack templates out of the box (railcode init). You can also use any stack or JavaScript framework you like, as long as it builds into a static frontend and a single ES module backend function.

The backend uses the same format as Cloudflare Workers, so most stacks can already target it:

export default {
  async fetch(request) {
    return new Response("hello");
  },
};

Point railcode.json at the built frontend directory and the built backend module. railcode deploy ships both as one unit; the two halves activate together and roll back together. See the app model for the templates and the bring-your-own rules.

A note on Next.js

You can deploy a Next.js app by building it with OpenNext and targeting our backend format, but we don't recommend it. Next.js is tightly coupled to Vercel's features and generally does not work well outside Vercel. If you like React, the hono+vite template (React + Vite on the front, Hono on the back) or tanstack will be much easier.

Managed agents

On paid plans we also run managed agents for you: server-side AI that you define in YAML and we execute. Each run gets a code sandbox and a durable, auditable history. Use an agent whenever the work has to read or write a file, run code, take minutes, run on a schedule, or answer from Slack. Anything shorter (summarize, classify, draft while the user waits) belongs in the backend function's own llm. See managed agents.

You own the code

You build with the tools you already use: Claude Code, Codex, Cursor, or a plain editor. The code is yours. It lives in your repo, runs locally with railcode dev, and goes on GitHub if you want it there. We ship a CLI, and a skill your coding agent can pick up, to deploy and manage projects end to end.

Where to go next

On this page