포스트

Supabase 셀프 호스팅 가이드

Supabase 는 백엔드를 위한 거의 모든 기능을 포함하고 있다. 클라우드에서 하나의 서비스를 사용할 수 있지만, 개발용으로 다루기 위해서는 셀프 호스팅이 편리하다. 요즘은 AI 애플리케이션을 개발하기 위해 인기가 더 높아졌다.

Supabase 셀프 호스팅 가이드

오랜만에 다뤄 보려고 하니깐 기억도 안나고 해서, 초심자의 마음으로 정리합니다.

1. Docker 기반 셀프 호스팅

따라하기 : 공식문서 - Self-Hosting with Docker

  1. 깃허브 다운로드 : 2026년 1월기준 최신 태그 v1.26.01
  2. 인스턴스용 디렉토리 생성
  3. 환경파일 복사
  4. 도커 이미지 다운로드
  5. 환경파일 키 생성 및 수정 (generate-keys.sh)
  6. 도커 컴포즈 시작

따라하기만 하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Get the code
git clone --depth 1 https://github.com/supabase/supabase

# Make your new supabase project directory
mkdir supabase-project

# Tree should look like this
# .
# ├── supabase
# └── supabase-project

# Copy the compose files over to your project
cp -rf supabase/docker/* supabase-project

# Copy the fake env vars
cp supabase/docker/.env.example supabase-project/.env

# Switch to your project directory
cd supabase-project

# Pull the latest images
docker compose pull

키를 자동으로 생성하고 적용하는 스크립트가 있어서 편하다.

1
2
3
4
5
6
7
8
# 키 생성 및 환경파일 수정 (자동)
sh ./utils/generate-keys.sh

# => JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY,
#   SECRET_KEY_BASE, VAULT_ENC_KEY, PG_META_CRYPTO_KEY,
#   LOGFLARE_PUBLIC_ACCESS_TOKEN, LOGFLARE_PRIVATE_ACCESS_TOKEN,
#   S3_PROTOCOL_ACCESS_KEY_ID, S3_PROTOCOL_ACCESS_KEY_SECRET,
#   POSTGRES_PASSWORD, DASHBOARD_PASSWORD

이 중에 DASHBOARD_PASSWORD 와 POSTGRES_PASSWORD 는 따로 작성하자. 그리고 psql 접속에 사용되는 POOLER_TENANT_ID 도 기억하기 쉽도록 수정한다.

1
2
3
4
5
vi .env

DASHBOARD_PASSWORD=...
POSTGRES_PASSWORD=...
POOLER_TENANT_ID=dev-server

준비는 다 되었고, supabase-project 디렉토리에서 명령어를 실행한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 시작
$ docker compose up -d
WARN[0000] No services to build
[+] up 14/14
 ✔ Network supabase_default                 Created
 ✔ Container supabase-imgproxy              Created
 ✔ Container supabase-vector                Healthy
 ✔ Container supabase-db                    Healthy
 ✔ Container supabase-analytics             Healthy
 ✔ Container supabase-pooler                Created
 ✔ Container supabase-kong                  Created
 ✔ Container supabase-auth                  Created
 ✔ Container supabase-meta                  Created
 ✔ Container realtime-dev.supabase-realtime Created
 ✔ Container supabase-rest                  Created
 ✔ Container supabase-studio                Created
 ✔ Container supabase-edge-functions        Created
 ✔ Container supabase-storage               Created

# 상태 확인
$ docker compose ps
$ docker compose logs analytics

# 종료
$ docker compose down

도커 스택이 모두 정상 작동하였으면 대시보드에 접속하자.

http://localhost:8000

2. 접속 테스트

Supabase 백엔드가 잘 작동하는지 테스트를 해보자.

edge function

rest query

GoTrue Auth API 확인하기

1
2
3
4
5
6
7
8
9
10
11
export SB_ANON_KEY="..."

curl -X GET 'http://localhost:8000/auth/v1/health' \
-H "apikey: $SB_ANON_KEY" \
-H "Authorization: Bearer $SB_ANON_KEY"

{
  "version":"v2.184.0",
  "name":"GoTrue",
  "description":"GoTrue is a user registration and authentication API"
}

todos 테이블 생성 후 쿼리하기

1
2
3
4
5
6
7
8
9
10
11
12
export SB_ANON_KEY="..."

curl 'http://localhost:8000/rest/v1/todos' \
-H "apikey: $SB_ANON_KEY" \
-H "Authorization: Bearer $SB_ANON_KEY"

[
  {"id":1,"task":"Create tables"},
  {"id":2,"task":"Enable security"},
  {"id":3,"task":"Add data"},
  {"id":4,"task":"Fetch data from the API"}
]

db connection

연결한 김에 postgres 의 타임존(TZ)을 변경하자.

1
2
3
4
5
6
7
# direct connect
psql 'postgres://postgres.{테넌트ID}:{패스워드}@localhost:5432/postgres'

# pooling connect
psql 'postgres://postgres.{테넌트ID}:{패스워드}@localhost:6543/postgres'

ALTER DATABASE postgres SET timezone TO 'Asia/Seoul';

3. 자바스크립트 API 사용

bun 으로 TS 프로젝트를 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
# 프로젝트 생성
bun init my-app
# > Blank 선택

cd my-app 

# supabase 클라이언트 패키지 설치
bun add @supabase/supabase-js

# 실행
bun index.ts
# 출력> Hello via Bun!

앞에서 생성했던 todos 테이블을 출력해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
import { createClient } from '@supabase/supabase-js'

console.log("Hello via Bun!");

let SB_URL = 'http://localhost:8000'
let SB_KEY = process.env.SB_ANON_KEY ?? '';

// Create a single supabase client for interacting with your database
const supabase = createClient(SB_URL, SB_KEY);

const { data } = await supabase.from('todos').select();
console.log(JSON.stringify(data, null, 2));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 실행
bun index.ts
# 출력>
Hello via Bun!
[
  {
    "id": 1,
    "task": "Create tables"
  },
  {
    "id": 2,
    "task": "Enable security"
  },
  {
    "id": 3,
    "task": "Add data"
  },
  {
    "id": 4,
    "task": "Fetch data from the API"
  }
]

plain Object 를 class Instance 로 변형하는 코드도 추가해 봤다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const isObjectArr = (value: unknown) => {
  return (
    typeof value === 'object' &&
    value !== null && 
    Array.isArray(data)
  );
}
// console.log(`=> ${isObjectArr(data) }`);

if (!isObjectArr(data)) {
  throw new TypeError('data is not an Array of Objects');
}

////////////////////////////////////////////

const formatNumber = (num: number, targetLength: number): string => {
  return String(num).padStart(targetLength, '0');
};

interface ITodo {
  id: number;
  task: string;
}

class Todo implements ITodo {
  id: number;
  task: string;  

  constructor(data: ITodo){
    // Object.assign(this, data);
    this.id = data.id;
    this.task = data.task;
  }

  getDisplayName(): string {
    let paddedId = formatNumber(this.id,2);
    let shortenTask = this.task.slice(0,10) + (this.task.length > 10 ? '..' : '');
    return `${paddedId}-${shortenTask}`;
  }  
}

let todos:Todo[] = data.map(item => new Todo(item));
for (const todo of todos){
  console.log(todo.getDisplayName());
}

/*
01-Create tab..
02-Enable sec..
03-Add data
04-Fetch data..
 */

