Fly.io로 WebSocket 게임 서버 배포하기
"로컬에서 잘 되는데 배포하니까 안 된다"는 웹 개발의 클래식한 문제입니다. 특히 WebSocket 게임 서버는 일반 웹 앱보다 배포가 까다롭습니다. 연결이 지속적이어야 하고, 지연 시간이 낮아야 하며, 서버가 항상 실행 중이어야 합니다. 이 글에서는 Fly.io를 선택한 이유와 배포 과정을 정리합니다.
왜 Fly.io인가?
게임 서버 호스팅 옵션을 비교했습니다:
- Heroku — WebSocket 지원하지만, 30초마다 연결을 끊는 기본 타임아웃이 있음. 게임에 치명적
- Render — 무료 티어가 15분 비활성 시 슬립. 게임 서버로는 부적합
- AWS EC2 — 완전한 제어 가능하지만, 설정이 복잡하고 비용이 높음
- Fly.io — WebSocket 네이티브 지원, Docker 기반, 도쿄 리전 지원, 합리적 가격
Fly.io를 선택한 핵심 이유:
- 도쿄 리전 (nrt) — 한국에서 가장 가까운 리전. 핑 약 30~50ms
- WebSocket 기본 지원 — 별도 설정 없이 WebSocket 업그레이드 처리
- auto_stop OFF 가능 — 게임 서버가 항상 실행되도록 설정 가능
- Docker 기반 — Dockerfile만 있으면 바로 배포
Dockerfile 작성
Node.js 게임 서버를 위한 멀티스테이지 Docker 빌드:
# 빌드 스테이지
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# 프로덕션 스테이지
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 5000
CMD ["node", "server.js"]
멀티스테이지 빌드를 사용하는 이유: 빌드 단계에서 npm ci로 의존성을 설치하고, 프로덕션 이미지에는 필요한 파일만 복사합니다. 개발 의존성(devDependencies)을 제외하여 이미지 크기를 줄입니다.
alpine 기반 이미지를 사용하여 최종 이미지 크기를 약 150MB 이하로 유지합니다. 일반 node:20 이미지는 1GB가 넘지만, alpine은 약 120MB입니다.
fly.toml 설정
Fly.io의 핵심 설정 파일입니다:
app = 'dogkov'
primary_region = 'nrt' # 도쿄
[build]
[http_service]
internal_port = 5000
force_https = true
auto_stop_machines = 'off' # 게임 서버는 항상 실행
auto_start_machines = true
min_machines_running = 1 # 최소 1대 항상 유지
processes = ['app']
[[vm]]
memory = '512mb'
cpu_kind = 'shared'
cpus = 1
핵심 설정 설명:
primary_region = 'nrt'— 나리타(도쿄) 리전. 한국에서 가장 낮은 지연 시간auto_stop_machines = 'off'— 트래픽이 없어도 서버를 끄지 않음. 게임 서버는 항상 대기 상태여야 합니다min_machines_running = 1— 최소 1대의 머신이 항상 실행 중internal_port = 5000— Node.js 서버가 리슨하는 포트force_https = true— HTTPS 강제. WebSocket도 WSS로 자동 업그레이드
배포 프로세스
배포는 한 줄 명령어로 완료됩니다:
# 최초 앱 생성
flyctl launch --name dogkov --region nrt
# 이후 배포
flyctl deploy
# 배포 확인
flyctl status
flyctl logs
flyctl deploy 실행 시 내부 프로세스:
- Dockerfile을 기반으로 Docker 이미지 빌드
- 이미지를 Fly.io 레지스트리에 푸시
- 도쿄 리전에 새 머신 생성
- 헬스 체크 통과 확인
- 기존 머신을 새 버전으로 롤링 업데이트
전체 과정은 약 2~3분 소요됩니다. 블루-그린 배포 방식이라 다운타임이 거의 없습니다.
WebSocket과 Fly.io의 궁합
Fly.io는 WebSocket 연결을 기본적으로 지원합니다. 추가 설정 없이 클라이언트에서 서버로의 WebSocket 업그레이드가 자동으로 처리됩니다.
// 클라이언트 연결 (자동으로 WSS 사용)
const socket = io('https://dogkov.fly.dev', {
transports: ['websocket'], // 폴링 스킵, 직접 WebSocket
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
주의할 점은 transports: ['websocket']을 명시하는 것입니다. Socket.io는 기본적으로 HTTP 폴링으로 시작해서 WebSocket으로 업그레이드하는데, 게임에서는 처음부터 WebSocket을 사용하는 것이 지연 시간을 줄입니다.
모니터링과 디버깅
배포 후 모니터링에 유용한 명령어들:
# 실시간 로그 확인
flyctl logs --app dogkov
# 서버 상태 확인
flyctl status --app dogkov
# SSH 접속 (디버깅용)
flyctl ssh console --app dogkov
# 머신 재시작
flyctl machines restart --app dogkov
서버 코드에 console.log를 남겨두면 flyctl logs로 실시간 확인할 수 있습니다. 플레이어 접속, 방 생성/삭제, 게임 시작/종료 등 주요 이벤트를 로깅하여 서버 상태를 모니터링합니다.
비용
Fly.io의 비용 구조는 단순합니다:
- VM 비용 — shared-cpu-1x, 512MB 메모리: 약 $3.19/월 (항시 실행 기준)
- 대역폭 — 매월 100GB 무료. 게임 데이터는 가벼워서 충분
- 무료 크레딧 — 신규 가입 시 $5 크레딧 제공
소규모 브라우저 게임에는 최적의 가성비입니다. AWS EC2 t3.micro도 비슷한 사양인데 설정 복잡도를 고려하면 Fly.io가 훨씬 편합니다.
마치며
Fly.io + Docker + Node.js 조합은 소규모 실시간 게임 서버 배포에 매우 적합합니다. Docker 이미지만 만들면 한 줄 명령어로 배포되고, WebSocket이 기본 지원되며, 도쿄 리전 덕분에 한국 사용자에게도 낮은 지연 시간을 제공합니다. "점심시간에 브라우저 탭 하나로 바로 게임"이라는 Dogkov의 목표를 실현할 수 있었습니다.