Guides

Vallkey on Railway: Node.js, Python, Ruby, Java and .NET

Step by step for each language, with and without Docker, tested on real deployments: start commands, railpack.json, and checking every request.

Updated October 2026

Tested on Railway, October 29, 2026 · Node.js 20 · Python 3.13 · Ruby · Java 21 · .NET 10

Vallkey's startup hook sends every request that carries a ghost key to Vallkey, which checks it against your rules and swaps in the real key. Requests without a ghost key are left alone, and your code calls the API exactly as the provider's docs show. This guide sets it up on Railway for each language, the way we tested it on real deployments.

Before you start

  • A ghost key from the dashboard, or from npx vallkey init in your project.
  • In your service's Variables, put the ghost key where the real key was, under the same name, such as OPENAI_API_KEY.
  • Optional: VALLKEY_ENV=production in the same Variables names this place in vallkey status and on the key's Activity tab.

Do not put NODE_OPTIONS, RUBYOPT or JAVA_TOOL_OPTIONS in Railway's variables

Railway uses your service's variables while it builds the app too. With a startup setting there, Railway's own install steps load the hook before it is installed, and the build fails: Cannot find module 'vallkey/auto' for Node.js, a failed gem install bundler for Ruby, Error opening zip file or JAR manifest missing for Java. Put the hook in your start command, a Procfile, railpack.json or a Dockerfile, as below. Python's VALLKEY_AUTO=1 is safe in the variables.

Set it up

Choose your language. Each tab is complete on its own.

Tested: Node.js 20 on Railway, fetch and node:https

  1. Install the package

    Terminal
    npm install vallkey
  2. Load the hook in your start script

    npx vallkey hook offers to change your start script in package.json, and shows the change first:

    package.json
    "scripts": {  "start": "node --require vallkey/auto server.js"}

    Starting with something other than node, such as tsx? Put the setting in front of the command instead. We tested this form with node; it works the same in front of any command:

    package.json
    "scripts": {  "start": "NODE_OPTIONS='--require vallkey/auto' tsx server.ts"}
  3. Deploy

    Push, or run railway up.

Check that it works

Every answer that went through Vallkey carries an x-guard-request-id header. With one real request from your deployed service, the CLI confirms it was that request, through that key:

Terminal
# 1. Call your deployed app once, then copy the x-guard-request-id#    header from the response Vallkey sent back to your service.# 2. Check that exact request:npx vallkey verify KEY_ID --request-id REQUEST_ID ✓ Verified  This exact request went through Vallkey and got a successful answer.
  • On a test key, make a harmless request your rules block, and check it with --expect denied. Seeing it refused proves your rules apply to the deployed service.
  • npx vallkey status lists every place the key is used from. A working service shows up by itself, such as Railway · Node.js 20.20 · production or Railway · Java 21.0 · production.

Every Railway service is a place of its own

Give each service its own ghost key for the same real key (Add another place on the key's page): each gets its own rules, limits and kill switch, and vallkey status shows each one apart.

If something is off

The build fails with Cannot find module 'vallkey/auto'

NODE_OPTIONS is set in the service's variables. Delete it there and put the hook in your start script. The same goes for RUBYOPT (the build fails at gem install bundler) and JAVA_TOOL_OPTIONS (it fails with Error opening zip file).

Java or .NET starts, but requests do not go through Vallkey

The hook file is not in the deployed app, or the setting points to the wrong place. Check that the .vallkey folder is committed and that railpack.json includes it. The path in the setting starts with /app/, where Railway puts your app.

The provider says the API key is invalid

The request went straight to the provider with the ghost key. The hook is not loaded: check the start command or Procfile Railway actually uses, under the service's Settings → Deploy. A custom start command set there replaces your package.json script and Procfile.

No x-guard-request-id header

That request did not go through Vallkey. Add VALLKEY_DEBUG=1 to the variables to log each request the hook sends to Vallkey (never the key), then read the deploy logs.

More

Deploy with a key that cannot be misused

Your services call APIs with a ghost key, inside your rules and budget. The real key never reaches Railway. Free plan, no card needed.