railway — Cursor rule: what it does and how to install
This rule guides Railway app setup, covering service configuration, external state storage, health checks, and secure handling of secrets.
.cursor/rules/railway.mdcWhat railway does
The rule explains how to define monorepo services in railway.json, keep service settings in railway.yaml, and use Nixpacks for repeatable builds. It recommends storing application state in external services such as Redis and routing communication between Railway services through the private network.
For Node.js apps, it covers handling SIGTERM and SIGINT so the server can close connections before exit. It also addresses a /health endpoint, Railway-managed secrets, and structured logs sent to stdout and stderr. Without /health, Railway may treat a deployment as failed or restart the service.
What it helps with
- define Railway services in a monorepo
- move application state to external Redis
- handle shutdown signals in a Node.js server
- add a
/healthcheck endpoint - store API keys as Railway secrets
Example prompts for the agent
- Review this monorepo’s setup for deployment on Railway.
- Change session storage so data survives a Railway service restart.
- Add
SIGTERMhandling and a/healthendpoint to this Node.js app.
How to install railway
Manually: put the files in place
.cursor/rules/railway.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/railway.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/railway.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/railway.mdManually: put the files in place
.clinerules/railway.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about railway
How do I install railway in Cursor?
Copy the rule file to .cursor/rules/railway.mdc.
What does the railway rule do?
It gives Railway-focused guidance on service configuration, external state, secrets, health checks, and graceful shutdown.
Why does a Railway app need a /health endpoint?
Railway uses it to check whether the service is running. Without the endpoint, a deployment can fail or the service may restart unnecessarily.
Source text
railway.mdc · CC0-1.0
---
description: This guide provides definitive best practices for developing and deploying applications on Railway, focusing on stateless services, reproducible builds with Nixpacks, secure configuration, and optimal performance.
globs: **/*
---
# railway Best Practices
Railway is a powerful PaaS for modern applications. Adhere to these guidelines to ensure your services are performant, reliable, and easily deployable.
## 1. Code Organization and Structure
Organize your codebase for clarity and Railway's build system. For monorepos, explicitly define service directories.
### ✅ GOOD: Explicit Service Definition (Monorepo)
Use `railway.json` to define services, ensuring Railway builds and deploys the correct subdirectories.
```json
// railway.json
{
"$schema": "https://railway.app/railway.schema.json",
"services": [
{
"name": "backend",
"path": "./backend",
"buildCommand": "npm install && npm run build",
"startCommand": "npm start"
},
{
"name": "frontend",
"path": "./frontend",
"buildCommand": "npm install && npm run build",
"startCommand": "npm start"
}
]
}
```
## 2. Common Patterns and Anti-patterns
Embrace statelessness and graceful shutdowns for scalable, resilient services.
### ❌ BAD: Stateful Services
Storing session data or temporary files directly on the server instance. This breaks horizontal scaling and leads to data loss on redeployments or scale-downs.
```typescript
// app.ts (BAD)
let inMemoryCache = {}; // Will be lost on scale-down/redeploy
```
### ✅ GOOD: Stateless Design
Persist state in external services (databases, Redis, S3). Your application must be able to start, stop, and scale without losing data or affecting other instances.
```typescript
// app.ts (GOOD)
import { createClient } from 'redis'; // Use an external Redis instance
const redisClient = createClient({
url: process.env.REDIS_URL // Connect to Railway-provisioned Redis
});
async function getFromCache(key: string) {
await redisClient.connect();
const value = await redisClient.get(key);
await redisClient.disconnect();
return value;
}
```
### ✅ GOOD: Graceful Shutdowns (Node.js Example)
Handle `SIGTERM` signals to ensure your application cleans up resources (e.g., close database connections, flush logs) before termination, preventing data corruption or dropped requests.
```typescript
// server.ts
import express from 'express';
// Assume connectToDatabase and disconnectFromDatabase exist
import { connectToDatabase, disconnectFromDatabase } from './db';
const app = express();
const PORT = process.env.PORT || 3000;
let server: any;
async function startServer() {
await connectToDatabase();
server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
}
function handleShutdown() {
console.log('SIGTERM received. Shutting down gracefully...');
server.close(async () => {
console.log('HTTP server closed.');
await disconnectFromDatabase();
process.exit(0);
});
// Force close if server hasn't closed after a timeout
setTimeout(() => {
console.error('Forcing shutdown after timeout.');
process.exit(1);
}, 10000); // 10 seconds
}
process.on('SIGTERM', handleShutdown);
process.on('SIGINT', handleShutdown); // Also handle Ctrl+C for local dev
startServer();
```
## 3. Performance Considerations
Optimize for low latency and efficient resource usage.
### ✅ GOOD: Private Networking for Internal Services
Always use Railway's private network for communication between services (e.g., app to database, app to cache). This reduces latency and eliminates egress costs.
```typescript
// database.ts
// ❌ BAD: Public endpoint (introduces latency, egress costs)
// const DB_HOST = 'your-db-public-url.railway.app';
// ✅ GOOD: Private network endpoint (fast, free internal traffic)
const DB_HOST = process.env.PGHOST || 'postgres.railway.internal'; // Or redis.railway.internal, mongo.railway.internal
const DB_PORT = process.env.PGPORT || 5432;
```
### ✅ GOOD: Region Selection
Deploy your application in a region geographically close to your primary user base to minimize latency.
## 4. Common Pitfalls and Gotchas
Avoid common mistakes that lead to deployment failures or runtime issues.
### ❌ BAD: Missing Health Checks
Railway needs a `/health` endpoint to determine if your service is running correctly. Without it, deployments may fail or services restart unnecessarily.
### ✅ GOOD: Implement a Basic Health Endpoint
```typescript
// app.ts
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
```
## 5. Configuration Management
Treat your configuration as code and manage secrets securely.
### ✅ GOOD: Config-as-Code with `railway.yaml`
Define environment variables, secrets, and service configurations directly in version control. This ensures reproducibility across environments.
```yaml
# railway.yaml
name: My Awesome Project
services:
- name: web
repo: .
buildCommand: npm install && npm run build
startCommand: npm start
variables:
NODE_ENV: production
secrets:
API_KEY: ${{ secrets.API_KEY }} # Reference secrets securely
```
### ✅ GOOD: Use Nixpacks for Reproducible Builds
Leverage Nixpacks for declarative, reproducible builds. Only use custom Dockerfiles if Nixpacks cannot infer your build process or if you have highly specialized requirements.
```toml
# nixpacks.toml (Example for a Python app needing specific system packages)
[phases.setup]
nixPkgs = ["python3", "postgresql"] # Add system-level dependencies here
```
## 6. Environment Variables
Manage environment variables and secrets securely.
### ❌ BAD: Hardcoding Secrets or Committing `.env`
Never commit sensitive information directly to your repository. This is a major security vulnerability.
```typescript
// config.ts (BAD)
const API_KEY = "super-secret-key-123";
```
### ✅ GOOD: Use Railway Secrets
Store secrets directly in Railway's dashboard or via `railway variables set --project <id> API_KEY=...` and reference them in `railway.yaml`. Access them via `process.env` at runtime.
```typescript
// config.ts (GOOD)
const API_KEY = process.env.API_KEY; // Railway injects this securely
```
## 7. Logging
Centralize logs to Railway's platform for easy debugging and monitoring.
### ✅ GOOD: Log to Standard Output (Stdout/Stderr)
Railway automatically collects logs written to `stdout` and `stderr`. Use structured logging (JSON) for easier parsing and integration with external log analysis tools.
```typescript
// logger.ts
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
timestamp: pino.stdTimeFunctions.isoTime,
});
// ✅ GOOD: Structured JSON log for easy parsing
logger.info({ event: 'user_login', userId: 'abc-123', ip: '192.168.1.1' }, 'User logged in');
// ❌ BAD: Unstructured log (hard to parse programmatically)
// console.log('User logged in');
```
## 8. Testing Approaches
Integrate automated testing into your CI/CD pipeline.
### ✅ GOOD: Automated CI/CD with GitHub Actions
Run unit, integration, and end-to-end tests automatically on every push or pull request *before* deploying to Railway. This ensures code quality and prevents regressions.
```yaml
# .github/workflows/deploy.yml
name: Deploy to Railway
on:
push:
branches:
- main
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test # ✅ Ensure tests pass before deployment
- name: Deploy to Railway
if: success() # Only deploy if tests pass
uses: railwayapp/github-action@v3
with:
token: ${{ secrets.RAILWAY_TOKEN }}
service: backend # Or use railway.json for multiple services
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc