요약 (TL;DR)
Supabase CLI는 Docker를 사용하여 PostgreSQL, Auth, Storage, Edge Functions 등 전체 Supabase 스택을 로컬 머신에서 실행합니다. brew install supabase/tap/supabase로 설치하고, supabase init 및 supabase start를 실행하여 로컬 환경을 설정한 다음, supabase db push와 supabase functions deploy를 사용하여 프로덕션 환경에 배포하세요. 클라우드에 접속하지 않고도 Supabase 백엔드를 구축하고 테스트하는 가장 빠른 방법입니다.
소개
개발자들이 로컬 테스트를 건너뛰기 때문에 백엔드 버그의 73%가 프로덕션 환경에서 발견됩니다. Supabase CLI를 사용하면 더 이상 이런 변명은 통하지 않습니다. 5분 안에 머신에서 완벽한 프로덕션과 동일한 환경을 얻을 수 있습니다.
여기 진짜 문제가 있습니다: 대부분의 개발자는 프로덕션 환경에서 직접 테스트하거나(위험) 클라우드와 완벽하게 일치하지 않는 로컬 환경을 구성하는 데 몇 시간을 보냅니다(좌절). Supabase CLI는 이 두 가지 문제를 모두 해결합니다. 프로덕션을 정확히 미러링하는 Docker 기반 로컬 스택을 제공하여 로컬에서 작동하는 것이 프로덕션에서도 작동하게 합니다.
Apidog로 Supabase API를 무료로 테스트하세요
이 가이드가 끝나면 다음을 할 수 있게 됩니다:
- 몇 분 만에 완벽한 로컬 Supabase 환경 설정하기
- 버전 관리된 마이그레이션으로 데이터베이스 스키마 변경 관리하기
- 배포 전에 Edge Functions를 로컬에서 빌드하고 테스트하기
- 단일 명령으로 운영 환경에 배포하기
CLI 없이 로컬 Supabase 개발이 어려운 이유
CLI 없이 Supabase 앱을 구축하려고 시도해 본 적이 있다면, 그 고통을 알 것입니다. 다음은 끊임없이 발생하는 세 가지 시나리오입니다.
"운영 환경에서 테스트" 함정. Supabase 대시보드에서 직접 스키마를 변경합니다. 잘 작동합니다. 프론트엔드를 푸시합니다. 3일 후, 동료가 레포를 풀하면 데이터베이스에 새 컬럼이 없어서 앱이 고장 납니다.
환경 불일치. 로컬 PostgreSQL 인스턴스를 설정하고, 수동으로 Supabase 스키마를 다시 생성하며, Row Level Security 정책이 로컬에서 다르게 작동하는 이유를 디버깅하는 데 두 시간을 보냅니다. 정책이 다르게 작동하는 것이 아니라, 정책 하나를 놓친 것입니다.
"내 컴퓨터에서는 잘 되는데" 문제. Edge Function은 Supabase 대시보드 편집기에서는 작동하지만, 실제 환경 변수 대신 하드코딩된 값으로 테스트했기 때문에 프로덕션 환경에서는 실패합니다.
이것들은 특수한 경우가 아닙니다. 스키마 드리프트(로컬 및 원격 데이터베이스의 동기화 불일치)는 Supabase를 사용하는 팀에서 가장 많이 보고되는 문제입니다. CLI는 이 세 가지 문제를 모두 해결합니다:
- 마이그레이션은 스키마 변경 사항을 버전 관리하고 재현 가능하게 유지합니다.
- 로컬 Docker 스택은 프로덕션을 정확히 미러링하며, 동일한 PostgreSQL 버전, 동일한 RLS 엔진을 사용합니다.
- 로컬 함수 서빙은 실제 환경 변수로 Edge Functions를 테스트합니다.
Supabase CLI 작동 방식
로컬 스택
supabase start를 실행하면 CLI는 다음 서비스가 포함된 Docker Compose 스택을 시작합니다:
| 서비스 | 포트 | 목적 |
|---|---|---|
| PostgreSQL | 54322 | 데이터베이스 |
| PostgREST | 54321 | 자동 생성 REST API |
| GoTrue | 54321/auth | 인증 서비스 |
| Realtime | 54321/realtime | WebSocket 구독 |
| Storage | 54321/storage | 파일 스토리지 |
| Studio | 54323 | 시각적 대시보드 |
| Inbucket | 54324 | 이메일 테스트 (모든 이메일을 로컬에서 포착) |
| Edge Runtime | 54321/functions | Deno 기반 함수 러너 |
이것은 Supabase Cloud에서 실행되는 것과 동일한 스택입니다. 여러분의 로컬 머신에서요.
설치
macOS:
brew install supabase/tap/supabase
Windows (Scoop):
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase
Linux / npm:
npm install -g supabase
설치 확인:
supabase --version
# supabase 1.x.x
supabase start프로젝트 설정
mkdir my-project && cd my-project
supabase init
이렇게 하면 다음이 생성됩니다:
supabase/
├── config.toml # 포트, 인증 설정, 스토리지 구성
├── seed.sql # 데이터베이스 재설정 시마다 로드되는 개발 데이터
└── migrations/ # 스키마 버전 기록
로컬 스택 시작하기
supabase start
첫 실행 시 약 1GB의 Docker 이미지를 다운로드합니다. 그 후에는 시작하는 데 약 10초가 걸립니다.
API URL: http://localhost:54321
DB URL: postgresql://postgres:postgres@localhost:54322/postgres
Studio: http://localhost:54323
anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
anon key를 .env.local 파일에 복사하세요. 프론트엔드에 필요할 것입니다.
마이그레이션을 통한 데이터베이스 관리
마이그레이션은 CLI 워크플로우의 핵심입니다. 모든 스키마 변경은 Git에서 추적되는 버전 관리된 SQL 파일이 됩니다. 더 이상 "누가 언제 데이터베이스를 변경했는지"에 대해 고민할 필요가 없습니다.
첫 마이그레이션 생성
supabase migration new create_posts_table
# 생성됨: supabase/migrations/20260324120000_create_posts_table.sql
파일 편집:
-- RLS와 함께 posts 테이블 생성
CREATE TABLE posts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
title TEXT NOT NULL,
content TEXT,
published BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Row Level Security 활성화
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- 누구나 발행된 게시물을 읽을 수 있음
CREATE POLICY "Anyone can read published posts"
ON posts FOR SELECT
USING (published = true);
-- 사용자는 자신의 게시물을 관리
CREATE POLICY "Users manage own posts"
ON posts FOR ALL
USING (auth.uid() = user_id);
-- 변경 시마다 updated_at 자동 업데이트
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER posts_updated_at
BEFORE UPDATE ON posts
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
적용하기:
supabase migration up
TypeScript 타입 생성
스키마 변경 후에는 항상 타입을 다시 생성하세요:
supabase gen types typescript --local > src/types/database.ts
프론트엔드가 완전한 타입 안전성을 확보합니다:
import { Database } from '@/types/database'
type Post = Database['public']['Tables']['posts']['Row']
type NewPost = Database['public']['Tables']['posts']['Insert']
// 이제 에디터가 런타임 전에 타입 에러를 잡아냅니다
const createPost = async (post: NewPost) => {
const { data, error } = await supabase
.from('posts')
.insert(post)
.select()
.single()
return data
}
개발 데이터 시딩
supabase/seed.sql 편집:
-- 테스트 사용자 (로컬 개발용으로 인증 우회)
INSERT INTO auth.users (id, email) VALUES
('00000000-0000-0000-0000-000000000001', 'alice@example.com'),
('00000000-0000-0000-0000-000000000002', 'bob@example.com');
-- 테스트 게시물
INSERT INTO posts (user_id, title, content, published) VALUES
('00000000-0000-0000-0000-000000000001', 'Supabase 시작하기', '내가 배운 것은...', true),
('00000000-0000-0000-0000-000000000002', '초안: API 설계 패턴', '작업 중...', false);
언제든지 재설정 및 시드 재적용:
supabase db reset
이렇게 하면 모든 것이 초기화되고, 모든 마이그레이션이 다시 실행되며, 시드 데이터가 로드됩니다. 매일 아침 새롭게 시작하기 위해 실행하세요.
Apidog로 Supabase API 테스트하기
로컬 Supabase가 실행되면 http://localhost:54321에 완벽하게 작동하는 REST API가 생성됩니다. Supabase는 PostgREST를 통해 모든 테이블에 대한 엔드포인트를 자동 생성합니다. curl로 이러한 엔드포인트를 수동으로 테스트하는 것은 특히 다른 사용자 토큰으로 RLS 정책을 테스트해야 할 때 빠르게 번거로워집니다.
Apidog는 로컬 Supabase 인스턴스에 직접 연결됩니다. 다음을 수행할 수 있습니다:
- 요청을 재사용 가능한 컬렉션으로 저장
- 환경을 전환하여 다른 사용자로 동일한 엔드포인트 테스트
- 어설션 추가("응답에 최소 1개의 게시물 포함") 및 테스트 스위트로 실행
- 팀과 API 문서를 자동으로 공유
로컬 Supabase로 Apidog 설정하기:
- Apidog에서 새 프로젝트 생성
- 기본 URL 설정:
http://localhost:54321 - 환경 변수 추가:
anon_key = 여러분의-로컬-익명-키 - 인증 헤더 추가:
Bearer {{anon_key}}
posts 엔드포인트 테스트하기:
GET http://localhost:54321/rest/v1/posts?published=eq.true
Authorization: Bearer {{anon_key}}
apikey: {{anon_key}}
이를 요청으로 저장하고, 응답에 최소 하나의 게시물이 포함되어 있다는 어설션을 추가한 다음, RLS 정책을 변경할 때마다 실행하세요. 프로덕션에 도달하기 전에 깨진 정책을 잡아낼 수 있습니다.
Apidog로 Supabase API 테스트 시작하기
Edge Functions: 로컬에서 빌드 및 테스트
Edge Functions는 사용자에게 가까운 엣지에서 Deno로 실행됩니다. 웹훅, 백그라운드 작업, 서버 측 로직이 필요한 API 엔드포인트에 완벽합니다.
함수 생성
supabase functions new send-welcome-email
이렇게 하면 supabase/functions/send-welcome-email/index.ts가 생성됩니다:
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
const { user_id } = await req.json()
// 서비스 역할은 RLS를 우회합니다 - 주의해서 사용하세요
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
const { data: profile } = await supabase
.from('profiles')
.select('email, full_name')
.eq('id', user_id)
.single()
// 여기에 이메일 발송 로직을 추가하세요
console.log(`Sending welcome email to ${profile?.email}`)
return new Response(
JSON.stringify({ success: true }),
{ headers: { 'Content-Type': 'application/json' } }
)
})
핫 리로드로 로컬 테스트
supabase functions serve
함수 서버는 파일 변경 사항을 감지하고 자동으로 다시 로드합니다. 테스트해 보세요:
curl -X POST http://localhost:54321/functions/v1/send-welcome-email \
-H "Authorization: Bearer YOUR_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"user_id": "00000000-0000-0000-0000-000000000001"}'
운영 환경 배포
# 단일 함수 배포
supabase functions deploy send-welcome-email
# 모든 함수 배포
supabase functions deploy
고급 기술 및 검증된 접근 방식
시크릿 관리
함수에 API 키를 하드코딩하지 마세요. 시크릿을 사용하세요:
# 운영 시크릿 설정
supabase secrets set RESEND_API_KEY=re_xxx STRIPE_KEY=sk_live_xxx
# 모든 시크릿 나열
supabase secrets list
# 시크릿 제거
supabase secrets unset STRIPE_KEY
함수에서 접근:
const resendKey = Deno.env.get('RESEND_API_KEY')
// 절대로 이렇게 하지 마세요: const resendKey = 're_xxx'
데이터베이스 브랜칭
큰 스키마 변경 작업을 하고 있나요? 격리된 브랜치를 생성하세요:
supabase branches create feature-payments
supabase branches switch feature-payments
# 변경사항을 만들고 테스트한 다음 병합
supabase branches merge feature-payments
이렇게 하면 실험하는 동안 주요 개발 데이터베이스를 깨끗하게 유지할 수 있습니다.
피해야 할 일반적인 실수
- Studio에서 데이터베이스를 직접 편집하는 것. 항상 마이그레이션을 사용하세요. 직접 편집은 추적되지 않으며 동료들에게도 적용되지 않습니다.
.env파일을 커밋하는 것. 운영 환경에서는supabase secrets set을 사용하세요..gitignore에.env*를 추가하세요.- 풀 후
supabase db reset을 건너뛰는 것. 동료의 변경 사항을 풀할 때, 새로운 마이그레이션이 로컬에서 실행되어야 합니다. 재설정을 통해 적용하세요. - 스키마 변경 후 타입을 다시 생성하지 않는 것. 컬럼을 추가하는 순간 TypeScript 타입은 오래된 것이 됩니다. 타입 생성을 마이그레이션 워크플로우의 일부로 만드세요.
- 로컬 테스트 없이 함수를 배포하는 것. 배포하기 전에 항상
supabase functions serve를 실행하고 실제 요청으로 테스트하세요. - 프론트엔드 코드에서 서비스 역할 키를 사용하는 것. 서비스 역할 키는 RLS를 우회합니다. Edge Functions 및 서버 측 코드에만 속하며, 브라우저에서는 절대로 사용하지 마세요.
성능 팁
# 메모리를 절약하기 위해 필요 없는 서비스는 건너뛰세요
supabase start --exclude-studio --exclude-inbucket
# 어떤 리소스가 사용 중인지 확인
docker stats
대안 및 비교
| 기능 | Supabase CLI | Firebase CLI | PlanetScale CLI |
|---|---|---|---|
| 로컬 데이터베이스 | 완전한 PostgreSQL | 에뮬레이터만 | 클라우드만 |
| 마이그레이션 | Git의 SQL 파일 | 기본 지원 없음 | 브랜칭 |
| Edge Functions | Deno 런타임 | 클라우드 함수 | 포함되지 않음 |
| 로컬 인증 | 완전한 GoTrue | 에뮬레이터 | 포함되지 않음 |
| 오픈 소스 | 완전한 오픈 소스 | 독점 | 독점 |
| 타입 생성 | 내장 | 수동 | 수동 |
Firebase의 로컬 에뮬레이터는 빠른 프로토타이핑에는 좋지만, 실제 PostgreSQL 인스턴스를 제공하지 않습니다. PlanetScale의 브랜칭 모델은 스키마 변경에는 탁월하지만, 항상 클라우드에 의존해야 합니다. Supabase CLI는 완전한 오픈 소스, PostgreSQL 네이티브 로컬 개발 경험을 원하는 팀에게 가장 적합합니다.
실제 사용 사례
멀티 테넌트 데이터를 사용하는 SaaS 애플리케이션. 핀테크 스타트업은 세 가지 환경(개발, 스테이징, 운영)에 걸쳐 47개의 마이그레이션을 관리합니다. RLS 정책은 어떤 코드도 운영 환경에 도달하기 전에 다른 사용자 역할로 로컬에서 테스트됩니다. 결과: 6개월 동안 스키마 관련 운영 환경 사고가 전혀 없었습니다.
전자상거래 주문 처리. 전자상거래 팀은 Stripe 웹훅 처리에 Edge Functions를 사용합니다. 그들은 실제 Stripe 테스트 이벤트를 사용하여 supabase functions serve로 웹훅 페이로드를 로컬에서 테스트합니다. 배포 시간이 2시간에서 15분으로 단축되었습니다.
모바일 앱 백엔드. React Native 팀은 모든 마이그레이션 후 TypeScript 타입을 생성하고 이를 내부 npm 패키지로 공유합니다. 프론트엔드와 백엔드는 자동으로 동기화됩니다. 더 이상 Slack에서 "이 엔드포인트가 어떤 필드를 반환하나요?"라는 질문을 하지 않습니다.
마무리
이제 다음을 할 수 있습니다:
- 몇 분 만에 완벽한 로컬 Supabase 환경 설정하기
- 마이그레이션을 사용하여 모든 스키마 변경을 버전 관리하기
- 핫 리로드로 Edge Functions를 로컬에서 테스트하기
- 스키마에서 TypeScript 타입을 자동으로 생성하기
supabase db push와supabase functions deploy로 배포하기- 운영 환경에 배포하기 전에 Apidog로 API 테스트하기
이 워크플로우는 즉시 효과를 발휘합니다. 팀은 더 빠르게 배포하고, 버그를 더 일찍 잡아내며, 다시는 스키마 드리프트에 시달리지 않을 것입니다.
다음 단계:
- 설치:
brew install supabase/tap/supabase - 프로젝트에서
supabase init실행 - 첫 마이그레이션 생성
- 로컬 엔드포인트를 테스트하기 위해 Apidog 설정
- 자신 있게 운영 환경에 배포
Apidog로 Supabase API를 무료로 테스트하세요
자주 묻는 질문 (FAQ)
Supabase CLI를 사용하려면 Docker가 필요합니까?네. supabase start를 실행하기 전에 Docker Desktop이 실행 중이어야 합니다. CLI는 Docker Compose를 사용하여 전체 스택을 로컬에서 실행합니다. Docker가 실행되고 있지 않으면 "Cannot connect to Docker daemon" 오류가 발생합니다.
로컬 데이터베이스를 운영 환경과 동기화하려면 어떻게 해야 합니까?supabase db pull을 사용하여 원격 스키마에서 마이그레이션을 생성한 다음, supabase db push를 사용하여 로컬 마이그레이션을 운영 환경에 적용합니다. 풀 후 로컬에서 supabase db reset을 실행하여 환경이 일치하는지 확인하세요.
Supabase Cloud 계정 없이 Supabase CLI를 사용할 수 있습니까?네. 클라우드 계정 없이도 개발을 위해 CLI를 완전히 로컬에서 사용할 수 있습니다. 운영 환경에 배포할 준비가 되었을 때만 supabase login 및 supabase link가 필요합니다.
팀에서 마이그레이션 충돌을 어떻게 처리해야 합니까?새로운 마이그레이션을 생성하기 전에 최신 Git 변경 사항을 풀하고 supabase db reset을 실행하세요. 설명적인 마이그레이션 이름을 사용하고, 호환성을 깨는 스키마 변경을 할 때는 팀원들과 소통하세요.
supabase db push와 supabase migration up의 차이점은 무엇입니까?supabase migration up은 보류 중인 마이그레이션을 로컬 데이터베이스에 적용합니다. supabase db push는 원격(운영) 프로젝트에 적용합니다. 항상 로컬에서 먼저 테스트하세요.
기존 프로젝트와 Supabase CLI를 사용할 수 있습니까?네. supabase link --project-ref YOUR_PROJECT_ID를 실행하여 기존 프로젝트에 연결한 다음, supabase db pull을 사용하여 현재 원격 스키마에서 마이그레이션을 생성할 수 있습니다.
RLS 정책을 로컬에서 어떻게 테스트합니까?http://localhost:54323의 Supabase Studio를 사용하여 사용자 역할을 전환하거나, 다른 JWT 토큰으로 API를 통해 테스트하세요. Apidog는 이를 쉽게 만듭니다. 다른 사용자 토큰으로 여러 환경을 생성하고 다른 사용자로 동일한 요청을 실행할 수 있습니다.
Supabase CLI는 무료입니까?네. CLI는 무료이며 오픈 소스입니다. 로컬 개발에는 비용이 들지 않습니다. Supabase Cloud 리소스는 운영 환경에 배포할 때만 비용을 지불합니다.
