diff --git a/apps/web/src/app/docs/executors/page.tsx b/apps/web/src/app/docs/executors/page.tsx
index 62b0197..62aeb1d 100644
--- a/apps/web/src/app/docs/executors/page.tsx
+++ b/apps/web/src/app/docs/executors/page.tsx
@@ -119,7 +119,54 @@ export default function ExecutorsDocsPage(): React.ReactElement {
needs:
-
diff --git a/apps/web/src/app/docs/executors/railway/page.tsx b/apps/web/src/app/docs/executors/railway/page.tsx
new file mode 100644
index 0000000..face0a8
--- /dev/null
+++ b/apps/web/src/app/docs/executors/railway/page.tsx
@@ -0,0 +1,442 @@
+import { Button } from '@tpmjs/ui/Button/Button';
+import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
+import { Icon } from '@tpmjs/ui/Icon/Icon';
+import type { Metadata } from 'next';
+import Link from 'next/link';
+
+import { AppFooter } from '~/components/AppFooter';
+import { AppHeader } from '~/components/AppHeader';
+
+export const metadata: Metadata = {
+ title: 'Deploy to Railway - Custom Executors - TPMJS',
+ description:
+ 'Deploy a TPMJS executor to Railway with one click. Always-on, auto-scaling, with a generous free tier.',
+};
+
+const healthCheck = `curl https://your-executor.up.railway.app/health`;
+
+const healthResponse = `{
+ "status": "ok",
+ "version": "1.0.0",
+ "info": {
+ "runtime": "railway",
+ "timestamp": "2024-01-01T00:00:00.000Z",
+ "region": "us-west1"
+ }
+}`;
+
+const executeExample = `curl -X POST https://your-executor.up.railway.app/execute-tool \\
+ -H "Content-Type: application/json" \\
+ -H "Authorization: Bearer your-api-key" \\
+ -d '{
+ "packageName": "@tpmjs/hello",
+ "name": "helloWorldTool",
+ "version": "latest",
+ "params": { "includeTimestamp": true }
+ }'`;
+
+const cliDeploy = `# Clone the template
+git clone https://github.com/tpmjs/tpmjs.git
+cd tpmjs/templates/railway-executor
+
+# Install Railway CLI
+npm install -g @railway/cli
+
+# Login to Railway
+railway login
+
+# Create a new project and deploy
+railway init
+railway up`;
+
+const envVars = `# Set environment variables via CLI
+railway variables set EXECUTOR_API_KEY=your-secure-key
+railway variables set OPENAI_API_KEY=sk-xxx
+railway variables set DATABASE_URL=postgres://...`;
+
+const localDev = `# Clone the repository
+git clone https://github.com/tpmjs/tpmjs.git
+cd tpmjs/templates/railway-executor
+
+# Run locally
+PORT=3000 node index.js
+
+# Test health endpoint
+curl http://localhost:3000/health`;
+
+const dockerDeploy = `# Build the image
+docker build -t tpmjs-executor .
+
+# Run locally
+docker run -p 3000:3000 -e EXECUTOR_API_KEY=your-key tpmjs-executor`;
+
+export default function RailwayExecutorPage(): React.ReactElement {
+ return (
+
+
+
+
+
+ {/* Breadcrumb */}
+
+
+ Executors
+
+
+ Railway
+
+
+ {/* Header */}
+
+
+
+
+
Deploy to Railway
+
+ Always-on execution with auto-scaling and a free tier
+
+
+
+
+
+ {/* Why Railway */}
+
+
+
+
$5
+
Free monthly credit
+
+
+
+
Auto
+
Scaling built-in
+
+
+
+
+ {/* One-Click Deploy */}
+
+ One-Click Deploy
+
+ Deploy the TPMJS executor to Railway with a single click:
+
+
+
+
+
+
+ Deploy on Railway
+
+
+
+ After deployment, your executor will be available at{' '}
+
+ https://your-project.up.railway.app
+
+
+
+
+ {/* CLI Deploy */}
+
+ Deploy via CLI
+
+ Prefer the command line? Deploy with the Railway CLI:
+
+
+
+
+ {/* Test Your Deployment */}
+
+ Test Your Deployment
+ Verify your executor is running:
+
+ Expected response:
+
+
+
+ {/* Authentication */}
+
+ Add Authentication
+
+
+ Important: Without an API key, anyone can execute tools on your
+ executor. Always set{' '}
+ EXECUTOR_API_KEY in production.
+
+
+
+
+
+ 1
+
+ Go to your Railway project dashboard
+
+
+
+ 2
+
+ Click on your service, then go to "Variables"
+
+
+
+ 3
+
+
+ Add EXECUTOR_API_KEY with a
+ secure random value
+
+
+
+
+ 4
+
+ Railway will automatically redeploy with the new variable
+
+
+
+
+ {/* Environment Variables */}
+
+ Environment Variables
+
+ Add environment variables via the Railway dashboard or CLI:
+
+
+
+ These variables will be available during tool execution.
+
+
+
+ {/* Execute a Tool */}
+
+ Execute a Tool
+
+ Test tool execution with a curl request:
+
+
+
+
+ {/* Local Development */}
+
+ Local Development
+ Run the executor locally for testing:
+
+
+
+ {/* Docker */}
+
+ Docker Deployment
+
+ The template includes a Dockerfile for container deployments:
+
+
+
+ Railway will automatically detect and use the Dockerfile if present.
+
+
+
+ {/* How It Works */}
+
+ How It Works
+
+ The Railway executor runs as an always-on Node.js service:
+
+
+
+
+ 1
+
+
+ Receives tool execution request via HTTP POST to{' '}
+ /execute-tool
+
+
+
+
+ 2
+
+ Creates an isolated temporary directory for the execution
+
+
+
+ 3
+
+
+ Installs the npm package using{' '}
+ npm install
+
+
+
+
+ 4
+
+
+ Loads the tool and calls its{' '}
+ execute() function
+
+
+
+
+ 5
+
+ Returns the result and cleans up the temporary directory
+
+
+
+
+ {/* Security */}
+
+ Security
+
+
+
+
+ Set EXECUTOR_API_KEY to require
+ authentication
+
+
+
+
+ Each tool execution uses an isolated temporary directory
+
+
+
+ Environment variables stored encrypted by Railway
+
+
+
+ All traffic encrypted via HTTPS
+
+
+
+ Auto-restart on failure for high availability
+
+
+
+
+ {/* Pricing */}
+
+ Pricing
+
+ Railway offers usage-based pricing with a generous free tier:
+
+
+
+
+
+ Tier
+ Price
+ Includes
+
+
+
+
+ Free Tier
+ $0/month
+ $5 credit, enough for light usage
+
+
+ Usage-based
+ ~$0.000463/min
+ 0.5 vCPU, 512MB RAM
+
+
+
+
+
+ See{' '}
+
+ Railway Pricing
+ {' '}
+ for current rates.
+
+
+
+ {/* Connect to TPMJS */}
+
+ Connect to TPMJS
+
+ 1. Go to your collection or agent settings on TPMJS
+ 2. Select "Custom Executor" in Executor Configuration
+
+ 3. Enter URL:{' '}
+
+ https://your-project.up.railway.app
+
+
+ 4. Enter your API key (if configured)
+ 5. Click "Verify Connection"
+
+
+
+ {/* Navigation */}
+
+
+
+ Back to Executors
+
+
+ Unsandbox Guide
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/app/docs/executors/unsandbox/page.tsx b/apps/web/src/app/docs/executors/unsandbox/page.tsx
index 715c2e5..795f120 100644
--- a/apps/web/src/app/docs/executors/unsandbox/page.tsx
+++ b/apps/web/src/app/docs/executors/unsandbox/page.tsx
@@ -397,11 +397,11 @@ export default function UnsandboxExecutorPage(): React.ReactElement {
{/* Navigation */}
- Back to Executors
+ Railway Guide
` header. |
+
+### Setting Up API Key Authentication
+
+1. Go to your Railway project dashboard
+2. Click on your service
+3. Go to "Variables" tab
+4. Add `EXECUTOR_API_KEY` with a secure random value
+5. The service will automatically redeploy
+
+### Adding Tool Environment Variables
+
+Pass environment variables that your tools need:
+
+1. In Railway dashboard, go to "Variables"
+2. Add your variables (e.g., `OPENAI_API_KEY`, `DATABASE_URL`)
+3. These will be available during tool execution
+
+Or use the Railway CLI:
+
+```bash
+railway variables set EXECUTOR_API_KEY=your-key
+railway variables set OPENAI_API_KEY=sk-xxx
+railway variables set DATABASE_URL=postgres://...
+```
+
+## Connecting to TPMJS
+
+1. Go to your TPMJS collection or agent settings
+2. In "Executor Configuration", select "Custom Executor"
+3. Enter your executor URL: `https://your-project.up.railway.app`
+4. Enter your API key (if configured)
+5. Click "Verify Connection" to test
+
+## Local Development
+
+```bash
+# Clone the repository
+git clone https://github.com/tpmjs/tpmjs.git
+cd tpmjs/templates/railway-executor
+
+# Run locally
+PORT=3000 node index.js
+
+# Or with an API key
+EXECUTOR_API_KEY=test-key PORT=3000 node index.js
+
+# Test health endpoint
+curl http://localhost:3000/health
+
+# Test tool execution
+curl -X POST http://localhost:3000/execute-tool \
+ -H "Content-Type: application/json" \
+ -d '{
+ "packageName": "@tpmjs/hello",
+ "name": "helloWorldTool",
+ "params": {}
+ }'
+```
+
+## Managing Your Service
+
+### View Logs
+
+```bash
+railway logs
+```
+
+Or view in the Railway dashboard under "Deployments" → select deployment → "Logs"
+
+### Redeploy
+
+```bash
+railway up
+```
+
+Or push to your connected GitHub repository for automatic deployments.
+
+### Scale Resources
+
+1. Go to Railway dashboard
+2. Click on your service
+3. Go to "Settings" tab
+4. Adjust CPU and memory limits
+
+### Custom Domains
+
+1. Go to Railway dashboard
+2. Click on your service
+3. Go to "Settings" tab
+4. Under "Domains", click "Generate Domain" or add a custom domain
+
+## Docker Deployment
+
+If you prefer Docker:
+
+```bash
+# Build the image
+docker build -t tpmjs-executor .
+
+# Run locally
+docker run -p 3000:3000 -e EXECUTOR_API_KEY=your-key tpmjs-executor
+```
+
+Railway will automatically detect and use the Dockerfile if present.
+
+## Security
+
+- Set `EXECUTOR_API_KEY` to require authentication for all requests
+- Tools run in isolated temporary directories
+- Each execution uses a fresh npm install
+- Environment variables are stored encrypted by Railway
+- Network traffic is encrypted via HTTPS
+
+## Pricing
+
+Railway pricing is usage-based with a generous free tier:
+
+- **Free Tier**: $5/month credit (enough for light usage)
+- **Pay-as-you-go**: ~$0.000463/min for 0.5 vCPU, 512MB RAM
+
+See [Railway Pricing](https://railway.app/pricing) for current rates.
+
+**Cost Optimization Tips:**
+- Use the "Sleep" feature for dev environments
+- Set memory limits appropriate for your tools
+- Monitor usage in Railway dashboard
+
+## Comparison: Railway vs Other Platforms
+
+| Feature | Railway | Vercel | Unsandbox |
+|---------|---------|--------|-----------|
+| Deploy method | One-click / CLI | One-click | CLI |
+| Cold starts | None (always-on) | Yes (serverless) | None |
+| Max runtime | Unlimited | 45min / 5hr | Unlimited |
+| Free tier | $5/month credit | Limited | None |
+| Pricing | Per usage | Per compute | Per uptime |
+| Docker support | Yes | No | Yes |
+| Auto-scaling | Yes | Yes | Manual |
+
+## Troubleshooting
+
+### "Connection refused" errors
+- Check that your service is running in Railway dashboard
+- Verify the URL is correct (check "Domains" in settings)
+- Ensure `EXECUTOR_API_KEY` matches if authentication is enabled
+
+### Tool installation failures
+- Check Railway logs for npm errors
+- Verify the package name and version are correct
+- Some packages may need additional system dependencies
+
+### Timeout errors
+- Railway has no timeout limit, but individual tool executions timeout at 2 minutes
+- For longer-running tools, consider increasing the timeout in the executor code
+
+## Support
+
+- [TPMJS Custom Executors Documentation](https://tpmjs.com/docs/executors)
+- [Railway Documentation](https://docs.railway.app)
+- [GitHub Issues](https://github.com/tpmjs/tpmjs/issues)
diff --git a/templates/railway-executor/index.js b/templates/railway-executor/index.js
new file mode 100644
index 0000000..375fc7a
--- /dev/null
+++ b/templates/railway-executor/index.js
@@ -0,0 +1,398 @@
+#!/usr/bin/env node
+/**
+ * TPMJS Executor for Railway
+ *
+ * A lightweight HTTP server that executes TPMJS tools.
+ * Designed for deployment on Railway with zero dependencies.
+ *
+ * API-compatible with the Vercel and Unsandbox executors.
+ */
+
+const http = require('node:http');
+const { execSync, spawn } = require('node:child_process');
+const fs = require('node:fs');
+const path = require('node:path');
+
+const PORT = process.env.PORT || 3000;
+const API_KEY = process.env.EXECUTOR_API_KEY || null;
+
+// CORS headers for cross-origin requests
+const corsHeaders = {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
+};
+
+/**
+ * Send JSON response with proper headers
+ */
+function jsonResponse(res, statusCode, data) {
+ res.writeHead(statusCode, {
+ 'Content-Type': 'application/json',
+ ...corsHeaders,
+ });
+ res.end(JSON.stringify(data));
+}
+
+/**
+ * Validate API key if configured
+ */
+function checkAuth(req) {
+ if (!API_KEY) return true;
+ const authHeader = req.headers.authorization;
+ return authHeader === `Bearer ${API_KEY}`;
+}
+
+/**
+ * Parse JSON request body
+ */
+function parseBody(req) {
+ return new Promise((resolve, reject) => {
+ let body = '';
+ req.on('data', (chunk) => {
+ body += chunk;
+ });
+ req.on('end', () => {
+ try {
+ resolve(body ? JSON.parse(body) : {});
+ } catch (_e) {
+ reject(new Error('Invalid JSON'));
+ }
+ });
+ req.on('error', reject);
+ });
+}
+
+/**
+ * GET /health - Health check endpoint
+ */
+function handleHealth(_req, res) {
+ jsonResponse(res, 200, {
+ status: 'ok',
+ version: '1.0.0',
+ info: {
+ runtime: 'railway',
+ timestamp: new Date().toISOString(),
+ region: process.env.RAILWAY_REGION || 'unknown',
+ },
+ });
+}
+
+/**
+ * Create isolated work directory and package.json
+ */
+function createWorkDir() {
+ const workDir = `/tmp/tpmjs-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`;
+ fs.mkdirSync(workDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(workDir, 'package.json'),
+ JSON.stringify({
+ name: 'tpmjs-execution',
+ private: true,
+ type: 'commonjs',
+ })
+ );
+ return workDir;
+}
+
+/**
+ * Install npm package in work directory
+ */
+function installPackage(workDir, packageSpec) {
+ execSync(`npm install --no-save --omit=dev --no-audit --no-fund ${packageSpec}`, {
+ cwd: workDir,
+ stdio: ['pipe', 'pipe', 'pipe'],
+ timeout: 60000, // 60s timeout for install
+ });
+}
+
+/**
+ * Generate the tool execution script
+ */
+function generateExecutionScript(packageName, name, params, env) {
+ const envSetup = env
+ ? Object.entries(env)
+ .map(([key, value]) => `process.env[${JSON.stringify(key)}] = ${JSON.stringify(value)};`)
+ .join('\n')
+ : '';
+
+ return `
+${envSetup}
+
+(async () => {
+ try {
+ const pkg = require(${JSON.stringify(packageName)});
+
+ // Find the tool export - check named export, default.name, or default
+ let tool = pkg[${JSON.stringify(name)}] || pkg.default?.[${JSON.stringify(name)}] || pkg.default;
+
+ if (!tool) {
+ throw new Error(\`Tool "${name}" not found in package "${packageName}"\`);
+ }
+
+ // Handle factory functions (tools that need to be instantiated)
+ if (typeof tool === 'function' && !tool.execute) {
+ const envVars = ${env ? JSON.stringify(env) : 'null'};
+
+ // Try no-arg call first
+ try {
+ const result = tool();
+ if (result && typeof result.execute === 'function') {
+ tool = result;
+ }
+ } catch {}
+
+ // Try with env config if still a function
+ if (typeof tool === 'function' && envVars) {
+ try {
+ const result = tool(envVars);
+ if (result && typeof result.execute === 'function') {
+ tool = result;
+ }
+ } catch {}
+ }
+ }
+
+ if (!tool || typeof tool.execute !== 'function') {
+ throw new Error(\`Tool "${name}" does not have an execute() function\`);
+ }
+
+ // Execute the tool
+ const result = await tool.execute(${JSON.stringify(params)});
+ process.stdout.write(JSON.stringify({ __tpmjs_result__: result }));
+ } catch (err) {
+ process.stderr.write(JSON.stringify({ __tpmjs_error__: err.message || String(err) }));
+ process.exitCode = 1;
+ }
+})();
+`.trim();
+}
+
+/**
+ * Run execution script and return results
+ */
+function runScript(workDir, env) {
+ return new Promise((resolve) => {
+ const child = spawn('node', ['execute.cjs'], {
+ cwd: workDir,
+ env: { ...process.env, ...env },
+ timeout: 120000, // 2 minute timeout
+ });
+
+ let stdout = '';
+ let stderr = '';
+
+ child.stdout.on('data', (data) => {
+ stdout += data;
+ });
+ child.stderr.on('data', (data) => {
+ stderr += data;
+ });
+
+ child.on('close', (code) => {
+ resolve({ exitCode: code, stdout, stderr });
+ });
+
+ child.on('error', (err) => {
+ resolve({ exitCode: 1, stdout: '', stderr: err.message });
+ });
+ });
+}
+
+/**
+ * Clean up work directory
+ */
+function cleanup(workDir) {
+ try {
+ fs.rmSync(workDir, { recursive: true, force: true });
+ } catch (_e) {
+ // Ignore cleanup errors
+ }
+}
+
+/**
+ * Parse execution result and determine response
+ */
+function parseExecutionResult(result, startTime) {
+ // Handle execution failure
+ if (result.exitCode !== 0) {
+ // Try to parse structured error from stderr
+ try {
+ const errorObj = JSON.parse(result.stderr);
+ if (errorObj.__tpmjs_error__) {
+ return {
+ success: false,
+ error: errorObj.__tpmjs_error__,
+ executionTimeMs: Date.now() - startTime,
+ };
+ }
+ } catch (_e) {
+ // Not structured error
+ }
+
+ return {
+ success: false,
+ error: result.stderr || `Script exited with code ${result.exitCode}`,
+ executionTimeMs: Date.now() - startTime,
+ };
+ }
+
+ // Parse the result
+ try {
+ const parsed = JSON.parse(result.stdout);
+ if (parsed.__tpmjs_result__ !== undefined) {
+ return {
+ success: true,
+ output: parsed.__tpmjs_result__,
+ executionTimeMs: Date.now() - startTime,
+ };
+ }
+ } catch (_e) {
+ // Not structured result
+ }
+
+ // Return raw output
+ return {
+ success: true,
+ output: result.stdout || null,
+ stderr: result.stderr || undefined,
+ executionTimeMs: Date.now() - startTime,
+ };
+}
+
+/**
+ * POST /execute-tool - Execute a TPMJS tool
+ */
+async function handleExecuteTool(req, res) {
+ const startTime = Date.now();
+
+ // Check authorization
+ if (!checkAuth(req)) {
+ return jsonResponse(res, 401, {
+ success: false,
+ error: 'Unauthorized',
+ executionTimeMs: Date.now() - startTime,
+ });
+ }
+
+ // Parse request body
+ let body;
+ try {
+ body = await parseBody(req);
+ } catch (_e) {
+ return jsonResponse(res, 400, {
+ success: false,
+ error: 'Invalid JSON body',
+ executionTimeMs: Date.now() - startTime,
+ });
+ }
+
+ const { packageName, name, version = 'latest', params = {}, env } = body;
+
+ // Validate required fields
+ if (!packageName || !name) {
+ return jsonResponse(res, 400, {
+ success: false,
+ error: 'Missing required fields: packageName, name',
+ executionTimeMs: Date.now() - startTime,
+ });
+ }
+
+ const packageSpec = `${packageName}@${version}`;
+ const workDir = createWorkDir();
+
+ try {
+ // Install the npm package
+ console.log(`[executor] Installing ${packageSpec}...`);
+ const installStart = Date.now();
+
+ try {
+ installPackage(workDir, packageSpec);
+ } catch (installError) {
+ console.error(`[executor] npm install failed:`, installError.message);
+ cleanup(workDir);
+ return jsonResponse(res, 500, {
+ success: false,
+ error: `npm install failed: ${installError.message}`,
+ stderr: installError.stderr?.toString(),
+ executionTimeMs: Date.now() - startTime,
+ });
+ }
+
+ console.log(`[executor] npm install completed in ${Date.now() - installStart}ms`);
+
+ // Generate and write execution script
+ const script = generateExecutionScript(packageName, name, params, env);
+ fs.writeFileSync(path.join(workDir, 'execute.cjs'), script);
+
+ // Run the execution script
+ console.log(`[executor] Running tool ${packageName}/${name}...`);
+ const runStart = Date.now();
+ const result = await runScript(workDir, env);
+ console.log(
+ `[executor] Tool execution completed in ${Date.now() - runStart}ms (exit: ${result.exitCode})`
+ );
+
+ // Cleanup and return result
+ cleanup(workDir);
+ return jsonResponse(res, 200, parseExecutionResult(result, startTime));
+ } catch (error) {
+ cleanup(workDir);
+ return jsonResponse(res, 500, {
+ success: false,
+ error: error.message || String(error),
+ executionTimeMs: Date.now() - startTime,
+ });
+ }
+}
+
+/**
+ * Main HTTP server
+ */
+const server = http.createServer(async (req, res) => {
+ const url = new URL(req.url, `http://localhost:${PORT}`);
+ const pathname = url.pathname;
+
+ // Handle CORS preflight
+ if (req.method === 'OPTIONS') {
+ res.writeHead(200, corsHeaders);
+ return res.end();
+ }
+
+ // Route requests (support both /api/path and /path)
+ if ((pathname === '/api/health' || pathname === '/health') && req.method === 'GET') {
+ return handleHealth(req, res);
+ }
+
+ if ((pathname === '/api/execute-tool' || pathname === '/execute-tool') && req.method === 'POST') {
+ return handleExecuteTool(req, res);
+ }
+
+ // Root path - simple info
+ if (pathname === '/' && req.method === 'GET') {
+ return jsonResponse(res, 200, {
+ name: 'TPMJS Executor',
+ version: '1.0.0',
+ runtime: 'railway',
+ endpoints: {
+ health: 'GET /health',
+ execute: 'POST /execute-tool',
+ },
+ });
+ }
+
+ // 404 for unknown routes
+ jsonResponse(res, 404, { error: 'Not found' });
+});
+
+// Start server
+server.listen(PORT, () => {
+ console.log(`TPMJS Executor running on port ${PORT}`);
+ console.log(`Health: http://localhost:${PORT}/health`);
+ console.log(`Execute: POST http://localhost:${PORT}/execute-tool`);
+ if (API_KEY) {
+ console.log(`Authentication: Required (EXECUTOR_API_KEY is set)`);
+ } else {
+ console.log(`Authentication: None (set EXECUTOR_API_KEY to enable)`);
+ }
+});
diff --git a/templates/railway-executor/package.json b/templates/railway-executor/package.json
new file mode 100644
index 0000000..fd0c75f
--- /dev/null
+++ b/templates/railway-executor/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "tpmjs-executor",
+ "version": "1.0.0",
+ "private": true,
+ "description": "TPMJS Tool Executor for Railway - Deploy your own executor on Railway",
+ "main": "index.js",
+ "scripts": {
+ "start": "node index.js",
+ "dev": "node index.js"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "dependencies": {},
+ "keywords": [
+ "tpmjs",
+ "executor",
+ "railway"
+ ]
+}
diff --git a/templates/railway-executor/railway.json b/templates/railway-executor/railway.json
new file mode 100644
index 0000000..22dda23
--- /dev/null
+++ b/templates/railway-executor/railway.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://railway.app/railway.schema.json",
+ "build": {
+ "builder": "NIXPACKS"
+ },
+ "deploy": {
+ "startCommand": "node index.js",
+ "healthcheckPath": "/health",
+ "healthcheckTimeout": 30,
+ "restartPolicyType": "ON_FAILURE",
+ "restartPolicyMaxRetries": 3
+ }
+}