claude-code·9 min read·
Deploy a headless Claude Code worker to Railway: a UK indie hacker guide for 2026
When the home PC is the wrong host - webhooks, public endpoints, multi-user triggers - Railway is the cleanest move for a UK indie hacker. Sterling billing, EU regions, a Dockerfile-or-Procfile worker. Here is the full walkthrough, including the storage and env gotchas.

There is a precise moment in every indie hacker's journey when the home PC stops being enough. It is the moment the agent needs to answer to someone other than you. A teammate wants to trigger a code review from Discord. A customer hits a webhook. Three users want the same brief at 7am in three different timezones. The desktop in your spare room cannot reliably serve a public endpoint, and you should not ask it to.
The right next move, for a UK indie hacker, is almost always Railway. Not because it is the most powerful cloud - it is not - but because it is the simplest path from a working Claude Agent SDK script on your laptop to a working Claude Agent SDK worker that other people can poke. This post is the end-to-end walkthrough: why Railway over the alternatives, the Dockerfile you actually want, the storage gotchas, the env handling, the sterling cost shape, and a worked example - a Discord-triggered code review worker - that you can ship over a weekend.
Why Railway, specifically, for UK indie hackers
The three sensible options for a small always-on worker in 2026 are Railway, Fly.io, and Render. Each has their fans. The case for Railway, for a UK indie hacker shipping a first worker:
Sterling billing without FX surprises. Railway charges in dollars but the dollar-to-sterling conversion on a UK card is clean - no Stripe-currency-converted-back-by-your-bank chaos. If you want to forecast spend in pounds for your bookkeeping, you can. Fly and Render are also in dollars but the dashboard experience is fiddlier for sterling reconciliation.
EU region and GDPR comfort. Railway has a Frankfurt-area EU region. Data, including any user data your worker touches, can stay inside the EEA. For UK indie hackers building products that touch UK or EU customers, that is one fewer awkward conversation with future enterprise buyers.
The deploy model is the simplest. Push to GitHub, Railway detects your Dockerfile, builds, deploys, gives you a *.up.railway.app subdomain. Fly's fly launch exposes more concepts on day one. Render's build pipeline is slower and the dashboard is dated. Railway sits in the goldilocks zone for the first GBP 10/mo of compute.
Sensible pricing curve. Hobby plan is roughly GBP 5/mo of usage credit, free, so a worker that idles overnight often costs nothing on top. Pay only for active CPU minutes and RAM-hours.
The case against: not free-tier-forever like a Vercel cron, and no static IP without Pro tier. If you need a fixed IP for a third-party allow-list, Fly or Hetzner is the better shout.
For the headless side of Claude Code that you are about to deploy, the headless claude -p scripting guide and the Claude Agent SDK quickstart are the two prerequisites worth a quick re-read before you go further.
The Dockerfile you actually want
Railway will happily deploy from a Procfile or a Nixpacks autodetect, but a small explicit Dockerfile is the most reproducible option and the one you should commit. Here is a Node TypeScript worker shape, using the Claude Agent SDK.
FROM node:20-bookworm-slim
WORKDIR /app
# Install build essentials only if you compile native modules
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl tini \
&& rm -rf /var/lib/apt/lists/*
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build
# Run as a non-root user
RUN useradd --create-home --uid 1001 app && chown -R app /app
USER app
EXPOSE 3000
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "dist/server.js"]
Why each line earns its place. node:20-bookworm-slim is small (around 70MB) and pulls fast. tini as PID 1 forwards SIGTERM properly on redeploys, so in-flight Agent SDK calls get a clean shutdown. The non-root user is not theatre - if your worker takes a public webhook, you want process privileges minimised.
For Python: python:3.12-slim, copy requirements.txt, pip install --no-cache-dir, copy code, non-root user, tini as PID 1.
Commit the Dockerfile to the root of your repo. Push to GitHub. In Railway's dashboard, "New Project" - "Deploy from GitHub" - pick the repo. First build takes around 90 seconds; subsequent ones cache and run in 30.
The Agent SDK worker itself
A complete-enough server skeleton. This is the bit that turns an HTTP request into an agent run.
import express from "express";
import { query } from "@anthropic-ai/claude-agent-sdk";
const app = express();
app.use(express.json());
app.get("/health", (_req, res) => {
res.status(200).json({ ok: true, ts: new Date().toISOString() });
});
app.post("/review", async (req, res) => {
const { repoUrl, pr } = req.body;
if (!repoUrl || !pr) {
return res.status(400).json({ error: "repoUrl and pr required" });
}
const prompt = `
Clone ${repoUrl}, check out PR #${pr}, review the diff
for security, correctness, and clarity. Return a JSON
summary with "verdict" (PASS|NOTES|BLOCK) and "findings".
`;
try {
const stream = query({
prompt,
options: {
model: "claude-opus-4-7",
cwd: "/data/workspaces",
allowedTools: ["Bash", "Read", "Write", "Grep"],
maxTurns: 12,
},
});
let final = "";
for await (const msg of stream) {
if (msg.type === "assistant") final = msg.message.content;
}
res.status(200).json({ result: final });
} catch (err) {
console.error("review failed", err);
res.status(500).json({ error: String(err) });
}
});
const port = Number(process.env.PORT) || 3000;
app.listen(port, () => console.log(`worker listening on ${port}`));
Three things to note. The /health endpoint is what keeps Railway from sleeping your service - configure it as the health check path in the Railway service settings. The cwd: "/data/workspaces" writes any cloned repos into a mounted Volume (next section), so they survive within the run but do not bloat your image. The maxTurns: 12 is critical - without it an agent that hits a tight loop will burn through your daily token budget in minutes. Set it.
Storage: volumes vs Postgres vs Supabase
This is the gotcha that catches every UK indie hacker the first time. Railway containers are ephemeral. Anything written to /app, /tmp, or anywhere else on the container disk vanishes on redeploy, on a crash, or when Railway shuffles your container between hosts. If your worker writes a state.json thinking it will be there next time, it will not.
Three sensible options for persistent state:
Railway Volumes. Attach a Volume to a path like /data. Anything you write there survives redeploys. Pricing is per GB-month, cheap (pennies for a few GB). Right for: scratch workspaces, cloned repos, log files, small JSON state. Wrong for: anything you want to query, anything you want to back up cleanly.
Railway Postgres. One-click add-on, GBP 1-2/mo for a small instance, ready in 90 seconds. Right for: conversation history, user records, queue state, anything that benefits from SQL. Wrong for: things you want to share with other services outside Railway.
Existing Supabase project. If you already have a Supabase project for your main app, point your Railway worker at the same project via service-role key. One source of truth across your stack, and you get the auth, storage, and realtime primitives for free. This is the move for most UK indie hackers who have already shipped a Next.js front end on Supabase.
The rule of thumb: container disk is scratch. Volumes are for files. Postgres or Supabase is for everything else. Mix them - a Volume for the active workspace plus Postgres for the run log is a clean shape.
Environment variables: do this properly the first time
Your Railway worker needs at minimum ANTHROPIC_API_KEY. Probably also a GitHub PAT, a Resend key, a Discord webhook secret. Three rules:
Set them in the Railway dashboard, not in a .env committed to the repo. Settings - Variables - "Raw Editor" lets you paste a .env-style block in one go. Each variable is encrypted at rest and exposed to your process at start.
Use a separate Anthropic API key per worker. Once you have more than one running worker, per-worker keys give you per-worker spend visibility in the Anthropic console. From 15 June 2026 the programmatic credit pool is shared across all your keys, but you still want per-worker daily caps so a runaway agent does not eat the lot.
Audit secrets on every deploy. Before you push to main, run git grep -i "sk-ant\|ghp_\|re_\|xoxb" in the project root. If anything matches, scrub it before push - Railway logs are not the right place for a leaked key.
For broader Claude Code workflow context, the hooks guide is worth a glance: any local hook scripts that fire on Bash or Edit will also fire inside a Railway worker if they are committed to the repo. Make sure none of them shell out to interactive tools or your worker will hang on a missing TTY.
Cost shape, sterling-first
Honest numbers for a UK indie hacker on Railway with one Claude Agent SDK worker:
- Container compute (512MB RAM, idle most of the day, fires 10 short runs a day): around GBP 3 to GBP 5 per month, often inside the GBP 5 Hobby free credit.
- Volume (1GB attached): GBP 0.15 per month, near rounding-error.
- Postgres (smallest tier, if you take it): GBP 1 to GBP 2 per month.
- Anthropic API spend for the agent itself (10 short runs/day, Sonnet, around 5k tokens each): around GBP 1.50 to GBP 4 per month.
All-in for a real production worker: GBP 6 to GBP 12 per month. That is the price of a single coffee a week for a piece of always-on infrastructure that takes webhooks, does real work, and survives redeploys cleanly. Set a Railway spend limit (Settings - Usage - Spend Limit) at, say, GBP 25/mo as a safety net. Set a hard daily spend cap on your Anthropic key in the Anthropic console. Belt, braces.
The worked example: a Discord-triggered code review worker
Concrete shippable build. You and three friends are working on a UK indie SaaS. You all open PRs. You want a button in your Discord channel that says "review this PR" and an agent that posts back the verdict in three minutes.
The pieces:
- A Discord bot registered with a slash command
/review <repo> <pr-number>. - A Railway worker exposing
POST /reviewwith the Express skeleton above. - A tiny Discord bot client that, on
/review, POSTs to the Railway worker and posts the JSON response back to the channel. GITHUB_TOKENin the worker's environment, scoped to read on the relevant repos.
The shape, end to end:
- Friend opens PR #42 on
my-org/my-app. - Friend types
/review my-app 42in Discord. - Discord bot POSTs to
https://my-worker.up.railway.app/review. - Worker spins up an agent, clones the repo into
/data/workspaces, checks out the PR, reads the diff, runsnpm test, evaluates security and correctness, returns a verdict. - Discord bot posts the verdict back as a threaded reply.
Time budget per review: around 90 to 180 seconds depending on diff size. Token spend: 5p to 30p per review at Opus pricing, less on Sonnet. Monthly all-in for a team of four pushing ten PRs a week: under GBP 15. Compare that to a paid code-review SaaS at GBP 30-80 per seat per month and you have justified the build in week one.
A worth-knowing wrinkle: Discord's slash-command response window is three seconds. Use a deferred response - acknowledge in under three seconds and edit the message with the final verdict when the agent finishes. Discord.js's interaction.deferReply() handles this in one line.
For patterns on chaining the review worker with other scheduled agents - a nightly QA pass, a weekly dependency scan - see the Claude Code multi-agent orchestration guide. The Railway worker is a natural target for those patterns: a single endpoint that several orchestrators can call.
When Railway is not the right answer
Skip Railway if the job runs once a day and only you trigger it (Task Scheduler wins), if it is genuinely heavyweight - multi-GB workspaces, hour-long runs (Hetzner is cheaper), or if you need a static IP for a third-party allow-list (Fly.io's dedicated IPs).
For everything else - a webhook the public can hit, a queue worker, a Slack or Discord bot, a scheduled job that needs to outlive your PC's downtime - Railway is the boring correct answer for a UK indie hacker in 2026. Cheap, sterling-billed, EU-region, deploy in ten minutes, sleep at night.
New here? IdeaStack publishes one deeply researched UK business opportunity every Thursday - real keyword data, competitor analysis, builder prompts. See the latest free report.
Frequently asked
Why Railway over Fly.io or Render for a UK Claude Code worker?
Railway bills in sterling on a UK-issued card without the FX faff, has an EU region in Amsterdam that is fine for GDPR, and the deploy model is the simplest of the three. Fly is more powerful but the CLI is heavier and the pricing model needs more thought. Render is fine but slower to deploy and feels older. For a UK indie hacker shipping a first worker, Railway is the lowest-friction option.
How much does a small Claude Code worker cost on Railway per month?
A small Node or Python worker that handles a few webhook hits a day, idles otherwise, and uses 512MB of RAM costs around GBP 3 to GBP 8 a month on Railway's usage-based pricing. The Hobby plan gives you GBP 5 of credits a month free, which often covers a single worker entirely. Persistent volumes and database add-ons are extra and small, typically under GBP 2 a month for an indie project.
Where does my Claude Agent SDK worker store state on Railway?
Railway containers are ephemeral, so anything written to the local disk vanishes on redeploy. For small state, attach a Railway Volume to a path like /data and write JSON files there. For richer state - conversation history, user records, queues - use a Railway-managed Postgres add-on or point at an existing Supabase project. Treat the container disk as scratch only and you will avoid the 'where did my logs go' confusion.
Do I need a Claude Code Pro plan to run a Railway worker?
No. Claude Code Pro covers interactive use of the CLI. A Railway worker calls the Claude Agent SDK programmatically, which bills on your Anthropic API key. From 15 June 2026 programmatic usage moves to a separate monthly credit pool capped at your subscription price. Set a hard daily spend cap on the key in the Anthropic console before you deploy anything to a public endpoint.
How do I expose a health check and avoid Railway killing my worker?
Add a tiny HTTP server to your worker that responds 200 OK on /health. Configure Railway's health check to hit that path every 30 seconds. Workers without any HTTP server can be classed as sleeping and scaled to zero, which is sometimes what you want and sometimes not. For a webhook-driven Discord or Telegram worker you need always-on, so the health endpoint is what keeps Railway happy.
Filed under