원래는 typia 라이브러리를 써보려고 했는데, 무슨 이유인지 안된다.

1
2
3
4
5
6
7
8
9
$ bun add typia
$ bun typia setup --manager bun

# 소스 작성하고...

$ bun index.ts
error: Error on typia.is(): no transform has been configured.

Read and follow https://typia.io/docs/setup please.

Self Hosting 에서 Supabase CLI 사용하기

supabase gen types 명령을 사용하는데 project_id 가 필요하다고 해서 넣었지만 작동하지 않았다.

  • project_id 는 다운로드 된 supabase 깃허브 소스에 있다.
    • supabase/supabase/config.toml 파일
1
2
$ bunx supabase gen types typescript --project-id xguihxuzqibwxjnimxev
2026/01/25 13:58:57 Access token not provided. Supply an access token by running supabase login or setting the SUPABASE_ACCESS_TOKEN environment variable.

공식문서 설명에 따르면 db-url 을 대신 사용할 수 있다.

  • db-url 옵션은 supabase db 명령에도 사용된다.
1
2
3
4
$ bunx supabase gen types typescript --db-url 'postgres://postgres.{테넌트ID}:{패스워드}@localhost:5432/postgres'
Connecting to localhost 5432
v0.95.2: Pulling from supabase/postgres-meta
...

스키마 전체를 풀어내는 거라서 장황하지만 그 내용은 Database, Tables, QueryResult 등을 정의한다.

4. 참고문서

유튜브에 관련 영상들이 많다.

9. Reviews

  • 컴퓨터를 종료하기 전에 supabase 도커 스택도 종료해야 옳다.
    • 다시 켜면 자동으로 시작되는데 정상 시동되지 않는 경우가 있다.
  • 타입스크립트를 오랜만에 만져보니 타이핑이 번거롭다. 잔소리도 많고.
  • Object 를 클래스 Instance 로 변환하는데 class-transformer 를 사용한다는데, 부하가 크지 않을까 염려된다.
    • zod 보다는 심플해 보인다.

 
 

끝!   읽어주셔서 감사합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.