포스트

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"
  }
]

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

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
const isObjectArr = (value) => {
  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);
  }

  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.

9. Reviews

  • 컴퓨터를 종료하기 전에 supabase 도커 스택도 종료해야 옳다.
    • 다시 켜면 자동으로 시작되는데 정상 시동되지 않는 경우가 있다.
  • 타입스크립트 오랜만에 만져보니 타이핑이 번거롭다.
    • class-transformer 는 뭐고 typia 는 또 뭔가?

 
 

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

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