Next.js + Supabase Docker 셀프 호스팅 구축기

저렴한 클라우드 VPS의 성능 한계를 극복하기 위해, 홈 서버에 Next.js와 Supabase 전체 스택을 Docker로 구축한 과정을 상세히 공유합니다. 비용 효율성과 성능 최적화를 동시에 달성하며, Supabase Storage를 완벽하게 활용하는 노하우를 소개합니다. 물리적인 인프라를 직접 제어하며 얻는 성능과 안정성에 대한 인사이트를 얻어가세요.

ByteGaze
2025-12-17
9분 소요

처음엔 저도 당연히 클라우드가 정답인 줄 알았습니다.

Vultr 계정에 잔액이 좀 남아있길래, 가장 저렴한 $6짜리 VPS 인스턴스를 하나 띄워 Next.js를 올리고 DB는 **Supabase Cloud(Free, 서울 리전)**를 연결했습니다. "같은 서울이니까 빠르겠지?"라고 생각했죠.

하지만 현실은 미묘하게 달랐습니다:

  1. CPU 병목: $6짜리 VPS의 빈약한 vCPU는 Next.js의 SSR 렌더링만으로도 숨이 찼습니다. 여기에 외부 HTTPS 통신(DB 연결) 오버헤드까지 더해지니 반응이 굼떴습니다.

  2. 데이터센터 간 지연: Vultr와 Supabase(AWS)가 물리적으로는 같은 서울에 있지만, 서로 다른 통신망을 타기 때문에 **"외부 인터넷"**을 거쳐야 합니다.

"아, 답답해서 못 쓰겠다!"

결국 눈길을 돌린 건 책상 위에서 조용히 먼지만 쌓여가던 미니 PC, TOPFEEL T1이었습니다.

▲ 나의 새로운 홈 서버: TOPFEEL T1 (작다고 무시하면 다칩니다)

이 녀석의 스펙을 기존에 쓰던 $6짜리 Vultr 인스턴스와 비교해보니, 이건 "다윗과 골리앗" 싸움도 아닌 "개미와 티라노사우루스" 수준의 체급 차이가 났습니다.

**구분**

**Vultr Cloud (High Frequency)**

**TOPFEEL T1 (Home Server)**

**비고**

**CPU**

1 vCPU (Intel)

**AMD Ryzen 7 8745HS (8C/16T)**

1코어 vs 16스레드

**RAM**

1 GB

**32 GB DDR5**

대역폭과 용량 모두 압승

**Storage**

32 GB NVMe

**2TB NVMe (PCIe 4.0)**

용량 64배 차이

**Cost**

**$6.00/월** (약 8,500원)

**전기세 약 4,000원 ~ 5000원/월**

성능이 뛰어남에도 비용이 더 저렴

⚡ 퍼포먼스: "같은 서울"이어도 로컬이 더 빠른 이유

Supabase 서울 리전을 쓰면 핑(Ping)은 3~5ms 내외로 매우 빠릅니다. 하지만 **"빠른 것"**과 **"즉각적인 것(Instant)"**의 차이는 큽니다.

클라우드 환경에서는 쿼리 하나를 날릴 때마다 Next.js(Vultr)인터넷망Supabase(AWS) 과정을 거치며 SSL 핸드쉐이크네트워크 홉이 발생합니다. 하지만 셀프 호스팅은 이 모든 과정이 생략됩니다.

**비교 항목**

**Supabase Cloud (서울)**

**Self-Hosted (로컬)**

**차이점**

**통신 경로**

Vultr ↔ Public Internet ↔ AWS

**Docker Bridge Network**

외부망 vs 내부망

**지연 시간 (RTT)**

2ms ~ 10ms (가변적)

**< 0.1ms (마이크로초)**

**비교 불가**

**안정성**

인터넷 회선 상태 영향 받음

**네트워크 변수 0%**

💡 핵심: 단순히 핑(Ping) 차이뿐만이 아닙니다. **Docker 내부망(Loopback)**을 통한 통신은 네트워크 패킷 처리 오버헤드가 거의 없어, CPU 부하를 획기적으로 줄여줍니다. 저사양/고사양을 막론하고 **"체감 빠릿함"**이 다를 수밖에 없습니다.

💡 팩트 체크: 전기세 폭탄? 직접 계산해봤습니다

