본문으로 건너뛰기
Previous
Next
Railway 완벽 가이드 | 간편한 배포·PostgreSQL·Redis·환경 변수·실전 활용

Railway 완벽 가이드 | 간편한 배포·PostgreSQL·Redis·환경 변수·실전 활용

Railway 완벽 가이드 | 간편한 배포·PostgreSQL·Redis·환경 변수·실전 활용

이 글의 핵심

Railway로 빠른 배포를 구현하는 완벽 가이드. Git 연동, PostgreSQL, Redis, 환경 변수, 도메인 설정까지 실전 예제로 정리. Railway·Deployment·PostgreSQL 중심으로 설명합니다.

이 글의 핵심

Railway로 빠른 배포를 구현하는 완벽 가이드입니다. Git 연동, PostgreSQL, Redis, 환경 변수, 도메인 설정까지 실전 예제로 정리했습니다.

실무 경험 공유: Heroku에서 Railway로 전환하면서, 비용이 70% 절감되고 배포 속도가 2배 빨라진 경험을 공유합니다.

들어가며: “Heroku가 비싸요”

실무 문제 시나리오

시나리오 1: Heroku 무료 플랜이 없어졌어요

비용이 부담됩니다. Railway는 $5 크레딧을 제공합니다. 시나리오 2: 데이터베이스 설정이 어려워요

복잡한 설정이 필요합니다. Railway는 클릭 한 번으로 추가됩니다. 시나리오 3: 배포가 느려요

빌드 시간이 깁니다. Railway는 빠른 빌드를 제공합니다.

1. Railway란?

Railway는 개발자 친화적인 클라우드 플랫폼으로, 복잡한 인프라 설정 없이 애플리케이션을 빠르게 배포할 수 있게 해줍니다.

왜 Railway인가?

전통적인 배포의 문제점:

  1. 복잡한 설정: AWS, GCP 등은 학습 곡선이 가파름
  2. 비용 불투명: 예상치 못한 요금 폭탄
  3. 느린 피드백: 배포 후 결과 확인까지 시간 소요
  4. 데이터베이스 분리: 앱과 DB를 따로 관리해야 함

Railway의 해결책:

전통적 배포:
Git Push → CI/CD 설정 → 컨테이너 레지스트리 → 
오케스트레이션 → 로드 밸런서 → SSL 인증서 → 도메인 설정
(약 2-3시간 소요)

Railway:
Git Push → 자동 배포
(약 2-3분 소요)

핵심 특징

1. 즉시 배포 (Zero Configuration)

  • Dockerfile, docker-compose.yml 자동 감지
  • Node.js, Python, Go, Ruby 등 주요 언어 자동 지원
  • 빌드 설정 자동 최적화

2. 인프라 as Code

# railway.toml
[build]
builder = "NIXPACKS"
buildCommand = "npm run build"

[deploy]
startCommand = "npm start"
restartPolicyType = "ON_FAILURE"
healthcheckPath = "/health"
healthcheckTimeout = 100

3. 통합 데이터베이스

  • PostgreSQL, MySQL, MongoDB, Redis 클릭 한 번으로 추가
  • 자동 백업 및 복원
  • 연결 문자열 자동 주입

4. 가격 모델

무료 크레딧: $5/월
실제 사용량 기반:
  - vCPU: $0.000463/분
  - 메모리: $0.000231 GB/분
  - 네트워크: $0.10/GB

예시 (소규모 앱):
  - 512MB RAM, 0.5 vCPU
  - 24/7 실행
  - 월 비용: ~$4-6

Railway vs 경쟁사

기능RailwayHerokuVercelRender
가격$5 무료 크레딧$7~/월$20~/월$7~/월
빌드 속도⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
DB 통합원클릭애드온외부 필요원클릭
도메인무료 SSL무료 SSL무료 SSL무료 SSL
백엔드✅ 최적✅ 좋음⚠️ 제한적✅ 좋음
프론트엔드✅ 지원✅ 지원✅ 최적✅ 지원
컨테이너✅ Docker✅ Docker✅ Docker

