Back to catalog

railway-environment-manager

AI-powered 3D asset generation platform for game development

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • GitHub stars: 2
  • Updated: 2025-11-21

Download the latest version (v1.0.0) from this page.

SKILL.md

# Railway Environment Manager

## Activation
When the user mentions any of these, activate this skill:
- "railway environment"
- "railway variables"
- "environment setup railway"
- "railway env sync"
- "railway staging production"
- "reference variables railway"
- "shared variables railway"
- "railway local development"

## Purpose
Manages environment variables, multi-environment configurations, and local development workflows on Railway. Provides best practices for variable management, environment separation, and secure configuration handling.

## Context
Railway supports multiple environments (development, staging, production) within a single project. Environment variables can be service-specific, shared across services, or reference other services' variables. Proper environment management is critical for secure, maintainable deployments.

## Core Capabilities

### 1. Environment Management
- Create and manage multiple environments
- Switch between environments
- Clone environments
- Environment-specific configurations

### 2. Variable Configuration
- Service-specific variables
- Shared variables across services
- Reference variables
- Secret management

### 3. Local Development
- Sync Railway variables locally
- Run commands with Railway environment
- Test with production-like configuration
- Development environment setup

### 4. Multi-Service Configuration
- Inter-service communication
- Private networking variables
- Database connection strings
- Service discovery

### 5. Security & Best Practices
- Secret rotation
- Environment isolation
- Variable validation
- Access control

## Implementation Patterns

### 1. Environment Setup

```bash
# List all environments
railway environment

# Create new environment
railway environment create staging

# Switch to environment
railway environment staging

# Set default environment
railway environment production --default

# Clone environment (copy variables)
# Note: Done via Railway Dashboard
# Project Settings → Environments → Clone Environment
```

### 2. Variable Configuration Patterns

#### Service-Specific Variables

```env
# Application configuration
NODE_ENV=production
PORT=3000
LOG_LEVEL=info

# Feature flags
FEATURE_BETA_UI=false
FEATURE_NEW_AUTH=true

# Application secrets
JWT_SECRET=your-jwt-secret-here
SESSION_SECRET=your-session-secret-here
ENCRYPTION_KEY=your-encryption-key-here

# External API keys
OPENAI_API_KEY=sk-...
STRIPE_SECRET_KEY=sk_live_...
SENDGRID_API_KEY=SG....
```

#### Database Connection Variables

```env
# Using Railway's auto-generated DATABASE_URL
DATABASE_URL=${{Postgres.DATABASE_URL}}

# Or using private domain for better performance
DATABASE_URL=postgresql://${{Postgres.PGUSER}}:${{Postgres.PGPASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:${{Postgres.PGPORT}}/${{Postgres.PGDATABASE}}

# Multiple databases
PRIMARY_DATABASE_URL=${{Postgres.DATABASE_URL}}
ANALYTICS_DATABASE_URL=${{AnalyticsDB.DATABASE_URL}}
CACHE_URL=${{Redis.REDIS_URL}}
```

#### Inter-Service Communication

```env
# API service URL (private network)
API_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}
API_INTERNAL_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}

# Worker service URL
WORKER_URL=http://${{worker.RAILWAY_PRIVATE_DOMAIN}}:${{worker.PORT}}

# Frontend URL (public)
FRONTEND_URL=https://${{frontend.RAILWAY_STATIC_URL}}

# Important: Always use http:// (not https://) for private network
# CORS origins
CORS_ORIGINS=${{frontend.RAILWAY_STATIC_URL}},${{admin.RAILWAY_STATIC_URL}}
```

#### Shared Variables

```env
# Set in Project Settings → Shared Variables
# These are available to ALL services

# Environment identifier
ENVIRONMENT=production

# Global configuration
APP_NAME=MyApplication
API_VERSION=v1
LOG_LEVEL=info

# Global secrets (use sparingly)
MASTER_ENCRYPTION_KEY=...
```

### 3. Local Development Setup

#### Using railway run

```bash
# Run development server with Railway variables
railway run npm run dev

# Run with specific environment
railway environment staging
railway run npm run dev

# Run database migrations
railway run npx prisma migrate deploy

# Run seed scripts
railway run npm run seed

# Run tests with Railway environment
railway run npm test
```

#### Using railway shell

