Skill: dbanys/railway-deploy (version 1.0.0)

--- name: deploy description: This skill should be used when the user wants to push code to Railway, says "railway up", "deploy", "deploy to railway", "ship", or "push". For initial setup or creating services, use new skill. For Docker images, use environment skill. allowed-tools: Bash(railway:*) --- # Deploy Deploy code from the current directory to Railway using `railway up`. ## When to Use - User asks to "deploy", "ship", "push code" - User says "railway up" or "deploy to Railway" - User wants to deploy local code changes - User says "deploy and fix any issues" (use --ci mode) ## Commit Message Always use the `-m` flag with a descriptive commit message summarizing what's being deployed: ```bash railway up --detach -m "Add user authentication endpoint" ``` Good commit messages: - Describe what changed: "Fix memory leak in worker process" - Reference tickets/issues: "Implement feature #123" - Be concise but meaningful: "Update deps and fix build warnings" ## Modes ### Detach Mode (default) Starts deploy and returns immediately. Use for most deploys. ```bash railway up --detach -m "Deploy description here" ``` ### CI Mode Streams build logs until complete. Use when user wants to watch the build or needs to debug issues. ```bash railway up --ci -m "Deploy description here" ``` **When to use CI mode:** - User says "deploy and watch", "deploy and fix issues" - User is debugging build failures - User wants to see build output ## Deploy Specific Service Default is linked service. To deploy to a different service: ```bash railway up --detach --service backend -m "Deploy description here" ``` ## Deploy to Unlinked Project Deploy to a project without linking first: ```bash railway up --project <project-id> --environment production --detach -m "Deploy description here" ``` Requires both `--project` and `--environment` flags. ## CLI Options | Flag | Description | |------|-------------| | `-m, --message <MSG>` | Commit message describing the deploy (always use this) | | `-d, --detach` | Don't attach to logs (default) | | `-c, --ci` | Stream build logs, exit when done | | `-s, --service <NAME>` | Target service (defaults to linked) | | `-e, --environment <NAME>` | Target environment (defaults to linked) | | `-p, --project <ID>` | Target project (requires --environment) | | `[PATH]` | Path to deploy (defaults to current directory) | ## Directory Linking Railway CLI walks UP the directory tree to find a linked project. If you're in a subdirectory of a linked project, you don't need to relink. For subdirectory deployments, prefer setting `rootDirectory` via the environment skill, then deploy normally with `railway up`. ## After Deploy ### Detach mode ``` Deploying to <service>... ``` Use `deployment` skill to check build status (with `--lines` flag). ### CI mode Build logs stream inline. If build fails, the error will be in the output. **Do NOT run `railway logs --build` after CI mode** - the logs already streamed. If you need more context, use `deployment` skill with `--lines` flag (never stream). ## Composability - **Check status after deploy**: Use `service` skill - **View logs**: Use `deployment` skill - **Fix config issues**: Use `environment` skill - **Redeploy after config fix**: Use `environment` skill ## Error Handling ### No Project Linked ``` No Railway project linked. Run `railway link` first. ``` ### No Service Linked ``` No service linked. Use --service flag or run `railway service` to select one. ``` ### Build Failure (CI mode) The build logs already streamed - analyze them directly from the `railway up --ci` output. Do NOT run `railway logs` after CI mode (it streams forever without `--lines`). Common issues: - Missing dependencies → check package.json/requirements.txt - Build command wrong → use environment skill to fix - Dockerfile issues → check dockerfile path

path: references/environment-config.md
content: # Environment Config Reference

The `EnvironmentConfig` object is used to configure services, volumes, and shared variables in Railway.

## Structure

```json
{
  "services": {
    "<serviceId>": {
      "source": { ... },
      "build": { ... },
      "deploy": { ... },
      "variables": { ... },
      "networking": { ... }
    }
  },
  "sharedVariables": { ... },
  "volumes": { ... },
  "buckets": { ... }
}
```

Only include fields being changed. The patch is merged with existing config.

Apply changes via CLI: `echo '<json>' | railway environment edit --json`

## Service Config

### Source
| Field | Type | Description |
|-------|------|-------------|
| `image` | string | Docker image (e.g., `nginx:latest`) |
| `repo` | string | Git repository URL |
| `branch` | string | Git branch to deploy |
| `commitSha` | string | Specific commit SHA |
| `rootDirectory` | string | Root directory (monorepos) |
| `checkSuites` | boolean | Wait for GitHub check suites |
| `autoUpdates.type` | disabled \| patch \| minor | Auto-update policy for Docker images |

