A kubectl-style CLI for Railway.app - manage your Railway infrastructure with familiar, powerful commands.
Download the latest binary from the Releases page:
# Linux (amd64)
curl -Lo railctl https://github.com/kubenoops/railctl/releases/latest/download/railctl-linux-amd64
chmod +x railctl
sudo mv railctl /usr/local/bin/
# Linux (arm64)
curl -Lo railctl https://github.com/kubenoops/railctl/releases/latest/download/railctl-linux-arm64
chmod +x railctl
sudo mv railctl /usr/local/bin/
# macOS (Apple Silicon)
curl -Lo railctl https://github.com/kubenoops/railctl/releases/latest/download/railctl-darwin-arm64
chmod +x railctl
sudo mv railctl /usr/local/bin/
# macOS (Intel)
curl -Lo railctl https://github.com/kubenoops/railctl/releases/latest/download/railctl-darwin-amd64
chmod +x railctl
sudo mv railctl /usr/local/bin/go install github.com/kubenoops/railctl/cmd/railctl@latestgit clone https://github.com/kubenoops/railctl.git
cd railctl
make build
sudo mv railctl /usr/local/bin/# Get your API token from https://railway.app/account/tokens
export RAILWAY_TOKEN=your-api-token-hererailctl automatically detects the token type on first use — no extra configuration needed:
| Token type | What it can do |
|---|---|
| Account (personal) | Access all workspaces and projects |
| Workspace-scoped | Access all projects in one workspace |
| Project-scoped | Access one project and environment |
When using a workspace or project token, flags like -w, -p, and -e (or their RAILCTL_* equivalents) are ignored with a warning — the scope is already baked into the token.
# List all projects
railctl get projects
# Create a new project
railctl create project my-app
# List services in production
railctl get services -p my-app -e production
# Deploy a new service
railctl create service api --image node:20-alpine -p my-app
# Update service image (triggers deployment)
railctl update service api --image node:20 -p my-app -e production- CI/CD & Build Setup - Build system, GitHub Actions workflows, release lifecycle, and dependency management
- Testing Architecture - Three-tier testing strategy, mock patterns, E2E test harness, and coverage matrix
- Railway Service Creation Behavior - Understanding Railway's automatic service instance creation and our workaround
Manage your Railway infrastructure as code with YAML config files:
# Define your stack
cat > stack.yaml <<EOF
services:
- name: api
image: node:20-alpine
deploy:
startCommand: "npm start"
replicas: 2
networking:
domain:
port: 3000
variables:
PORT: "3000"
DATABASE_URL: "${{postgres.DATABASE_URL}}"
EOF
# Preview changes
railctl diff -f stack.yaml -p my-app -e production
# Apply changes
railctl apply -f stack.yaml -p my-app -e production
# Apply with deployment wait
railctl apply -f stack.yaml -p my-app -e production --awaitSee Declarative Configuration Reference for the full schema.
# List all projects
railctl get projects
railctl get projects -o wide # Show more details
railctl get projects -o json # JSON output
railctl get projects -o yaml # YAML output
# Describe a specific project
railctl describe project my-app
# Create a new project
railctl create project my-new-app
# Delete a project (with confirmation)
railctl delete project old-app
railctl delete project old-app --yes # Skip confirmation# List environments in a project
railctl get environments -p my-app
railctl get envs -p my-app # Short alias
# Create an environment
railctl create environment staging -p my-app
# Delete an environment
railctl delete environment staging -p my-app --yes# List services in an environment
railctl get services -p my-app -e production
railctl get svc -p my-app -e production # Short alias
# Describe a service
railctl describe service api -p my-app -e production
# Create a service from Docker image
railctl create service api --image node:20-alpine -p my-app
# Create with deployment configuration
railctl create service api --image node:20 \
--start-command "npm start" \
--restart-policy ON_FAILURE \
--max-retries 3 \
--replicas 2 \
-p my-app
# Create with health checks for zero-downtime deployments
railctl create service api --image node:20 \
--healthcheck-path /health \
--healthcheck-timeout 60 \
-p my-app
# Update service image (triggers new deployment)
railctl update service api --image node:20 -p my-app -e production
# Update deployment configuration
railctl update service api \
--restart-policy ON_FAILURE \
--max-retries 5 \
--replicas 3 \
-p my-app -e production
# Delete a service
railctl delete service api -p my-app -e production --yesControl how your services run and scale:
| Flag | Description | Valid Values |
|---|---|---|
--start-command |
Override container's default start command | Any string |
--restart-policy |
Control restart behavior | ON_FAILURE, ALWAYS, NEVER |
--max-retries |
Maximum restart attempts (requires --restart-policy) |
Integer >= 0 |
--replicas |
Number of instances (horizontal scaling) | Integer >= 1 |
--healthcheck-path |
HTTP endpoint for health checks | Path (e.g., /health) |
--healthcheck-timeout |
Max seconds to wait for health check | Integer (default: 300) |
Note: These flags are available for both create service and update service commands.
# View recent deployment logs
railctl logs service api -p my-app -e production
# View last 50 log lines
railctl logs service api --tail 50 -p my-app -e production
# Follow logs in real-time (like tail -f)
railctl logs service api -f -p my-app -e production
# View logs from specific deployment
railctl logs service api --deployment abc123 -p my-app -e production# List volumes in an environment
railctl get volumes -p my-app -e production
railctl get volumes -o wide # Show volume IDs and detailed sizes
railctl get volumes -o json # JSON output
# Create a volume attached to a service
railctl create volume --mount-path /app/data -s backend -p my-app -e production
railctl create volume my-data --mount-path /app/uploads -s api -p my-app -e production
# Update volume properties
railctl update volume my-data --name uploads -p my-app -e production
railctl update volume my-data --mount-path /app/uploads -p my-app -e production
railctl update volume my-data --attach -s backend -p my-app -e production
railctl update volume my-data --detach -p my-app -e production
# Delete a volume
railctl delete volume my-data -p my-app -e production
railctl delete volume my-data --yes -p my-app -e production # Skip confirmation# List variables for a service
railctl get variables -p my-app -e production -s api
railctl get vars -p my-app -e production -s api # Short alias
# Set a single variable (triggers deployment)
railctl set variable DATABASE_URL=postgres://... -p my-app -e production -s api
# Set multiple variables at once
railctl set variable API_KEY=abc123 DEBUG=true -p my-app -e production -s api
# Set variables without triggering deployment
railctl set variable FEATURE_FLAG=enabled --skip-deployment -p my-app -e production -s api
# Delete a variable
railctl delete variable OLD_KEY -p my-app -e production -s api --yesAvoid repeating flags by setting context variables:
# Set your working context
export RAILCTL_WORKSPACE=my-team
export RAILCTL_PROJECT=my-app
export RAILCTL_ENVIRONMENT=production
export RAILCTL_SERVICE=api
# Now commands are much shorter
railctl get projects
railctl get variables
railctl set variable NEW_VAR=value
railctl delete variable OLD_VAR --yesDeploy from private registries (requires Railway Pro plan):
# Using flags
railctl create service app \
--image registry.example.com/myapp:v1 \
--registry-username user \
--registry-password token \
-p my-project
# Using environment variables (recommended)
export RAILCTL_REGISTRY_USERNAME=user
export RAILCTL_REGISTRY_PASSWORD=token
railctl create service app --image registry.example.com/myapp:v1 -p my-project
# Update with new image from private registry
railctl update service app \
--image registry.example.com/myapp:v2 \
--registry-username user \
--registry-password token \
-p my-project -e productionThese flags are available on every command:
| Flag | Short | Description |
|---|---|---|
--token |
Railway API token (default: RAILWAY_TOKEN env var) |
|
--workspace |
-w |
Workspace name (default: RAILCTL_WORKSPACE env var) |
--project |
-p |
Project name (default: RAILCTL_PROJECT env var) |
--environment |
-e |
Environment name (default: RAILCTL_ENVIRONMENT env var) |
--service |
-s |
Service name (default: RAILCTL_SERVICE env var) |
--output |
-o |
Output format: table, wide, json, yaml (default: table) |
| Variable | Description | Example |
|---|---|---|
RAILWAY_TOKEN |
Railway API token (required) | frp_xxxxxxxxx |
RAILCTL_PROJECT |
Default project name/ID | my-app |
RAILCTL_ENVIRONMENT |
Default environment name/ID | production |
RAILCTL_SERVICE |
Default service name/ID | api |
RAILCTL_REGISTRY_USERNAME |
Docker registry username | myuser |
RAILCTL_REGISTRY_PASSWORD |
Docker registry password | mytoken |
| Variable | Description | Example |
| ---------- | ------------- | --------- |
RAILWAY_TOKEN |
Railway API token (required) | frp_xxxxxxxxx |
RAILCTL_WORKSPACE |
Default workspace name (required when multiple workspaces exist) | my-team |
RAILCTL_PROJECT |
Default project name | my-app |
RAILCTL_ENVIRONMENT |
Default environment name | production |
RAILCTL_SERVICE |
Default service name | api |
RAILCTL_REGISTRY_USERNAME |
Docker registry username | myuser |
RAILCTL_REGISTRY_PASSWORD |
Docker registry password | mytoken |
All get and describe commands support multiple output formats:
- Table (default) - Human-readable tabular output
- Wide (
-o wide) - Table with additional columns - JSON (
-o json) - Machine-readable JSON - YAML (
-o yaml) - YAML format
The examples/ directory contains production-ready deployment templates
that show how to deploy real-world stacks on Railway using railctl:
| Example | Description | Services |
|---|---|---|
| n8n | n8n workflow automation in queue mode | PostgreSQL, Redis, n8n Primary, n8n Worker (×2) |
| Temporal | Temporal durable workflow engine | PostgreSQL, Temporal Server, Temporal UI, Worker (×2) |
Each example includes:
- Declarative YAML config files for every service
- A shared
deploy.shscript that handles idempotent create-or-update - Cleanup scripts for full teardown
.envrc.exampletemplates for secrets- Detailed README with architecture diagrams
# Quick start with the n8n example
cd examples/n8n
cp .envrc.example .envrc
# Edit .envrc with your Railway token and secrets
source .envrc
./deploy.sh- Go 1.22 or higher
- Railway API token
# Build (version auto-detected from git tags)
make build
# Build with specific version
make build VERSION=v1.0.0
# Build for all platforms (linux/darwin, amd64/arm64)
make build-all
# Install to GOPATH/bin
make install
# Run directly
go run ./cmd/railctl --help# Run unit + integration tests
make test
# Run E2E tests (requires RAILWAY_TOKEN; builds binary first)
make test-e2e
# E2E with verbose output
E2E_VERBOSE=1 make test-e2e
# E2E keeping resources for debugging
E2E_KEEP=1 make test-e2e
# Run with coverage
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.outSee Testing Architecture for the full testing strategy.
# Format code
make fmt
# Run linter
make lintrailway-cli/
├── .github/
│ ├── CODEOWNERS # Pipeline change protection
│ ├── dependabot.yml # Dependency update config
│ └── workflows/
│ ├── pr.yml # PR unit test workflow
│ ├── release.yml # Auto pre-release + build workflow
│ └── manual-release.yml # Manual release dispatch
├── cmd/railctl/ # CLI entry point
│ └── main.go # Main entrypoint
├── internal/ # Internal packages
│ ├── api/ # Railway GraphQL API client
│ │ ├── client.go # HTTP/GraphQL client
│ │ ├── projects.go # Project operations
│ │ ├── environments.go # Environment operations
│ │ ├── services.go # Service operations
│ │ ├── variables.go # Variable operations
│ │ ├── deployments.go # Deployment operations
│ │ ├── interface.go # API interface definition
│ │ └── mock.go # Mock client for testing
│ ├── cmd/ # Cobra command implementations
│ │ ├── root.go # Root command
│ │ ├── get_*.go # Get commands
│ │ ├── create_*.go # Create commands
│ │ ├── update_*.go # Update commands
│ │ ├── delete_*.go # Delete commands
│ │ └── describe_*.go # Describe commands
│ ├── output/ # Output formatting
│ │ ├── table.go # Table formatter
│ │ ├── json.go # JSON formatter
│ │ └── yaml.go # YAML formatter
│ ├── resolver/ # Name/ID resolution
│ │ └── resolver.go # Resolve names to IDs
│ └── types/ # Domain models
│ └── types.go # Shared type definitions
├── tests/e2e/ # End-to-end tests
│ ├── run.sh # E2E test suite (~100 tests)
│ └── README.md # E2E documentation
├── docs/ # Documentation
│ ├── testing-architecture.md
│ ├── ci-build-setup.md
│ └── railway-service-creation-behavior.md
├── Makefile # Build automation
├── SKILL.md # Development guidelines
├── go.mod # Go module definition
└── README.md # This file
When updating a service from a private Docker registry, you must re-provide registry credentials due to Railway API limitations. The API replaces configuration rather than merging, and credentials are encrypted.
Workaround: Set environment variables once:
export RAILCTL_REGISTRY_USERNAME=user
export RAILCTL_REGISTRY_PASSWORD=token- README.md - This file
- SKILL.md - Development guidelines and patterns
- Declarative Configuration - Config file schema, variable expansion, and examples
MIT License - see LICENSE file for details
Built with:
- Cobra - CLI framework
- Go - Programming language
- Railway API - Infrastructure platform
Note: This is an unofficial CLI tool. For the official Railway CLI, see railway.app/cli.