```bash
# Open shell with Railway variables
railway shell

# Now all variables are available
echo $DATABASE_URL
echo $OPENAI_API_KEY

# Run any command
npm run dev
psql $DATABASE_URL
node scripts/test-connection.js

# Exit shell
exit
```

#### Environment File Generation

```typescript
// scripts/sync-env.ts
import { execSync } from 'child_process';
import { writeFileSync } from 'fs';

/**
 * Generate .env.local file from Railway environment
 * WARNING: This exposes production secrets locally - use with caution
 */
async function syncEnvironment() {
  try {
    // Get Railway variables
    const output = execSync('railway variables', { encoding: 'utf-8' });

    // Parse and format as .env
    const lines = output
      .split('\n')
      .filter(line => line.includes('='))
      .map(line => {
        const [key, ...valueParts] = line.split('=');
        const value = valueParts.join('=');
        return `${key.trim()}=${value.trim()}`;
      });

    // Write to .env.local
    writeFileSync('.env.local', lines.join('\n'));

    console.log('✅ Environment synced to .env.local');
    console.log('⚠️  WARNING: .env.local contains secrets - never commit this file');
  } catch (error) {
    console.error('❌ Failed to sync environment:', error);
    process.exit(1);
  }
}

syncEnvironment();
```

```bash
# Run sync script
railway run node scripts/sync-env.ts

# Add to .gitignore
echo ".env.local" >> .gitignore
```

### 4. Multi-Environment Configuration

#### Environment-Specific Settings

```typescript
// lib/config.ts
export const config = {
  environment: process.env.ENVIRONMENT || 'development',
  isProduction: process.env.ENVIRONMENT === 'production',
  isStaging: process.env.ENVIRONMENT === 'staging',
  isDevelopment: process.env.ENVIRONMENT === 'development',

  // Database
  database: {
    url: process.env.DATABASE_URL!,
    poolMin: parseInt(process.env.DATABASE_POOL_MIN || '2'),
    poolMax: parseInt(process.env.DATABASE_POOL_MAX || '10'),
  },

  // API
  api: {
    port: parseInt(process.env.PORT || '3000'),
    url: process.env.API_URL || 'http://test-frontend:3000',
    corsOrigins: process.env.CORS_ORIGINS?.split(',') || ['http://test-frontend:3000'],
  },

  // Services
  services: {
    openai: {
      apiKey: process.env.OPENAI_API_KEY!,
    },
    stripe: {
      secretKey: process.env.STRIPE_SECRET_KEY!,
      webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
    },
  },

  // Feature flags (environment-specific)
  features: {
    betaUI: process.env.FEATURE_BETA_UI === 'true',
    newAuth: process.env.FEATURE_NEW_AUTH === 'true',
    analytics: process.env.FEATURE_ANALYTICS !== 'false', // enabled by default
  },

  // Logging
  logging: {
    level: process.env.LOG_LEVEL || 'info',
    pretty: process.env.ENVIRONMENT !== 'production',
  },
};

// Validation
export function validateConfig(): void {
  const required = [
    'DATABASE_URL',
    'OPENAI_API_KEY',
  ];

  const missing = required.filter(key => !process.env[key]);

  if (missing.length > 0) {
    throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
  }
}
```

#### Environment-Specific Package.json Scripts

```json
{
  "scripts": {
    "dev": "next dev",
    "dev:staging": "railway environment staging && railway run npm run dev",
    "dev:production": "railway environment production && railway run npm run dev",

    "build": "next build",
    "build:staging": "railway environment staging && railway up",
    "build:production": "railway environment production && railway up",

    "migrate": "prisma migrate deploy",
    "migrate:staging": "railway environment staging && railway run npm run migrate",
    "migrate:production": "railway environment production && railway run npm run migrate",

    "seed": "ts-node prisma/seed.ts",
    "seed:staging": "railway environment staging && railway run npm run seed",

    "db:push": "prisma db push",
    "db:push:staging": "railway environment staging && railway run npm run db:push",

    "sync-env": "railway run node scripts/sync-env.ts",
    "sync-env:staging": "railway environment staging && npm run sync-env",
    "sync-env:production": "railway environment production && npm run sync-env"
  }
}
```

### 5. Variable Management Utilities