### Build
| Field | Type | Description |
|-------|------|-------------|
| `builder` | NIXPACKS \| DOCKERFILE \| RAILPACK | Build system |
| `buildCommand` | string | Command for Nixpacks builds |
| `dockerfilePath` | string | Path to Dockerfile |
| `watchPatterns` | string[] | Patterns to trigger deploys |
| `nixpacksConfigPath` | string | Path to nixpacks config |

### Deploy
| Field | Type | Description |
|-------|------|-------------|
| `startCommand` | string | Container start command |
| `multiRegionConfig` | object | Region → replica config. See [Multi-Region Config](#multi-region-config). |
| `healthcheckPath` | string | Health check endpoint |
| `healthcheckTimeout` | number | Seconds to wait for health |
| `restartPolicyType` | ON_FAILURE \| ALWAYS \| NEVER | Restart behavior |
| `restartPolicyMaxRetries` | number | Max restart attempts |
| `cronSchedule` | string | Cron schedule for cron jobs |
| `sleepApplication` | boolean | Sleep when inactive |

### Variables
| Field | Type | Description |
|-------|------|-------------|
| `value` | string | Variable value |
| `isOptional` | boolean | Allow empty value |

Set to `null` to delete a variable.

For variable references, see [variables.md](variables.md).

### Lifecycle
| Field | Type | Description |
|-------|------|-------------|
| `isDeleted` | boolean | Mark for deletion (requires ADMIN) |
| `isCreated` | boolean | Mark as newly created |

## Multi-Region Config

Controls replica count per region. Structure: region name → `{ numReplicas }` or `null` to remove.

```json
{
  "multiRegionConfig": {
    "us-west2": { "numReplicas": 3 },
    "europe-west4-drams3a": { "numReplicas": 2 }
  }
}
```

### Available Regions

| Name | Location | Aliases |
|------|----------|---------|
| `us-west2` | US West (California) | "us west", "california" |
| `us-east4-eqdc4a` | US East (Virginia) | "us east", "virginia" |
| `europe-west4-drams3a` | EU West (Amsterdam) | "europe", "eu", "amsterdam" |
| `asia-southeast1-eqsg3a` | Southeast Asia (Singapore) | "asia", "singapore" |

### Interpreting User Requests

- "add 3 replicas to europe" → `{ "europe-west4-drams3a": { "numReplicas": 3 } }`
- "add a replica to all regions" → set `numReplicas: 1` for all 4 regions
- "remove from asia" → `{ "asia-southeast1-eqsg3a": null }`
- "increase replicas to 5" (no region specified) → query current config first, update existing region(s)

**Important:** When user doesn't specify a region, query the current `multiRegionConfig` and modify the existing region(s). Don't assume a default region.

## Common Operations

### Set Build Command
```json
{ "services": { "<serviceId>": { "build": { "buildCommand": "npm run build" } } } }
```

### Set Start Command
```json
{ "services": { "<serviceId>": { "deploy": { "startCommand": "node server.js" } } } }
```

### Set Replicas
```json
{ "services": { "<serviceId>": { "deploy": { "multiRegionConfig": { "us-west2": { "numReplicas": 3 } } } } } }
```

### Add Variables
```json
{ "services": { "<serviceId>": { "variables": { "API_KEY": { "value": "xxx" } } } } }
```

### Delete Variable
```json
{ "services": { "<serviceId>": { "variables": { "OLD_VAR": null } } } }
```
Via CLI: `echo '{"services":{"<serviceId>":{"variables":{"OLD_VAR":null}}}}' | railway environment edit --json`

### Add Shared Variable
```json
{ "sharedVariables": { "DATABASE_URL": { "value": "postgres://..." } } }
```

### Change Docker Image
```json
{ "services": { "<serviceId>": { "source": { "image": "nginx:latest" } } } }
```

### Connect GitHub Repo
```json
{ "services": { "<serviceId>": { "source": { "repo": "owner/repo", "branch": "main" } } } }
```

### Change Git Branch
```json
{ "services": { "<serviceId>": { "source": { "branch": "develop" } } } }
```

### Set Health Check
```json
{ "services": { "<serviceId>": { "deploy": { "healthcheckPath": "/health", "healthcheckTimeout": 30 } } } }
```

### Change Builder
```json
{ "services": { "<serviceId>": { "build": { "builder": "DOCKERFILE", "dockerfilePath": "./Dockerfile" } } } }
```

### Delete Service
```json
{ "services": { "<serviceId>": { "isDeleted": true } } }
```
Via CLI: `echo '{"services":{"<serviceId>":{"isDeleted":true}}}' | railway environment edit --json`

### Delete Volume
```json
{ "volumes": { "<volumeId>": { "isDeleted": true } } }
```

### New Service Instance
```json
{ "services": { "<serviceId>": { "isCreated": true, "source": { "image": "nginx" } } } }
```

**Note:** `isCreated: true` is required for new service instances.


path: references/monorepo.md
content: # Monorepo Reference

Railway supports two types of monorepo deployments with different configuration approaches.

## Key Decision: Root Directory vs Custom Commands

| Approach | When to Use | What Happens |
|----------|-------------|--------------|
| **Root Directory** | Isolated apps (no shared code) | Only that directory's code is available |
| **Custom Commands** | Shared monorepos (TypeScript, workspaces) | Full repo available, filter at build/start |

**Critical:** Setting root directory means code outside that directory is NOT available during build. For monorepos with shared packages, use custom commands instead.

## Isolated Monorepo

Apps are completely independent - no shared code between directories.

```
├── frontend/        # React app (standalone)
│   ├── package.json
│   └── src/
└── backend/         # Python API (standalone)
    ├── requirements.txt
    └── main.py
```

### Configuration

Set **Root Directory** for each service:
- Frontend service: `/frontend`
- Backend service: `/backend`

Each service only sees its own directory's code.

### When to Use

- Frontend and backend in different languages
- No shared packages or dependencies
- Each app has its own package.json/requirements.txt
- Apps don't import from sibling directories

## Shared Monorepo

Apps share code from common packages or the root.

```
├── package.json           # Root workspace config
├── packages/
│   ├── frontend/
│   │   ├── package.json
│   │   └── src/
│   ├── backend/
│   │   ├── package.json
│   │   └── src/
│   └── shared/            # Shared utilities
│       ├── package.json
│       └── src/
└── tsconfig.json          # Shared TS config
```

### Configuration

Do NOT set root directory. Instead, use custom build and start commands:

**pnpm:**
```
Build: pnpm --filter backend build
Start: pnpm --filter backend start
```

**npm workspaces:**
```
Build: npm run build --workspace=packages/backend
Start: npm run start --workspace=packages/backend
```

**yarn workspaces:**
```
Build: yarn workspace backend build
Start: yarn workspace backend start
```

**bun:**
```
Build: bun run --filter backend build
Start: bun run --filter backend start
```

**Turborepo:**
```
Build: turbo run build --filter=backend
Start: turbo run start --filter=backend
```

### When to Use

- TypeScript/JavaScript monorepo with workspaces
- Packages import from sibling packages (`@myapp/shared`)
- Shared tsconfig.json, eslint config at root
- Using pnpm, yarn workspaces, npm workspaces, or bun
- Using Turborepo, Nx, or similar build tools

## Watch Paths

Prevent changes in one package from triggering rebuilds of other services.

Set watch paths for each service to only rebuild when relevant files change:

| Service | Watch Paths |
|---------|-------------|
| frontend | `/packages/frontend/**`, `/packages/shared/**` |
| backend | `/packages/backend/**`, `/packages/shared/**` |

Include shared packages in watch paths if the service depends on them.

### Pattern Format

Uses gitignore-style patterns:
```
/packages/backend/**     # All files in backend
/packages/shared/**      # All files in shared (if depends on it)
!**/*.md                 # Ignore markdown changes
```

## Configuration Examples

### Isolated: React + Python API

Two separate apps, no shared code.

**Frontend service:**
- Root Directory: `/frontend`
- No custom commands needed (Railpack auto-detects)

**Backend service:**
- Root Directory: `/backend`
- No custom commands needed

### Shared: TypeScript Monorepo with pnpm

Frontend and backend share a `@myapp/shared` package.

**Frontend service:**
- Root Directory: (leave empty)
- Build Command: `pnpm --filter frontend build`
- Start Command: `pnpm --filter frontend start`
- Watch Paths: `/packages/frontend/**`, `/packages/shared/**`

**Backend service:**
- Root Directory: (leave empty)
- Build Command: `pnpm --filter backend build`
- Start Command: `pnpm --filter backend start`
- Watch Paths: `/packages/backend/**`, `/packages/shared/**`

### Shared: Turborepo

**Frontend service:**
- Root Directory: (leave empty)
- Build Command: `turbo run build --filter=frontend`
- Start Command: `turbo run start --filter=frontend`
- Watch Paths: `/apps/frontend/**`, `/packages/**`

**Backend service:**
- Root Directory: (leave empty)
- Build Command: `turbo run build --filter=backend`
- Start Command: `turbo run start --filter=backend`
- Watch Paths: `/apps/backend/**`, `/packages/**`

## Common Mistakes

### Using Root Directory for Shared Monorepos

**Wrong:**
```
Root Directory: /packages/backend
```
This breaks builds because `@myapp/shared` isn't available.

**Right:**
```
Root Directory: (empty)
Build Command: pnpm --filter backend build
Start Command: pnpm --filter backend start
```

### Forgetting Watch Paths

Without watch paths, changing `frontend/` triggers a rebuild of `backend/`.

Always set watch paths for monorepos to avoid unnecessary builds.

### Missing Shared Packages in Watch Paths

If `backend` imports from `shared`, include both in watch paths:
```
/packages/backend/**
/packages/shared/**
```

Otherwise changes to `shared` won't trigger backend rebuilds.

## Detecting Monorepo Type

Check for these indicators:

**Isolated monorepo:**
- Separate package.json in each directory
- No workspace config in root package.json
- No imports between directories

**Shared monorepo:**
- Root package.json with `workspaces` field
- `pnpm-workspace.yaml` exists
- Packages import from each other (`@myapp/shared`)
- Shared tsconfig.json at root
- turbo.json or nx.json at root


path: references/railpack.md
content: # Railpack Reference

Railpack is Railway's default builder. Zero-config for most projects.

Full docs: https://railpack.com/llms.txt

## Detection

Railpack analyzes source code to detect language, framework, and build requirements automatically.

Supported: Node, Python, Go, PHP, Java, Ruby, Rust, Elixir, Gleam, Deno, C/C++, static files.

## Static Sites

### Detection Patterns

Railpack serves static files via Caddy when it detects:
1. `Staticfile` in root (can specify `root: dist`)
2. `index.html` in root
3. `public/` directory
4. `RAILPACK_STATIC_FILE_ROOT` env var set

### Root Directory Priority

1. `RAILPACK_STATIC_FILE_ROOT` env var
2. `root` in `Staticfile`
3. `public/` directory
4. Current directory (if index.html exists)

### Common Patterns

Railpack auto-detects common static build outputs. Only set `RAILPACK_STATIC_FILE_ROOT` for non-standard output directories.

| Framework | Build Output | Config Needed |
|-----------|--------------|---------------|
| Plain HTML | root | None (auto-detected) |
| Vite | dist | None (auto-detected) |
| Astro (static) | dist | None (auto-detected) |
| Create React App | build | None (auto-detected) |
| Angular | dist/<project> | `RAILPACK_STATIC_FILE_ROOT=dist/<project>` (non-standard path) |
| Custom output | varies | Set if output dir is non-standard |

### Custom Caddyfile

Put a `Caddyfile` in project root to override default serving behavior.

## Node.js

### Detection
- `package.json` in root

### Version Priority
1. `RAILPACK_NODE_VERSION` env var
2. `engines.node` in package.json
3. `.nvmrc` or `.node-version`
4. Default: Node 22

### Package Manager Detection
1. `packageManager` field in package.json (enables Corepack)
2. Lock file: `pnpm-lock.yaml`, `bun.lockb`, `yarn.lock`
3. Default: npm

### Build Command
Auto-detected from package.json `scripts.build`. Override via `buildCommand` in service settings.

### Start Command
Auto-detected:
1. `scripts.start` in package.json
2. `main` field in package.json
3. `index.js` or `index.ts` in root

Override via `startCommand` in service settings.

### Framework Detection

| Framework | Detection | Notes |
|-----------|-----------|-------|
| Next.js | `next` in dependencies | Caches `.next/cache` |
| Vite | `vite` in devDependencies | Static or SSR mode |
| Astro | `astro` in dependencies | Caches `.astro` |
| Nuxt | `nuxt` in dependencies | Auto start command |
| Remix | `@remix-run/*` in deps | - |

### Static Site Mode (SPA)

For frameworks like Vite, CRA, Astro (static), Angular:
- Railpack builds then serves via Caddy
- Set `RAILPACK_SPA_OUTPUT_DIR` if output isn't `dist`

## Python

### Detection
- `requirements.txt`, `pyproject.toml`, `Pipfile`, or `uv.lock`

### Version Priority
1. `RAILPACK_PYTHON_VERSION` env var
2. `.python-version` file
3. `requires-python` in pyproject.toml
4. Default: Python 3.12

### WSGI/ASGI Auto-Config

| Framework | Start Command |
|-----------|---------------|
| Django | `gunicorn <project>.wsgi` |
| FastAPI | `uvicorn main:app --host 0.0.0.0` |
| Flask | `gunicorn app:app` |

Override via `startCommand` in service settings.

## Go

### Detection
- `go.mod` in root

### Build
Compiles to binary automatically. For `cmd/` structure, set binary name or use `RAILPACK_GO_BIN`.

## Rust

### Detection
- `Cargo.toml` in root

### Build
Release build by default. Binary auto-detected from Cargo.toml.

## Configuration

### Preferred: Service Settings

Use `environment` skill to set:
- `buildCommand` - Custom build command
- `startCommand` - Custom start command

These are stored in Railway and don't pollute your codebase.

### Environment Variables

| Variable | Purpose |
|----------|---------|
| `RAILPACK_STATIC_FILE_ROOT` | Static file serving directory |
| `RAILPACK_SPA_OUTPUT_DIR` | SPA build output (defaults to `dist`) |
| `RAILPACK_NODE_VERSION` | Node.js version |
| `RAILPACK_PYTHON_VERSION` | Python version |
| `RAILPACK_GO_BIN` | Go binary name |
| `RAILPACK_PACKAGES` | Additional Mise packages (`pkg@version`) |
| `RAILPACK_BUILD_APT_PACKAGES` | System packages for build |
| `RAILPACK_DEPLOY_APT_PACKAGES` | System packages for runtime |

### railpack.json

Advanced config for custom build steps:

```json
{
  "$schema": "https://schema.railpack.com",
  "packages": {
    "ffmpeg": "latest"
  },
  "deploy": {
    "aptPackages": ["libmagic1"]
  }
}
```

### railway.toml

Alternative config format:

```toml
[build]
builder = "RAILPACK"
buildCommand = "npm run build"

[deploy]
startCommand = "npm start"
```

## Minimal Project Scaffolding

When no code exists, suggest these patterns:

### Static HTML
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Site</title>
</head>
<body>
  <h1>Hello Railway</h1>
</body>
</html>
```

### Vite React
```bash
npm create vite@latest . -- --template react
```

### Astro
```bash
npm create astro@latest
```

### Python FastAPI
```python
# main.py
from fastapi import FastAPI
app = FastAPI()

@app.get("/")
def root():
    return {"message": "Hello Railway"}
```

```txt
# requirements.txt
fastapi
uvicorn
```

### Go HTTP Server
```go
// main.go
package main

import (
    "fmt"
    "net/http"
    "os"
)

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello Railway")
    })
    http.ListenAndServe(":"+port, nil)
}
```

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Static site 404s | Check output dir - set `RAILPACK_STATIC_FILE_ROOT` if non-standard |
| Wrong build command | Use `environment` skill to set `buildCommand` |
| Wrong start command | Use `environment` skill to set `startCommand` |
| Missing system package | Add to `RAILPACK_BUILD_APT_PACKAGES` or `RAILPACK_DEPLOY_APT_PACKAGES` |
| Wrong Node version | Set `RAILPACK_NODE_VERSION` or add `.nvmrc` |
| Wrong Python version | Set `RAILPACK_PYTHON_VERSION` or add `.python-version` |


path: references/variables.md
content: # Variables Reference

Variables in Railway support references to other services, shared variables, and Railway-provided values.

## Template Syntax

```
${{NAMESPACE.VAR}}
```

| Namespace | Description |
|-----------|-------------|
| `shared` | Shared variables (project-wide) |
| `<serviceName>` | Variables from another service (case-sensitive) |

## Examples

**Reference shared variable:**
```json
{ "value": "${{shared.DOMAIN}}" }
```

**Reference another service's variable:**
```json
{ "value": "${{api.API_KEY}}" }
```

**Combine with text:**
```json
{ "value": "https://${{shared.DOMAIN}}/api" }
```

## Railway-Provided Variables

Railway injects these into every service automatically.

### Networking
| Variable | Description | Example | Availability |
|----------|-------------|---------|--------------|
| `RAILWAY_PUBLIC_DOMAIN` | Public domain | `myapp.up.railway.app` | Only if service has a domain |
| `RAILWAY_PRIVATE_DOMAIN` | Private DNS (internal only) | `myapp.railway.internal` | Always |
| `RAILWAY_TCP_PROXY_DOMAIN` | TCP proxy domain | `roundhouse.proxy.rlwy.net` | Only if TCP proxy enabled |
| `RAILWAY_TCP_PROXY_PORT` | TCP proxy port | `11105` | Only if TCP proxy enabled |

**Note:** `RAILWAY_PUBLIC_DOMAIN` is only available if the service has a domain configured.
Check the service's environment config to verify a domain exists before referencing it.

### Context
| Variable | Description |
|----------|-------------|
| `RAILWAY_PROJECT_ID` | Project ID |
| `RAILWAY_PROJECT_NAME` | Project name |
| `RAILWAY_ENVIRONMENT_ID` | Environment ID |
| `RAILWAY_ENVIRONMENT_NAME` | Environment name |
| `RAILWAY_SERVICE_ID` | Service ID |
| `RAILWAY_SERVICE_NAME` | Service name |
| `RAILWAY_DEPLOYMENT_ID` | Deployment ID |
| `RAILWAY_REPLICA_ID` | Replica ID |
| `RAILWAY_REPLICA_REGION` | Region (e.g., `us-west2`) |

### Volume (if attached)
| Variable | Description |
|----------|-------------|
| `RAILWAY_VOLUME_NAME` | Volume name |
| `RAILWAY_VOLUME_MOUNT_PATH` | Mount path |

### Git (if deployed from GitHub)
| Variable | Description |
|----------|-------------|
| `RAILWAY_GIT_COMMIT_SHA` | Commit SHA |
| `RAILWAY_GIT_BRANCH` | Branch name |
| `RAILWAY_GIT_REPO_NAME` | Repository name |
| `RAILWAY_GIT_REPO_OWNER` | Repository owner |
| `RAILWAY_GIT_AUTHOR` | Commit author |
| `RAILWAY_GIT_COMMIT_MESSAGE` | Commit message |

## Wiring Services Together

### Frontend → Backend (public network)
Use when: Browser makes requests to API (browser can't access private network)

```json
{
  "services": {
    "<frontendId>": {
      "variables": {
        "API_URL": { "value": "https://${{backend.RAILWAY_PUBLIC_DOMAIN}}" }
      }
    }
  }
}
```

### Service → Database (private network)
Use when: Backend connects to database (faster, no egress cost, more secure)

Railway databases auto-generate connection URL variables. Use the private versions:

| Database | Variable Reference |
|----------|-------------------|
| Postgres | `${{Postgres.DATABASE_URL}}` |
| MySQL | `${{MySQL.DATABASE_URL}}` |
| Redis | `${{Redis.REDIS_URL}}` |
| Mongo | `${{Mongo.MONGO_URL}}` |

**Postgres/MySQL example:**
```json
{
  "services": {
    "<apiId>": {
      "variables": {
        "DATABASE_URL": { "value": "${{Postgres.DATABASE_URL}}" }
      }
    }
  }
}
```

**Redis example:**
```json
{
  "services": {
    "<apiId>": {
      "variables": {
        "REDIS_URL": { "value": "${{Redis.REDIS_URL}}" }
      }
    }
  }
}
```

**Mongo example:**
```json
{
  "services": {
    "<apiId>": {
      "variables": {
        "MONGO_URL": { "value": "${{Mongo.MONGO_URL}}" }
      }
    }
  }
}
```

**Note:** Service names are case-sensitive. Match the exact name from your project (e.g., "Postgres", "Redis").

### Service → Service (private network)
Use when: Microservices communicate internally

```json
{
  "services": {
    "<workerServiceId>": {
      "variables": {
        "API_INTERNAL_URL": { "value": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}" }
      }
    }
  }
}
```

## When to Use Public vs Private

| Use Case | Domain | Reason |
|----------|--------|--------|
| Browser → API | Public | Browser can't access private network |
| Service → Service | Private | Faster, no egress, more secure |
| Service → Database | Private | Databases should never be public |
| External webhook → Service | Public | External services need public access |
| Cron job → API | Private | Internal communication |


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)