Railway를 선택해야 하는 경우:

  • ✅ 빠른 프로토타입 제작
  • ✅ 백엔드 API 서버
  • ✅ PostgreSQL/Redis 필요
  • ✅ Docker 기반 배포
  • ✅ 비용 예측 가능성

다른 플랫폼을 고려해야 하는 경우:

  • ⚠️ Next.js SSR (→ Vercel 권장)
  • ⚠️ 엔터프라이즈 SLA (→ AWS/GCP)
  • ⚠️ 복잡한 네트워킹 (→ Kubernetes)

주요 사용 사례

1. MVP/스타트업 백엔드

// Express API 서버
import express from 'express';
import { Pool } from 'pg';

const app = express();
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,  // Railway가 자동 주입
});

app.get('/api/data', async (req, res) => {
  const { rows } = await pool.query('SELECT * FROM data');
  res.json(rows);
});

// Railway가 PORT 환경 변수 제공
app.listen(process.env.PORT);

2. Discord/Telegram 봇

# Python Discord 봇
import discord
import os

client = discord.Client()

@client.event
async def on_ready():
    print(f'봇 로그인: {client.user}')

# Railway가 24/7 실행 보장
client.run(os.environ['DISCORD_TOKEN'])

3. 백그라운드 작업 워커

// Bull Queue 워커
import Queue from 'bull';

const queue = new Queue('email', process.env.REDIS_URL);

queue.process(async (job) => {
  await sendEmail(job.data);
});

// Railway가 워커를 항상 실행 상태로 유지

내부 동작 원리

배포 파이프라인:

1. Git Push 감지

2. Nixpacks 빌더 실행
   - 언어/프레임워크 감지
   - 의존성 설치
   - 빌드 명령 실행

3. Docker 이미지 생성
   - 최적화된 레이어링
   - 캐싱 활용

4. 컨테이너 배포
   - 이전 버전 유지 (롤백 대비)
   - 헬스체크 확인
   - 트래픽 전환

5. 서비스 활성화

Nixpacks란? Railway가 개발한 빌드 시스템으로, Heroku의 Buildpacks와 유사하지만 더 빠릅니다:

# Nixpacks가 자동으로 감지
package.json Node.js 프로젝트
requirements.txt Python 프로젝트
go.mod Go 프로젝트
Cargo.toml Rust 프로젝트
Dockerfile Docker 빌드

2. 프로젝트 배포

계정 생성 및 초기 설정

1. Railway 가입

# 방문: https://railway.app
# GitHub 계정으로 로그인 (권장)
# 이유: Git 저장소 자동 연동, 쉬운 권한 관리

2. 첫 프로젝트 생성

  • New Project 클릭
  • 3가지 옵션:
    1. Deploy from GitHub repo (가장 일반적)
    2. Empty Project (직접 설정)
    3. Deploy a Template (빠른 시작)

방법 1: GitHub 연동 배포 (권장)

단계별 가이드:

1단계: 저장소 연결
Railway Dashboard → New Project → 
Deploy from GitHub repo → 저장소 선택

2단계: 자동 감지
Railway가 자동으로 감지:
- package.json → Node.js
- requirements.txt → Python
- go.mod → Go
- Dockerfile → Docker

3단계: 환경 변수 설정 (필요시)
Variables 탭에서 추가:
DATABASE_URL=...
API_KEY=...

4단계: 배포 시작
자동으로 빌드 & 배포 시작
로그 실시간 확인 가능

실제 예제: Express 앱 배포

// 1. 프로젝트 구조
my-api/
├── src/
│   └── index.ts
├── package.json
├── tsconfig.json
└── .gitignore

// 2. package.json 설정
{
  "name": "my-api",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx watch src/index.ts"
  },
  "engines": {
    "node": ">=18.0.0"  // Node 버전 명시 (권장)
  }
}

// 3. src/index.ts
import express from 'express';

const app = express();
const port = process.env.PORT || 3000;  // Railway가 PORT 제공

app.get('/', (req, res) => {
  res.json({ message: 'Hello from Railway!' });
});

app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

app.listen(port, () => {
  console.log(`서버 실행 중: http://localhost:${port}`);
});

