Deploy a personal OpenClaw assistant on Railway with persistent storage.
- A Railway account.
- An API key from a supported model provider. This guide uses OpenAI. Anthropic, Gemini, OpenRouter, and others work the same way — see the OpenClaw models documentation.
Never commit tokens or API keys to your fork.
Two ways to get the same result: a public domain and a healthy /healthz. Use the Railway dashboard, or clone your fork and let a coding agent follow the included skill.
The container exposes only Caddy on port 8080; OpenClaw listens on a loopback-only internal port.
Caddy removes inbound proxy and Tailscale identity headers, rebuilds forwarding information from
Railway's overwritten X-Real-IP, and then sends the normalized request to OpenClaw. Health checks
without X-Real-IP use their direct connection address. This avoids depending on Railway's internal
proxy IP ranges. It assumes public ingress overwrites X-Real-IP and that no untrusted sibling service
can reach port 8080 directly over the Railway private network.
- Fork this repository.
- In Railway, choose New Project → Deploy from GitHub repo and select your fork.
The first deployment usually fails. That is expected: the Volume, token, and public domain below are not configured yet.
OpenClaw stores configuration, chat history, and paired browser sessions on disk. The service will not start without a Volume mounted at exactly:
/data
On the Railway Project Canvas, right-click the OpenClaw service card, choose Attach Volume, and set that mount path.
Open the service Variables tab and add:
-
OPENCLAW_GATEWAY_TOKEN: a long random secret. Generate one with:openssl rand -hex 32
Paste the printed value into Railway. Do not commit it.
-
OPENAI_API_KEY: your OpenAI API key.
Optional, only if you want Telegram as well:
TELEGRAM_BOT_TOKEN: message @BotFather, send/newbot, and copy the token.TELEGRAM_ALLOWED_USER_IDS: your numeric Telegram user ID (digits only, not your@username). In Telegram, open @userinfobot, tap Start, and copy theIdit replies with (for example123456789). Comma-separate multiple IDs.
If you add these variables after the first deploy, redeploy.
- Open Settings → Networking → Public Networking.
- Click Generate Domain.
- Set the target port to
8080.
Redeploy and wait for Success. Then open:
https://<your-domain>/healthz
A successful response means the Gateway is running.
This path uses the Railway CLI from your laptop. A coding agent (Claude Code, Cursor, Codex, Grok, and similar) runs the steps in .agents/skills/railway-cli-deploy/SKILL.md.
-
Fork this repository.
-
Clone your fork and open that directory:
git clone https://github.com/<you>/<your-fork>.git cd <your-fork>
-
Install the Railway CLI:
bash <(curl -fsSL https://railway.com/install.sh)Confirm with
railway --version. You do not need to log in first. -
In this repository, paste this prompt to your coding agent:
Deploy this OpenClaw repo to Railway. Follow .agents/skills/railway-cli-deploy/SKILL.md exactly.
The agent will create the Railway project, upload this directory, attach a Volume at /data, generate a public domain on port 8080, set OPENCLAW_GATEWAY_TOKEN, and check https://<domain>/healthz. Complete the browser login if the CLI opens one. When the agent asks, add provider keys (and optional Telegram tokens) in Railway Variables — do not put them in the prompt.
Use either path, or both. Telegram works after the Telegram variables are set; you do not need the Console. Where a command is shown, run it in Railway's Console tab on the OpenClaw service — a shell inside the running container, not your laptop. The Console is available only after the latest deployment is Success.
-
In the Railway Console, generate a one-time Owner link:
openclaw-railway owner-url
-
Open the printed URL immediately. It is short-lived and single-use. It pairs this browser profile as Owner and redirects to model setup. Do not share it, and do not open the bare service URL first.
-
On that model screen, click Test & use. Chat will not start until you do.
If
OPENAI_API_KEYis already set, it appears as OpenAI API key with the noteOPENAI_API_KEY set. You do not need Settings → Model Providers, and you do not paste the key again.The default candidate is
openai/gpt-5.6-sol. If the live test fails because your account cannot use that model, pick another OpenAI model on the same screen and click Test & use again. -
After that, use the normal URL:
https://<your-domain>/Each new browser profile needs a freshly generated Owner link.
This path does not require the Control UI or a Console openclaw models set. Set the Telegram variables first, then message the bot. With your user ID in TELEGRAM_ALLOWED_USER_IDS, it replies directly. The default model is openai/gpt-5.6-sol.
If you set TELEGRAM_BOT_TOKEN but omit TELEGRAM_ALLOWED_USER_IDS, OpenClaw stays on its default pairing policy. The first DM returns a code. Approve it in the Railway Console (replace <CODE>):
openclaw pairing approve telegram <CODE>To use a different model later, run this in the Railway Console:
openclaw models set openai/gpt-5.5To list options:
openclaw models listAll persistent data lives under /data. To download a backup:
railway login
railway ssh --service openclaw -- tar -C /data -czf - . > openclaw-backup.tar.gzReplace openclaw with your service name if it is different. The archive can contain credentials, conversations, and pairing state. Keep it private.
- Official image (default): leave
OPENCLAW_IMAGE_TAGunset to use the repository's tested stable tag, currently2026.8.1. To pin another version, setOPENCLAW_IMAGE_TAGto a tagged OpenClaw image — the tag only, not a digest or architecture suffix. - Build from source: set
OPENCLAW_BUILD_MODE=sourceandOPENCLAW_GIT_REF=<commit-sha-or-branch>. Source builds need more memory and time.
Persistent data in /data survives image changes.