"성능 좋은 건 알겠는데, 24시간 켜두면 전기세 폭탄 맞는 거 아냐?"라고 걱정하실 수 있습니다. 그래서 Ryzen 7 8745HS 기준으로 전력 효율 세팅을 적용해 시뮬레이션을 돌려봤습니다.

  • 최적 세팅: 바이오스에서 SmartShift 35W 제한

  • 실제 소비전력:

    • 풀로드(Full Load): 시스템 전체 약 56W

    • 일상 평균(Idle/Web): 시스템 전체 약 25W

  • 한 달 예상 요금 (24시간 가동 시):

    • 월 사용량: 약 18kWh (25W × 24h × 30일)

    • 예상 비용: 약 3,900원 ~ 5,500원 (주택용 누진세 2~3단계 기준)

결론적으로, 서버를 하루 종일 켜둬도 Vultr 최저가 요금($6, 약 8,500원)의 절반 수준입니다. 성능은 수십 배 좋으면서 유지비는 더 저렴한 셈이죠.


"저 깡패 같은 성능을 놔두고 왜 내가 1GB 램에 갇혀서 고통받아야 하지?"

그래서 결심했습니다. "모든 걸 집으로 가져오자. 성능도, 데이터도, 속도도!"

이번 프로젝트에서는 미니PC를 활용해 Next.jsSupabase 전체 스택을 Docker로 띄워 운영하기로 결정했습니다.

Docker Host 안에 Next.js App 컨테이너와 Supabase(Kong, Auth, DB, Realtime 등) 컨테이너들이 떠 있는 모습

1. 왜 Supabase Self-Hosting인가?

Supabase는 오픈소스 Firebase 대안으로 아주 훌륭합니다. 특히 docker-compose 하나로 인증(GoTrue), 스토리지, 실시간 구독(Realtime), API 게이트웨이(Kong)까지 한방에 구축할 수 있다는 게 엄청난 매력이었죠.

단, 홈 서버에서 돌릴 때는 미디어 파일 처리배포의 유연성을 고민해야 했습니다.

2. 미디어 서버 분리 vs Supabase Storage

처음에는 Nginx를 통해 로컬 디렉토리에 정적 파일을 직접 저장하고 서빙하려 했습니다. 하지만 이 방식은 확장성도 떨어지고, 무엇보다 Supabase 생태계의 장점을 전혀 활용하지 못하는 구식 방식이었습니다.

❌ (Before) 버려진 로컬 파일시스템 코드

처음 작성했던 코드는 대략 이랬습니다. 디렉토리가 없으면 생성하고, 경로를 계산해서 파일을 쓰고, URL을 반환하는... 아주 귀찮은 작업들이었죠.

TypeScript

// src/utils/local-storage.ts (이제는 안 씁니다) import fs from 'fs'; import path from 'path';

export async function saveFileLocally(file: Buffer, filename: string) { // 1. 저장할 디렉토리 경로 설정 (public 폴더 내 uploads) const uploadDir = path.join(process.cwd(), 'public/uploads');

// 2. 디렉토리가 없으면 생성 (recursive 옵션 필수) if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); }

// 3. 파일 쓰기 const filePath = path.join(uploadDir, filename); fs.writeFileSync(filePath, file);

// 4. 접근 가능한 정적 URL 반환 return /uploads/${filename}; }

✅ (After) Supabase Storage 전면 도입

Supabase Storage를 사용하니 로컬 파일시스템을 직접 제어할 필요가 싹 사라졌습니다. 로컬 Docker 환경에서도 S3와 동일한 방식의 객체 스토리지를 사용할 수 있게 된 것이죠.

TypeScript

// src/utils/storage.ts // 위에서 짰던 복잡한 fs 코드는 다 갖다 버리고 이렇게 깔끔해졌습니다. export async function uploadToStorage(file: Buffer, path: string) { const { data, error } = await supabase.storage .from('media') // 버킷 이름 .upload(path, file, { contentType: 'image/webp', upsert: true });

if (error) throw error;

// 저장된 파일의 공개 URL을 바로 받아옵니다. const { data: { publicUrl } } = supabase.storage .from('media') .getPublicUrl(data.path);

return publicUrl; }

![이미지: Supabase Studio의 Storage 대시보드 화면]

로컬 Docker 환경에서도 S3 부럽지 않은 파일 관리 대시보드를 사용할 수 있습니다.

🔗 참고: 더 자세한 설정 방법은 Supabase 공식 셀프 호스팅 가이드를 참고하세요.

3. 마무리 및 예고

이렇게 홈 서버 하나에 빵빵한 풀스택 환경을 구축했습니다. 비용은 전기세 뿐입니다.

하지만, "집에 정전이 나면 어떡하지?"

다음 편에서는 단돈 $6로 엔터프라이즈급 무중단 재해 복구(DR) 시스템을 구축한 이야기를 다룹니다. (힌트: Vultr + Cloudflare)

👉 다음 글: 홈 서버가 죽어도 서비스는 죽지 않는다 (Zero-Downtime DR) 보러 가기