```typescript
// lib/railway-utils.ts

/**
 * Check if running in Railway environment
 */
export function isRailway(): boolean {
  return !!process.env.RAILWAY_ENVIRONMENT;
}

/**
 * Get current Railway environment
 */
export function getRailwayEnvironment(): string | undefined {
  return process.env.RAILWAY_ENVIRONMENT;
}

/**
 * Check if running in specific environment
 */
export function isEnvironment(env: string): boolean {
  return process.env.ENVIRONMENT === env ||
         process.env.RAILWAY_ENVIRONMENT === env;
}

/**
 * Get Railway service information
 */
export function getRailwayServiceInfo() {
  return {
    projectId: process.env.RAILWAY_PROJECT_ID,
    environmentId: process.env.RAILWAY_ENVIRONMENT_ID,
    serviceId: process.env.RAILWAY_SERVICE_ID,
    serviceName: process.env.RAILWAY_SERVICE_NAME,
    deploymentId: process.env.RAILWAY_DEPLOYMENT_ID,
    staticUrl: process.env.RAILWAY_STATIC_URL,
    privateDomain: process.env.RAILWAY_PRIVATE_DOMAIN,
  };
}

/**
 * Build private service URL
 */
export function getPrivateServiceUrl(
  serviceName: string,
  port?: number
): string {
  const domain = process.env[`${serviceName.toUpperCase()}_RAILWAY_PRIVATE_DOMAIN`];
  const servicePort = port || process.env[`${serviceName.toUpperCase()}_PORT`];

  if (!domain) {
    throw new Error(`Private domain not found for service: ${serviceName}`);
  }

  return `http://${domain}${servicePort ? `:${servicePort}` : ''}`;
}

/**
 * Get service URL (private for Railway, localhost for local)
 */
export function getServiceUrl(
  serviceName: string,
  localPort: number
): string {
  if (isRailway()) {
    return getPrivateServiceUrl(serviceName);
  }
  return `http://localhost:${localPort}`;
}
```

### 6. Environment Variable Validation

```typescript
// lib/env-validation.ts
import { z } from 'zod';

// Define schema for environment variables
const envSchema = z.object({
  // Environment
  NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
  ENVIRONMENT: z.enum(['development', 'staging', 'production']),

  // Server
  PORT: z.string().transform(Number).pipe(z.number().min(1).max(65535)),

  // Database
  DATABASE_URL: z.string().url(),

  // APIs
  OPENAI_API_KEY: z.string().min(1),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),

  // Optional
  STRIPE_WEBHOOK_SECRET: z.string().optional(),
  SENTRY_DSN: z.string().url().optional(),

  // Feature Flags
  FEATURE_BETA_UI: z.string().transform(v => v === 'true').default('false'),
});

export type Env = z.infer<typeof envSchema>;

/**
 * Validate and parse environment variables
 */
export function validateEnv(): Env {
  try {
    return envSchema.parse(process.env);
  } catch (error) {
    if (error instanceof z.ZodError) {
      const issues = error.issues.map(issue =>
        `${issue.path.join('.')}: ${issue.message}`
      );

      console.error('❌ Environment validation failed:');
      issues.forEach(issue => console.error(`  - ${issue}`));

      throw new Error('Invalid environment configuration');
    }
    throw error;
  }
}

// Usage in your app
// import { validateEnv } from './lib/env-validation';
// const env = validateEnv();
```

## Best Practices

### 1. Environment Isolation
- **Create separate environments** for development, staging, and production
- **Never share production secrets** with development
- **Use environment-specific feature flags**
- **Test in staging** before deploying to production
- **Clone environments** carefully (secrets may need different values)

### 2. Variable Naming Conventions
- **Use SCREAMING_SNAKE_CASE** for all environment variables
- **Prefix service-specific variables**: `STRIPE_SECRET_KEY`, `OPENAI_API_KEY`
- **Use descriptive names**: `DATABASE_POOL_MAX` instead of `DB_MAX`
- **Group related variables**: `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`
- **Boolean flags**: `FEATURE_NAME=true/false` or `ENABLE_FEATURE=true/false`

### 3. Secret Management
- **Never commit secrets** to version control
- **Rotate secrets regularly** (at least quarterly)
- **Use strong secrets**: min 32 characters, random
- **Limit secret access**: only services that need them
- **Use Railway's secret management** (not plain text)

### 4. Reference Variables
- **Always use reference variables** for inter-service communication
- **Use RAILWAY_PRIVATE_DOMAIN** for private networking
- **Example**: `API_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}`
- **Benefits**: Automatic updates, no hardcoding, DRY principle

### 5. Local Development
- **Use railway run** instead of syncing .env files
- **Never commit .env.local** with production secrets
- **Use separate API keys** for development
- **Test with railway shell** before deployment
- **Document required variables** in README.md

### 6. Variable Organization
```env
# ============================================
# Environment Configuration
# ============================================
ENVIRONMENT=production
NODE_ENV=production
LOG_LEVEL=info