// 4. GitHub에 푸시
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/username/my-api.git
git push -u origin main

// 5. Railway에서 배포
# Railway Dashboard에서 저장소 선택하면 자동 배포 시작

배포 로그 해석:

# 성공적인 배포 로그
 Cloning repository
 Installing dependencies (npm install)
 Building project (npm run build)
 Starting service (npm start)
 Health check passed
 Deployment live at: https://my-api.up.railway.app

# 실패 로그 예시
 Build failed: Module not found
해결: package.json에 의존성 추가

 Start failed: PORT not listening
해결: app.listen(process.env.PORT) 확인

방법 2: CLI 배포

CLI는 로컬 테스트와 디버깅에 유용합니다.

설치 및 인증:

# npm으로 설치
npm install -g @railway/cli

# 또는 Homebrew (macOS)
brew install railway

# 또는 curl (Linux)
sh <(curl -sSL https://railway.app/install.sh)

# 설치 확인
railway --version

# 로그인 (브라우저 열림)
railway login
# GitHub 계정으로 인증

# 인증 확인
railway whoami

프로젝트 초기화:

# 기존 프로젝트 연결
cd my-project
railway link
# 프로젝트 선택 메뉴 표시

# 새 프로젝트 생성
railway init
# 프로젝트 이름 입력

# 연결 확인
railway status

로컬 개발 with Railway 환경:

# Railway 환경 변수를 로컬에서 사용
railway run npm run dev

# 실제 동작:
# 1. Railway에서 환경 변수 다운로드
# 2. 로컬 프로세스에 주입
# 3. 앱 실행

# 예시: DATABASE_URL 사용
railway run node -e "console.log(process.env.DATABASE_URL)"

배포 명령어:

# 현재 디렉토리 배포
railway up

# 특정 디렉토리 배포
railway up --dir ./api

# 환경 지정 배포
railway up --environment production

# 배포 로그 실시간 확인
railway logs

# 특정 서비스 로그
railway logs --service api

# 배포 중단 (Ctrl+C로는 안 됨)
# Railway Dashboard에서 수동으로 중단

고급 CLI 사용법:

# 환경 변수 관리
railway variables set API_KEY=secret123
railway variables get API_KEY
railway variables delete API_KEY

# 데이터베이스 터널링 (로컬에서 프로덕션 DB 접속)
railway connect postgres
# 로컬 포트로 프록시 생성: localhost:5432

# Shell 접속
railway shell
# 컨테이너 내부로 SSH

# 도메인 확인
railway domain

# 프로젝트 삭제
railway unlink

방법 3: Docker 배포

Railway는 Dockerfile을 자동 감지합니다.

기본 Dockerfile:

# Node.js 예시
FROM node:18-alpine

WORKDIR /app

# 의존성 복사 (캐싱 최적화)
COPY package*.json ./
RUN npm ci --only=production

# 소스 코드 복사
COPY . .

# TypeScript 빌드 (필요시)
RUN npm run build

# 실행
CMD ["node", "dist/index.js"]

# Railway가 자동으로 PORT 환경 변수 제공
EXPOSE $PORT

멀티 스테이지 빌드 (권장):

# 빌드 스테이지
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# 프로덕션 스테이지
FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# 빌드된 파일만 복사
COPY --from=builder /app/dist ./dist

CMD ["node", "dist/index.js"]

Docker Compose 지원:

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "${PORT}:3000"
    environment:
      - DATABASE_URL=${DATABASE_URL}
    depends_on:
      - postgres
  
  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

주의사항:

  • Railway는 각 서비스를 별도로 배포
  • docker-compose의 서비스 간 네트워킹은 Railway의 Private Networking 사용
  • 볼륨은 Railway의 Persistent Storage로 대체

자동 배포 설정

Git 브랜치 전략:

main 브랜치 → Production 환경
staging 브랜치 → Staging 환경
feature/* → Preview 환경 (PR 생성 시)

Railway에서 설정:

# 1. 프로젝트 설정 → Environments
# 2. Production 환경 설정:
#    - Deploy on: main 브랜치
#    - Auto Deploy: 활성화

# 3. Staging 환경 추가:
#    - 새 Environment 생성
#    - Deploy on: staging 브랜치

# 4. PR Preview 활성화:
#    - Settings → PR Deploy
#    - 체크 활성화

Webhook 설정 (고급):

// 배포 성공 시 Slack 알림
// Railway Dashboard → Settings → Webhooks

// webhook-handler.js
const express = require('express');
const app = express();

app.post('/webhook', express.json(), (req, res) => {
  const { event, project, environment } = req.body;
  
  if (event === 'deployment.success') {
    // Slack으로 알림 전송
    sendSlackNotification(
      `✅ 배포 성공: ${project.name} (${environment})`
    );
  }
  
  res.sendStatus(200);
});

배포 트러블슈팅

문제 1: 빌드 실패

# 증상
 npm run build failed

# 원인 & 해결
1. package.json에 build 스크립트 없음
   해결: "build": "tsc" 추가

2. TypeScript 컴파일 에러
   해결: 로컬에서 npm run build 확인

3. 메모리 부족
   해결: railway.toml에서 메모리 증가

문제 2: 시작 실패

# 증상
 Service failed to start

# 원인 & 해결
1. PORT 환경 변수 사용
   해결: app.listen(process.env.PORT)

2. start 스크립트 없음
   해결: package.json에 추가

3. 의존성 누락
   해결: package.json 확인

문제 3: 헬스체크 실패

# railway.toml
[deploy]
healthcheckPath = "/health"
healthcheckTimeout = 100
restartPolicyType = "ON_FAILURE"

# 앱에 헬스체크 엔드포인트 추가
app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

3. 데이터베이스 통합

Railway의 가장 강력한 기능 중 하나는 원클릭 데이터베이스 프로비저닝입니다.

PostgreSQL 완벽 가이드

1. 데이터베이스 추가

Railway Dashboard → Your Project → 
New → Database → PostgreSQL → Add PostgreSQL

자동으로 생성되는 것들:

  • PostgreSQL 15 인스턴스
  • 전용 볼륨 (영구 스토리지)
  • 연결 환경 변수들:
    • DATABASE_URL (전체 연결 문자열)
    • PGHOST (호스트 주소)
    • PGPORT (포트 번호, 기본 5432)
    • PGUSER (사용자명)
    • PGPASSWORD (비밀번호)
    • PGDATABASE (데이터베이스 이름)

2. 연결 설정 (Node.js)

// lib/db.ts
import { Pool } from 'pg';

// 방법 1: DATABASE_URL 사용 (권장)
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: false,  // Railway는 SSL 필수
  },
});

// 방법 2: 개별 환경 변수 사용
const pool = new Pool({
  host: process.env.PGHOST,
  port: parseInt(process.env.PGPORT || '5432'),
  user: process.env.PGUSER,
  password: process.env.PGPASSWORD,
  database: process.env.PGDATABASE,
  ssl: {
    rejectUnauthorized: false,
  },
});

// 연결 테스트
pool.query('SELECT NOW()', (err, res) => {
  if (err) {
    console.error('DB 연결 실패:', err);
  } else {
    console.log('DB 연결 성공:', res.rows[0]);
  }
});

export default pool;

3. 마이그레이션 설정

Railway는 마이그레이션을 자동 실행하지 않으므로, 직접 설정해야 합니다.

// migrations/001_create_users.sql
CREATE TABLE IF NOT EXISTS users (
  id SERIAL PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  name VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);

방법 1: node-pg-migrate 사용

# 설치
npm install node-pg-migrate

# package.json에 스크립트 추가
{
  "scripts": {
    "migrate": "node-pg-migrate up",
    "migrate:down": "node-pg-migrate down"
  }
}

# 로컬에서 실행 (Railway DB 사용)
railway run npm run migrate

# 배포 시 자동 실행
# Railway Dashboard → Settings → Deploy →
# Build Command: npm run build
# Start Command: npm run migrate && npm start

방법 2: Prisma 사용

// schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

// package.json
{
  "scripts": {
    "migrate": "prisma migrate deploy",
    "generate": "prisma generate"
  }
}

// 배포 설정
# Start Command: npm run migrate && npm start

4. 실전 CRUD 예제

// api/users.ts
import pool from '../lib/db';
import { Request, Response } from 'express';

// 사용자 목록 조회
export async function getUsers(req: Request, res: Response) {
  try {
    const { rows } = await pool.query(
      'SELECT id, email, name, created_at FROM users ORDER BY created_at DESC'
    );
    res.json(rows);
  } catch (error) {
    console.error('사용자 조회 실패:', error);
    res.status(500).json({ error: '서버 오류' });
  }
}

// 사용자 생성
export async function createUser(req: Request, res: Response) {
  const { email, name } = req.body;
  
  try {
    const { rows } = await pool.query(
      'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *',
      [email, name]
    );
    res.status(201).json(rows[0]);
  } catch (error: any) {
    if (error.code === '23505') {  // unique violation
      res.status(409).json({ error: '이메일이 이미 존재합니다' });
    } else {
      console.error('사용자 생성 실패:', error);
      res.status(500).json({ error: '서버 오류' });
    }
  }
}

// 사용자 업데이트
export async function updateUser(req: Request, res: Response) {
  const { id } = req.params;
  const { name } = req.body;
  
  try {
    const { rows } = await pool.query(
      'UPDATE users SET name = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 RETURNING *',
      [name, id]
    );
    
    if (rows.length === 0) {
      res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
    } else {
      res.json(rows[0]);
    }
  } catch (error) {
    console.error('사용자 업데이트 실패:', error);
    res.status(500).json({ error: '서버 오류' });
  }
}

// 사용자 삭제
export async function deleteUser(req: Request, res: Response) {
  const { id } = req.params;
  
  try {
    const { rowCount } = await pool.query(
      'DELETE FROM users WHERE id = $1',
      [id]
    );
    
    if (rowCount === 0) {
      res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
    } else {
      res.status(204).send();
    }
  } catch (error) {
    console.error('사용자 삭제 실패:', error);
    res.status(500).json({ error: '서버 오류' });
  }
}

5. 연결 풀 최적화

// lib/db.ts
import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: { rejectUnauthorized: false },
  // 연결 풀 설정
  max: 20,                    // 최대 연결 수
  idleTimeoutMillis: 30000,   // 유휴 연결 타임아웃 (30초)
  connectionTimeoutMillis: 2000,  // 연결 타임아웃 (2초)
});

// 연결 풀 이벤트 모니터링
pool.on('connect', () => {
  console.log('새 DB 연결 생성');
});

pool.on('error', (err) => {
  console.error('예기치 않은 DB 오류:', err);
  process.exit(-1);
});

// Graceful Shutdown
process.on('SIGTERM', async () => {
  console.log('SIGTERM 수신, DB 연결 종료 중...');
  await pool.end();
  process.exit(0);
});

export default pool;

Redis 완벽 가이드

1. Redis 추가

Railway Dashboard → Your Project → 
New → Database → Redis → Add Redis

자동 생성:

  • Redis 7.x 인스턴스
  • REDIS_URL 환경 변수
  • 영구 스토리지 (AOF 활성화)

2. Redis 연결 (Node.js)

// lib/redis.ts
import { createClient } from 'redis';

const redis = createClient({
  url: process.env.REDIS_URL,
  socket: {
    reconnectStrategy: (retries) => {
      if (retries > 10) {
        console.error('Redis 재연결 실패');
        return new Error('Redis 재연결 포기');
      }
      // 지수 백오프: 100ms, 200ms, 400ms, ...
      return Math.min(retries * 100, 3000);
    },
  },
});

redis.on('error', (err) => {
  console.error('Redis 오류:', err);
});

redis.on('connect', () => {
  console.log('Redis 연결됨');
});

redis.on('reconnecting', () => {
  console.log('Redis 재연결 중...');
});

// 연결 초기화
export async function connectRedis() {
  await redis.connect();
}

export default redis;

3. 실전 Redis 패턴

패턴 1: 캐싱

// 캐시 헬퍼 함수
async function getCached<T>(
  key: string,
  fetchFn: () => Promise<T>,
  ttl: number = 3600
): Promise<T> {
  // 1. 캐시 확인
  const cached = await redis.get(key);
  if (cached) {
    return JSON.parse(cached);
  }
  
  // 2. 캐시 미스 → 데이터 가져오기
  const data = await fetchFn();
  
  // 3. 캐시 저장
  await redis.setEx(key, ttl, JSON.stringify(data));
  
  return data;
}

// 사용 예시
app.get('/api/users/:id', async (req, res) => {
  const userId = req.params.id;
  
  const user = await getCached(
    `user:${userId}`,
    () => pool.query('SELECT * FROM users WHERE id = $1', [userId])
      .then(result => result.rows[0]),
    300  // 5분 캐시
  );
  
  res.json(user);
});

패턴 2: 세션 관리

import session from 'express-session';
import RedisStore from 'connect-redis';

const sessionStore = new RedisStore({ client: redis });

app.use(session({
  store: sessionStore,
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24,  // 24시간
  },
}));

패턴 3: 레이트 리미팅

async function rateLimit(
  userId: string,
  limit: number,
  windowSec: number
): Promise<boolean> {
  const key = `rate:${userId}`;
  const current = await redis.incr(key);
  
  if (current === 1) {
    await redis.expire(key, windowSec);
  }
  
  return current <= limit;
}

// 미들웨어
app.use(async (req, res, next) => {
  const userId = req.session.userId || req.ip;
  
  const allowed = await rateLimit(
    userId,
    100,   // 100 요청
    60     // 1분
  );
  
  if (!allowed) {
    return res.status(429).json({ error: '요청 제한 초과' });
  }
  
  next();
});

패턴 4: Pub/Sub

// 발행자
await redis.publish('notifications', JSON.stringify({
  userId: 123,
  message: '새 메시지가 도착했습니다',
}));

// 구독자 (별도 프로세스)
const subscriber = redis.duplicate();
await subscriber.connect();

subscriber.subscribe('notifications', (message) => {
  const data = JSON.parse(message);
  console.log('알림 수신:', data);
  // WebSocket으로 클라이언트에 전송
  io.to(data.userId).emit('notification', data);
});

MySQL 및 MongoDB

// MySQL
import mysql from 'mysql2/promise';

const connection = await mysql.createConnection(
  process.env.MYSQL_URL!
);

// MongoDB
import { MongoClient } from 'mongodb';

const client = new MongoClient(process.env.MONGO_URL!);
await client.connect();
const db = client.db();

데이터베이스 백업

자동 백업: Railway는 매일 자동 백업을 생성합니다 (유료 플랜).

수동 백업:

# CLI로 데이터베이스 덤프
railway connect postgres
pg_dump -h localhost -p 5432 -U postgres > backup.sql

# 복원
psql -h localhost -p 5432 -U postgres < backup.sql

4. 환경 변수

설정

# Railway Dashboard → Variables
DATABASE_URL=postgresql://...
API_KEY=secret123
NODE_ENV=production

로컬에서 사용

railway run npm run dev

5. 도메인 설정

Railway 도메인

your-app.up.railway.app

커스텀 도메인

  1. Settings → Domains
  2. 도메인 추가
  3. DNS 설정
Type  Name  Value
CNAME www   your-app.up.railway.app

6. 실전 예제

Express + PostgreSQL

// server.ts
import express from 'express';
import pool from './lib/db';
const app = express();
app.use(express.json());
app.get('/api/users', async (req, res) => {
  const result = await pool.query('SELECT * FROM users');
  res.json(result.rows);
});
app.post('/api/users', async (req, res) => {
  const { email, name } = req.body;
  const result = await pool.query(
    'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *',
    [email, name]
  );
  res.status(201).json(result.rows[0]);
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

package.json

{
  "scripts": {
    "start": "node dist/server.js",
    "build": "tsc",
    "dev": "tsx watch src/server.ts"
  }
}

7. Monorepo 배포

설정

# railway.toml
[build]
builder = "NIXPACKS"
buildCommand = "npm run build --workspace=api"
[deploy]
startCommand = "npm run start --workspace=api"
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 10

8. 로그 확인

railway logs

정리 및 체크리스트

핵심 요약

  • Railway: 간편한 배포 플랫폼
  • Git 연동: 자동 배포
  • 데이터베이스: PostgreSQL, Redis
  • 환경 변수: 간편한 관리
  • 도메인: 커스텀 도메인 지원
  • 저렴한 비용: 사용한 만큼만

구현 체크리스트

  • Railway 계정 생성
  • Git 연동
  • 프로젝트 배포
  • 데이터베이스 추가
  • 환경 변수 설정
  • 도메인 설정
  • 로그 확인

같이 보면 좋은 글


이 글에서 다루는 키워드

Railway, Deployment, PostgreSQL, Redis, DevOps, Hosting, Backend

내부 동작과 핵심 메커니즘

이 글의 주제는 「Railway 완벽 가이드 | 간편한 배포·PostgreSQL·Redis·환경 변수·실전 활용」입니다. 앞선 튜토리얼을 구현·런타임 관점에서 다시 압축합니다. 구성 요소 간 책임 분리와 관측 가능한 지점을 기준으로 “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)·동시성이 어디서 터지는가”를 한 장면으로 그리면 장애 분석이 빨라집니다.

처리 파이프라인(개념도)

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]

경계에서의 지연·실패(시퀀스 관점)

sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(프로세스·런타임·게이트웨이)
  participant D as 의존성(외부 API·DB·큐)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)

알고리즘·프로토콜·리소스 관점 체크포인트

  • 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(버퍼 경계, 프로토콜 상태, 트랜잭션 격리, 파일 디스크립터 상한)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 동일 입력에 동일 출력이 보장되는 순수 층과, 시간·네트워크·스레드 스케줄에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합, GC·할당, 캐시 미스처럼 누적 비용을 의심 목록에 넣습니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때(소켓 버퍼, 큐 깊이, 스트림) 어디서 어떤 신호로 속도를 줄일지 정의합니다.

프로덕션 운영 패턴

실서비스에서는 기능과 함께 관측·배포·보안·비용·규제가 동시에 요구됩니다.

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율/지연 분위수(p95/p99), 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시 계층·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션 호환성·플래그가 문서화되어 있는가
용량피크 트래픽·디스크·파일 디스크립터·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 가능한 한 프로덕션에 가깝게 맞추는 것이 재현율을 높입니다.


확장 예시: 엔드투엔드 미니 시나리오

「Railway 완벽 가이드 | 간편한 배포·PostgreSQL·Redis·환경 변수·실전 활용」을 실제 배포·운영 흐름으로 옮긴 체크리스트형 시나리오입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드 표를 API 또는 이벤트 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 한 화면(로그+메트릭+트레이스)에서 추적한다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지(또는 피처 플래그) 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값이 기대 범위인지 본다.

의사코드 스케치(프레임워크 무관)

handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)        // 경계에서 거절
  authorize(validated, ctx)                  // 권한·테넌트
  result = domainCore(validated)             // 순수에 가까운 규칙
  persistOrEmit(result, idempotentKey)       // I/O: 멱등·재시도 정책
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성 불안정, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정이 로컬과 다름프로필·시크릿·기본값, 지역 리전단일 소스(예: 스키마 검증된 설정)와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

자주 묻는 질문 (FAQ)

Q. Heroku와 비교하면 어떤가요?

A. Railway가 더 저렴하고 간단합니다. Heroku는 더 성숙하지만 비쌉니다.

Q. Vercel과 비교하면 어떤가요?

A. Railway는 백엔드에 적합합니다. Vercel은 프론트엔드에 최적화되어 있습니다.

Q. 무료로 사용할 수 있나요?

A. 매달 $5 크레딧이 제공됩니다. 소규모 프로젝트는 충분합니다.

Q. 프로덕션에서 사용해도 되나요?

A. 네, 많은 스타트업에서 안정적으로 사용하고 있습니다.