VPS에 Supabase 직접 설치하고 Docker로 운영하기
공식 Docker Compose 스택을 사용하여 나만의 서버에 Supabase를 구축하는 방법을 설명합니다. 보안을 위해 반드시 교체해야 할 비밀값, 14개 서비스의 역할, 권장 RAM 사양, 데이터 손실 없는 업데이트 및 백업 가이드를 상세히 다룹니다.
구축할 내용
Supabase를 직접 호스팅한다는 것은 공식 Docker Compose 스택을 자신의 서버에서 실행하는 것을 의미합니다. 여기에는 Postgres, 그 앞단의 REST API, 인증 서비스, 파일 스토리지, 실시간 웹소켓, 그리고 Studio 대시보드가 포함됩니다. 하나의 저장소를 복제하고 하나의 .env 파일을 수정한 뒤, 약 14개의 컨테이너를 실행하면 사용자가 직접 제어하는 Supabase 프로젝트처럼 동작합니다.
설치 과정은 짧습니다. 문제가 발생하는 지점은 .env 파일입니다. 이 파일은 저장소에 공개된 데모용 비밀값(secrets)을 포함한 채 배포되므로, 기본 설정 그대로 스택을 시작하면 이를 발견한 누구에게나 서버가 노출됩니다. 이 가이드에서는 반드시 교체해야 할 비밀값, 각 서비스의 용도, 스택이 실제로 필요로 하는 메모리 용량, 그리고 데이터베이스를 삭제하지 않고 업데이트하는 방법을 다룹니다.
Compose 자체가 생소하다면 먼저 Docker Compose 기초 (VPS 환경)를 읽어보시기 바랍니다. 아래의 모든 내용은 docker compose version 명령어가 이미 버전을 정상적으로 출력하는 환경을 전제로 합니다.
스택의 실제 구성 요소
Supabase는 단일 프로그램이 아닙니다. Compose 파일은 하나의 네트워크 위에서 여러 개의 개별 서비스를 시작하며, 각 서비스의 역할을 이해하면 복잡한 컨테이너 이름들을 디버깅 가능한 단위로 파악할 수 있습니다.
db은 Supabase 확장 기능이 로드된 PostgreSQL입니다. 다른 모든 서비스가 이 컨테이너와 통신합니다. 이 컨테이너가 비정상 상태라면 다른 모든 서비스도 실패합니다.kong는 API 게이트웨이입니다. 포트 8000에서 대기하며/rest/v1/,/auth/v1/,/storage/v1/요청을 적절한 백엔드로 라우팅합니다. 외부로 노출해야 하는 유일한 컨테이너입니다.rest은 PostgREST입니다. Postgres 스키마를 읽어 REST API로 제공하므로, 코드를 작성하지 않아도 새로운 테이블이 새로운 엔드포인트가 됩니다.auth은 GoTrue입니다. 사용자를 식별하는 JSON 웹 토큰(JWT)을 발행합니다.storage과imgproxy는 파일 업로드 및 이미지 크기 조정을 처리합니다.realtime은 데이터베이스 변경 사항을 웹소켓을 통해 스트리밍합니다.studio과meta는 대시보드와 그 이면의 관리용 API입니다.analytics(Logflare)과vector는 로그를 수집하며,supavisor는 Postgres 연결 풀러입니다.
위 목록이 아래에 명시된 리소스 요구 사항의 이유입니다. 단순히 데이터베이스 하나를 실행하는 것이 아니라, 데이터베이스와 수십 개의 지원 서비스를 함께 실행하는 것이기 때문입니다.
사이징: 8 GB RAM 계획
2026년 7월 기준, 신규 설치 시 스택은 사용자 데이터나 트래픽을 제외하고도 약 2.5에서 3 GB의 상주 메모리를 점유합니다. 분석 서비스와 Studio Node.js 프로세스가 가장 많은 메모리를 소비하는 두 가지 요소입니다. 2 GB 서버에서는 컨테이너가 시작되더라도 커널의 out of memory killer에 의해 하나가 종료되며, 보통 analytics 또는 db에서 발생합니다. 이 경우 컨테이너가 exit code 137과 함께 재시작을 반복하는 증상이 나타납니다.
운영이 필요한 환경이라면 8 GB RAM과 4 vCPU를 할당하십시오. 4 GB는 무거운 쿼리와 Studio 세션을 동시에 실행할 때 속도가 저하되는 것을 감수한다면 단일 개발 인스턴스용으로 사용할 수 있습니다. Postgres, 스토리지 볼륨, 로그 데이터가 모두 프로젝트 디렉터리 하위에 위치하므로 디스크 용량도 중요합니다. 40 GB로 시작하여 모니터링하십시오. PhotoPrism 및 Immich와 같은 서비스는 퀵 스타트 페이지에 명시된 것보다 훨씬 높은 최소 RAM 요구 사양을 가지고 있으므로, 셀프 호스팅을 할 때는 플랜을 선택하기 전에 서비스별 요구 사양을 미리 확인하는 습관을 들이는 것이 좋습니다.
설치: 공식 저장소 복제
지원되는 경로를 사용하면 메인 저장소에서 docker 디렉터리를 자신의 프로젝트 디렉터리로 복사하게 됩니다. 이러한 분리는 중요합니다. 나중에 git pull를 실행해도 사용자의 .env이 덮어쓰이지 않기 때문입니다.
git clone --depth 1 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/* supabase-project
cp supabase/docker/.env.example supabase-project/.env
cd supabase-project
docker compose pulldocker compose pull은 수 기가바이트의 이미지를 다운로드합니다. 모든 서비스가 Pulled로 표시되면서 완료되어야 합니다. 여기서 manifest unknown 오류가 발생한다면 고정된 이미지 태그가 업스트림에서 제거된 것입니다. 이 경우 태그를 직접 수정하기보다 저장소의 최신 사본을 다시 내려받아 해결해야 합니다.
첫 시작 전에 변경해야 할 보안 정보
스택을 시작하기 전에 이 작업을 수행하십시오. 시작 후에는 늦습니다. 이 값 중 일부는 최초 부팅 시 데이터에 기록되므로, 나중에 변경하려면 데이터베이스를 초기화해야 합니다.
저장소에는 모든 값을 올바르게 생성하는 생성기가 포함되어 있습니다. 여기에는 새로운 JWT 비밀 키로 서명해야 하는 두 개의 API 키도 포함됩니다.
sh utils/generate-keys.sh --update-env이 스크립트는 JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, SECRET_KEY_BASE, REALTIME_DB_ENC_KEY, VAULT_ENC_KEY, PG_META_CRYPTO_KEY 및 Logflare 토큰에 대한 새로운 값을 .env에 기록합니다. 이 스크립트는 일반적인 Ubuntu 이미지에 포함되어 있는 openssl를 필요로 합니다.
스크립트가 설정하지 않으므로 .env에서 직접 수정해야 하는 두 가지 값은 다음과 같습니다.
POSTGRES_PASSWORD. 영문자와 숫자만 사용하십시오. 여기에 구두점을 사용하면 여러 서비스가 문자열을 결합하여 생성하는 연결 문자열이 손상됩니다. 이 경우 구문 분석 오류가 아닌 인증 오류처럼 보이기 때문에 사용자가 엉뚱한 곳을 찾게 됩니다.DASHBOARD_USERNAME및DASHBOARD_PASSWORD. 이는 Studio를 위한 기본 인증 자격 증명입니다. 제공되는 기본 비밀번호는 말 그대로this_password_is_insecure_and_should_be_updated입니다.
ANON_KEY 및 SERVICE_ROLE_KEY를 임의로 만들 수 없는 이유를 이해해야 합니다. 두 값 모두 JWT_SECRET으로 서명된 JWT입니다. 게이트웨이는 모든 요청마다 해당 서명을 검증하므로, 비밀 키와 일치하지 않는 키는 {"message":"Invalid authentication credentials"} 오류와 함께 거부됩니다. 이는 자체 호스팅 시 가장 흔히 발생하는 실패 사례입니다. 운영자가 JWT_SECRET는 변경했지만 데모 키는 그대로 유지한 경우입니다. 항상 세 가지를 모두 함께 생성하십시오.
SERVICE_ROLE_KEY은 root 비밀번호처럼 취급하십시오. 이 키는 행 수준 보안(row level security)을 완전히 우회합니다. 서버 측 코드 외의 다른 곳에는 절대 두지 마십시오.
SITE_URL 및 API_EXTERNAL_URL는 사용자가 실제로 접속할 주소(예: https://supabase.example.com)로 설정하십시오. Auth 서비스는 이 값들을 기반으로 이메일 확인 및 OAuth 콜백 링크를 생성합니다. 따라서 기본값인 http://localhost:8000로 그대로 두면 모든 사용자가 자신의 로컬 컴퓨터로 연결됩니다.
그런 다음 설정 내용을 확인하십시오.
sh run.sh secrets서비스를 시작하고 상태 확인하기
sh run.sh start
docker compose psrun.sh start은 docker compose up -d --wait를 감싸고 있으므로, 상태 확인이 통과될 때까지 반환되지 않습니다. 모든 서비스는 running (healthy) 또는 running 상태를 보여야 합니다. Postgres가 다른 서비스가 연결되기 전에 초기화 스크립트를 실행하므로, 첫 부팅에는 2분에서 4분 정도 소요됩니다.
컨테이너가 계속 재시작된다면, 서비스 이름으로 로그를 확인하십시오:
docker compose logs db
docker compose logs auth이제 Studio는 8000 포트에서 실행되며, 설정한 대시보드 사용자 이름과 비밀번호를 요구합니다.
포트 8000을 공용 인터넷에 노출하지 마십시오
Kong은 8000 포트에서 일반 HTTP를 사용합니다. 모든 API 키와 사용자 비밀번호가 평문으로 네트워크를 통과하며, Studio 자격 증명은 암호화가 아닌 base64 인코딩 방식의 기본 인증(basic authentication)을 사용합니다.
앞단에 리버스 프록시를 배치하여 TLS(transport layer security)를 종료하고, Kong을 루프백 주소에 바인딩하여 외부에서 접근할 수 없도록 하십시오. docker-compose.yml에서 kong 포트 매핑은 127.0.0.1:8000:8000가 되며, 프록시가 해당 포트로 요청을 전달합니다. 여러 Compose 앱 앞단의 Traefik에서 인증서 관련 내용을 다룹니다.
또한 방화벽에서 나머지 포트도 닫으십시오. Docker는 자체적으로 iptables 규칙을 작성하여 포트를 게시하는데, 일반적인 ufw 설정으로는 이를 감지할 수 없기 때문입니다. 이 함정에 관한 설명은 Docker 컨테이너가 ufw 규칙을 무시하는 이유에서 확인할 수 있습니다.
디렉터리가 아닌 데이터베이스를 백업하십시오
Postgres 데이터는 ./volumes/db/data의 바인드 마운트에 저장됩니다. 컨테이너가 실행 중일 때 해당 디렉터리를 복사하면 데이터가 일관되지 않은 상태로 복사됩니다. Postgres는 쓰기 작업을 버퍼링하며 디스크의 파일은 체크포인트 시점에만 일관성을 유지하기 때문입니다. 이를 복원하면 대개는 성공하지만, 간혹 마지막 트랜잭션이 조용히 유실될 수 있습니다. 이는 백업에서 발생할 수 있는 최악의 장애 유형입니다.
대신 덤프를 사용하십시오. pg_dumpall은 컨테이너 내부에서 실행되어 일관된 스냅샷을 생성합니다.
docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sql백업 파일을 신뢰하기 전에 파일이 비어 있지 않은지 확인하십시오. 그 후 restic을 이용한 암호화된 외부 백업을 사용하여 정해진 일정에 따라 해당 덤프를 서버 외부로 전송하십시오. 동시에 .env도 백업해야 합니다. JWT_SECRET을 분실하면 발급된 모든 토큰이 무효화되며, 저장된 모든 암호화된 비밀 정보를 읽을 수 없게 됩니다.
업로드된 파일은 ./volumes/storage에 위치하며, 이는 일반적인 파일이므로 단순 복사로도 충분합니다.
데이터 손실 없는 업데이트
Supabase는 docker-compose.yml에서 이미지 버전을 고정하므로, 직접 변경하지 않는 한 아무것도 변경되지 않는다. 직접 구성하는 모든 스택에서도 버전 고정을 적용할 가치가 있다. 따라서 직접 호스팅하는 RustDesk 릴레이는 변경되는 태그를 따르지 않고 2개의 서버 이미지를 고정한다. 업그레이드는 시간을 낼 수 있는 날 아침에 직접 선택해서 수행해야 한다. 매번 먼저 덤프를 생성한다.
docker compose pull
sh run.sh recreaterecreate은 스택을 중지한 뒤 새로운 이미지로 다시 시작합니다. 데이터는 컨테이너 내부가 아닌 호스트의 바인드 마운트에 저장되므로 안전하게 유지됩니다. Postgres 메이저 버전 업그레이드는 자동으로 수행되지 않으며 덤프 및 복원 과정이 필요하므로, 메이저 버전 업데이트 전에는 저장소의 CHANGELOG.md를 반드시 읽어보십시오.
Compose 파일 자체의 변경 사항을 적용하려면 업스트림 저장소를 다시 클론한 뒤 docker 디렉터리를 프로젝트로 복사하십시오. 이때 .env를 덮어쓰지 않도록 주의해야 합니다.
데이터베이스를 포함한 모든 것을 삭제하는 전체 초기화는 별도의 스크립트로 제공되며, 실행 시 확인 절차를 거칩니다.
sh reset.shFAQ
API 호출 시 "Invalid authentication credentials" 오류가 발생하는 이유는 무엇입니까?
ANON_KEY 또는 SERVICE_ROLE_KEY이 .env에 현재 저장된 JWT_SECRET로 서명되지 않았기 때문입니다. 게이트웨이는 모든 요청의 서명을 검증하며, 일치하지 않으면 거부합니다. sh utils/generate-keys.sh --update-env를 사용하여 세 가지를 모두 새로 생성한 다음, sh run.sh recreate을 실행하여 서비스가 새로운 값을 읽도록 하십시오.
2 GB VPS에서 자체 호스팅 Supabase를 실행할 수 있습니까?
안정적으로 실행할 수 없습니다. 2026년 7월 기준으로 스택은 약 14개의 서비스를 실행하므로 유휴 상태에서도 3 GB에 가까운 메모리를 사용합니다. 따라서 2 GB 서버에서는 out of memory killer에 의해 컨테이너가 종료되며, docker compose ps에서 종료 코드 137을 확인하게 됩니다. 프로덕션 환경에는 8 GB를 사용하고, 개인 개발 환경이라도 최소 4 GB를 확보하십시오.
자체 호스팅 Supabase에 Edge Functions가 포함되어 있습니까?
네. Compose 파일에는 Deno 기반의 함수 런타임이 포함되어 있으며, ./volumes/functions 경로 아래에 배치한 모든 함수를 서비스합니다. 호스팅 플랫폼의 글로벌 배포 네트워크는 포함되어 있지 않으므로, 함수는 사용자의 서버 한 곳에서만 실행됩니다.
Postgres 데이터베이스에 직접 연결하려면 어떻게 해야 합니까?
서버 내부에서 대화형 셸을 사용하려면 docker exec -it supabase-db psql -U postgres을 사용하십시오. 외부 클라이언트의 경우, 포트 5432를 통해 Supavisor에 연결하고 사용자 postgres.<POOLER_TENANT_ID>와 POSTGRES_PASSWORD를 사용하십시오. 해당 포트를 인터넷에 직접 노출하지 마십시오. VPN이나 SSH 터널을 통해 접근해야 합니다.
인증 확인 이메일의 링크가 localhost로 연결되는 이유는 무엇입니까?
.env의 SITE_URL 및 API_EXTERNAL_URL 값이 기본값으로 설정되어 있기 때문입니다. 인증 서비스는 이 두 값을 기반으로 모든 확인 및 비밀번호 재설정 링크를 생성하므로, 설정된 주소로 이메일을 발송합니다. 두 값을 모두 실제 공개 URL로 설정한 뒤 스택을 다시 생성하십시오.