# ============================================
# Server Configuration
# ============================================
PORT=3000
HOST=0.0.0.0

# ============================================
# Database Configuration
# ============================================
DATABASE_URL=${{Postgres.DATABASE_URL}}
DATABASE_POOL_MIN=2
DATABASE_POOL_MAX=10

# ============================================
# External Services
# ============================================
OPENAI_API_KEY=sk-...
STRIPE_SECRET_KEY=sk_live_...

# ============================================
# Internal Services (Private Network)
# ============================================
API_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}
WORKER_URL=http://${{worker.RAILWAY_PRIVATE_DOMAIN}}:${{worker.PORT}}

# ============================================
# Feature Flags
# ============================================
FEATURE_BETA_UI=false
FEATURE_NEW_AUTH=true
```

## Common Workflows

### 1. Create New Environment

```bash
# Create staging environment
# (Done via Railway Dashboard)
# Project → Settings → Environments → Create Environment → "staging"

# Switch to staging
railway environment staging

# Copy variables from production
# (Done via Railway Dashboard)
# Environments → staging → Actions → Import from production

# Update environment-specific variables
# (Update via Railway Dashboard or GraphQL API)
```

### 2. Add New Service

```bash
# Add service to project
railway add

# Configure service variables
# In Railway Dashboard:
# 1. Select service
# 2. Variables tab
# 3. Add service-specific variables
# 4. Add references to other services

# Example variables for new API service:
PORT=3000
DATABASE_URL=${{Postgres.DATABASE_URL}}
REDIS_URL=${{Redis.REDIS_URL}}
FRONTEND_URL=https://${{frontend.RAILWAY_STATIC_URL}}
```

### 3. Migrate to Private Networking

```bash
# Before: Using public URLs
API_URL=https://api-production.up.railway.app

# After: Using private networking
API_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}

# Benefits:
# - Faster (no public internet)
# - No egress fees
# - More secure
# - Auto-updates if service changes
```

### 4. Environment-Specific Deployments

```bash
# Deploy to staging
railway environment staging
railway up

# Test in staging
curl https://staging-app.up.railway.app/health

# Deploy to production
railway environment production
railway up

# Monitor deployment
railway logs --follow
```

## Troubleshooting

### Variable Not Available
- Check variable is set in correct environment
- Verify service has access to variable
- For reference variables, check syntax: `${{service.VARIABLE}}`
- Restart service after adding variables

### Private Networking Issues
- Use http:// not https:// for private domains
- Verify services are in same project and environment
- Check reference variable syntax
- Ensure RAILWAY_PRIVATE_DOMAIN is used, not RAILWAY_STATIC_URL

### Local Development Issues
- Run `railway login` to authenticate
- Verify project is linked: `railway status`
- Check environment: `railway environment`
- Use `railway shell` to debug variable availability

### Reference Variable Errors
- Ensure referenced service exists
- Check service name spelling
- Verify variable exists on referenced service
- Use exact syntax: `${{ServiceName.VARIABLE_NAME}}`

## Security Checklist

- [ ] No secrets in version control (.env files in .gitignore)
- [ ] Production secrets separate from development
- [ ] Strong secrets (min 32 characters)
- [ ] Secrets rotated regularly
- [ ] Private networking for inter-service communication
- [ ] Variable validation on application startup
- [ ] Limited access to production environment
- [ ] Audit logs enabled for variable changes
- [ ] No secrets in client-side code
- [ ] Railway project tokens secured

## Resources

- Railway Variables Docs: https://docs.railway.com/guides/variables
- Railway Environments: https://docs.railway.com/guides/environments
- Railway CLI Reference: https://docs.railway.com/reference/cli-api
- Railway Private Networking: https://docs.railway.com/guides/private-networking

## Notes
- Environment variables are encrypted at rest on Railway
- Reference variables auto-update when source changes
- Private networking is free (no egress charges)
- Shared variables are useful but use sparingly (can cause coupling)
- Always validate environment variables on application startup
- Consider using type-safe environment variable libraries (zod, envalid)