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인가?
전통적인 배포의 문제점:
- 복잡한 설정: AWS, GCP 등은 학습 곡선이 가파름
- 비용 불투명: 예상치 못한 요금 폭탄
- 느린 피드백: 배포 후 결과 확인까지 시간 소요
- 데이터베이스 분리: 앱과 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 경쟁사
| 기능 | Railway | Heroku | Vercel | Render |
|---|---|---|---|---|
| 가격 | $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가지 옵션:
- Deploy from GitHub repo (가장 일반적)
- Empty Project (직접 설정)
- 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
커스텀 도메인
- Settings → Domains
- 도메인 추가
- 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 연동
- 프로젝트 배포
- 데이터베이스 추가
- 환경 변수 설정
- 도메인 설정
- 로그 확인
같이 보면 좋은 글
- Vercel 완벽 가이드
- Docker Compose 완벽 가이드
- Heroku 대안 가이드 | Vercel·Netlify·Railway·Render·Fly.io 완벽 비교
이 글에서 다루는 키워드
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·환경 변수·실전 활용」을 실제 배포·운영 흐름으로 옮긴 체크리스트형 시나리오입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드 표를 API 또는 이벤트 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 한 화면(로그+메트릭+트레이스)에서 추적한다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지(또는 피처 플래그) 확인한다.
- 부하 후 검증: 피크 대비 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 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정이 로컬과 다름 | 프로필·시크릿·기본값, 지역 리전 | 단일 소스(예: 스키마 검증된 설정)와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
자주 묻는 질문 (FAQ)
Q. Heroku와 비교하면 어떤가요?
A. Railway가 더 저렴하고 간단합니다. Heroku는 더 성숙하지만 비쌉니다.
Q. Vercel과 비교하면 어떤가요?
A. Railway는 백엔드에 적합합니다. Vercel은 프론트엔드에 최적화되어 있습니다.
Q. 무료로 사용할 수 있나요?
A. 매달 $5 크레딧이 제공됩니다. 소규모 프로젝트는 충분합니다.
Q. 프로덕션에서 사용해도 되나요?
A. 네, 많은 스타트업에서 안정적으로 사용하고 있습니다.