TELEPASI

검색하기 전에 통하다

Self-hosted Supabase PostgreSQL 데이터베이스의 일일 자동 백업 가이드

강병우
2026.07.17 👁️ 80

Supabase DB Daily Backup

Self-hosted Supabase PostgreSQL 데이터베이스의 일일 자동 백업·복원 가이드입니다.


개요

항목 내용
스크립트 /usr/local/bin/supabase-daily-backup.sh
스케줄 Cron 0 0 * * * (매일 00:00)
백업 저장소 /var/www/example.com/dbBackup/
로그 /var/www/example.com/dbBackup/backup.log
보관 기간 최근 3일 (RETENTION_DAYS=3)
파일 형식 example-backup-YYYYMMDD-HHMMSS.sql.gz
백업 방식 docker exec supabase-db pg_dump → gzip 압축

백업 프로세스

  1. 백업 디렉토리 존재 여부 확인 (없으면 생성)
  2. supabase-db 컨테이너에서 pg_dump로 스키마+데이터 덤프
  3. .sql 파일을 gzip으로 압축
  4. 3일 이상 된 example-backup-*.sql(.gz) 삭제
  5. 결과·파일 목록·디스크 사용량을 로그에 기록

pg_dump 옵션

-U postgres
-d postgres
--no-owner
--no-acl
--clean
--if-exists
  • --clean --if-exists: 복원 시 기존 객체를 먼저 삭제할 수 있는 덤프 생성
  • --no-owner --no-acl: 소유자/권한 의존을 줄여 복원 호환성 향상

파일 구조

/usr/local/bin/
└── supabase-daily-backup.sh

/var/www/example.com/dbBackup/
├── example-backup-YYYYMMDD-HHMMSS.sql.gz
├── ...
└── backup.log

현재 백업 예시 (2026-07-16 기준)

example-backup-20260713-011055.sql.gz
example-backup-20260713-090001.sql.gz
example-backup-20260714-090001.sql.gz
example-backup-20260715-090001.sql.gz
example-backup-20260716-090001.sql.gz
backup.log

스크립트 전문

경로: /usr/local/bin/supabase-daily-backup.sh

#!/bin/bash

################################################################################
# Supabase 일일 자동 백업 스크립트
# 매일 자정에 실행되며, 최근 3일간의 백업만 유지합니다.
################################################################################

# 설정
BACKUP_DIR="/var/www/example.com/dbBackup"
LOG_FILE="$BACKUP_DIR/backup.log"
RETENTION_DAYS=3

# 로그 함수
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "=========================================="
log "Supabase 백업 시작"
log "=========================================="

# 백업 디렉토리 확인
if [ ! -d "$BACKUP_DIR" ]; then
    mkdir -p "$BACKUP_DIR"
    chmod 755 "$BACKUP_DIR"
    log "백업 디렉토리 생성: $BACKUP_DIR"
fi

# 백업 파일명 (날짜 포함)
BACKUP_FILE="example-backup-$(date +%Y%m%d-%H%M%S).sql"
BACKUP_PATH="$BACKUP_DIR/$BACKUP_FILE"

log "백업 파일: $BACKUP_FILE"

# Docker를 통한 데이터베이스 백업
log "데이터베이스 백업 중..."

docker exec supabase-db pg_dump \
    -U postgres \
    -d postgres \
    --no-owner \
    --no-acl \
    --clean \
    --if-exists > "$BACKUP_PATH" 2>> "$LOG_FILE"

# 백업 성공 확인
if [ $? -eq 0 ] && [ -f "$BACKUP_PATH" ]; then
    FILE_SIZE=$(du -h "$BACKUP_PATH" | cut -f1)
    log "✅ 백업 완료: $BACKUP_FILE (크기: $FILE_SIZE)"
    
    # 압축 (선택사항 - 용량 절약)
    log "백업 파일 압축 중..."
    gzip "$BACKUP_PATH"
    if [ $? -eq 0 ]; then
        COMPRESSED_SIZE=$(du -h "$BACKUP_PATH.gz" | cut -f1)
        log "✅ 압축 완료: $BACKUP_FILE.gz (압축 크기: $COMPRESSED_SIZE)"
    else
        log "⚠️  압축 실패"
    fi
else
    log "❌ 백업 실패"
    exit 1
fi

# 오래된 백업 삭제 (최근 3일만 유지)
log "오래된 백업 파일 정리 중... (${RETENTION_DAYS}일 이상)"

# .sql.gz 파일 삭제
DELETED_COUNT=0
while IFS= read -r old_file; do
    if [ -n "$old_file" ]; then
        log "삭제: $(basename "$old_file")"
        rm -f "$old_file"
        ((DELETED_COUNT++))
    fi
done < <(find "$BACKUP_DIR" -name "example-backup-*.sql.gz" -type f -mtime +$RETENTION_DAYS)

# .sql 파일 삭제 (압축 안된 파일)
while IFS= read -r old_file; do
    if [ -n "$old_file" ]; then
        log "삭제: $(basename "$old_file")"
        rm -f "$old_file"
        ((DELETED_COUNT++))
    fi
done < <(find "$BACKUP_DIR" -name "example-backup-*.sql" -type f -mtime +$RETENTION_DAYS)

if [ $DELETED_COUNT -gt 0 ]; then
    log "✅ $DELETED_COUNT 개의 오래된 백업 삭제 완료"
else
    log "✅ 삭제할 오래된 백업 없음"
fi

# 현재 백업 목록
log "=========================================="
log "현재 백업 파일 목록:"
log "=========================================="
ls -lh "$BACKUP_DIR"/example-backup-*.sql.gz 2>/dev/null | awk '{print $9, $5}' | while read line; do
    log "  $line"
done

# 디스크 사용량
DISK_USAGE=$(du -sh "$BACKUP_DIR" | cut -f1)
log "=========================================="
log "백업 디렉토리 총 사용량: $DISK_USAGE"
log "백업 완료"
log "=========================================="

exit 0

스크립트 복사해서 사용하기

아래 스크립트 전문을 그대로 복사한 뒤, 본인 서버 환경에 맞게 값만 바꿔 사용하면 됩니다.

1. 사전 확인

Self-hosted Supabase가 Docker로 실행 중이어야 합니다.

# DB 컨테이너 이름 확인 (기본값: supabase-db)
docker ps --format "table {{.Names}}\t{{.Status}}" | grep -i db

# Docker 명령 실행 권한 확인
docker exec supabase-db pg_dump --version

컨테이너 이름이 supabase-db가 아니면 스크립트의 docker exec 부분을 본인 환경에 맞게 수정하세요.

2. 설치 순서

# 1) 스크립트 파일 생성
sudo nano /usr/local/bin/supabase-daily-backup.sh
# → 위 "스크립트 전문" 전체를 붙여넣기

# 2) 실행 권한 부여
sudo chmod +x /usr/local/bin/supabase-daily-backup.sh

# 3) 백업 디렉토리 미리 만들기 (선택 — 스크립트가 자동 생성함)
sudo mkdir -p /var/www/example.com/dbBackup
sudo chmod 755 /var/www/example.com/dbBackup

# 4) 수동 실행으로 테스트
sudo /usr/local/bin/supabase-daily-backup.sh

# 5) 백업 파일 생성 확인
ls -lh /var/www/example.com/dbBackup/

# 6) Cron 등록 (아래 "Cron 설정" 참고)
crontab -e

Cron 등록 시에도 BACKUP_DIR 경로를 본인 환경에 맞게 바꿔 주세요.

3. 반드시 바꿔야 할 항목

# 스크립트 위치 예시 값 본인 환경
1 BACKUP_DIR /var/www/example.com/dbBackup 백업을 저장할 디렉토리
2 RETENTION_DAYS 3 백업 보관 일수 (원하면 7, 14 등)
3 BACKUP_FILE 접두사 example-backup- mysite-backup- 등 구분하기 쉬운 이름
4 docker exec 컨테이너명 supabase-db docker ps로 확인한 DB 컨테이너 이름
5 find / ls 파일 패턴 example-backup-* 3번 접두사와 동일하게
6 Cron 로그 경로 /var/www/example.com/dbBackup/backup.log 1번 BACKUP_DIR과 일치

LOG_FILE$BACKUP_DIR/backup.log로 되어 있으므로 BACKUP_DIR만 바꾸면 로그 경로도 함께 맞춰집니다.

4. 커스터마이징 예시

도메인이 myservice.com이고, 백업을 7일간 보관하려면 스크립트 상단을 이렇게 바꿉니다.

# 설정 — ↓ 이 3줄(및 접두사)을 본인 환경에 맞게 수정
BACKUP_DIR="/var/www/myservice.com/dbBackup"
LOG_FILE="$BACKUP_DIR/backup.log"
RETENTION_DAYS=7

# ...

BACKUP_FILE="myservice-backup-$(date +%Y%m%d-%H%M%S).sql"

접두사를 myservice-backup-으로 바꿨다면, 아래 4곳의 파일 패턴도 같이 수정해야 합니다.

# find (삭제 대상 .sql.gz)
-name "myservice-backup-*.sql.gz"

# find (삭제 대상 .sql)
-name "myservice-backup-*.sql"

# ls (백업 목록 출력)
ls -lh "$BACKUP_DIR"/myservice-backup-*.sql.gz

5. 보통 바꾸지 않아도 되는 항목

항목 기본값 설명
pg_dump -U postgres Self-hosted Supabase 기본 DB 사용자
pg_dump -d postgres Self-hosted Supabase 기본 DB 이름
pg_dump 옵션 --no-owner --no-acl --clean --if-exists 복원 호환성을 위한 권장 옵션
스크립트 설치 경로 /usr/local/bin/supabase-daily-backup.sh 다른 경로를 쓰면 Cron에도 동일하게 반영

DB 사용자·DB 이름을 커스텀했다면 pg_dump -U, -d 값도 함께 맞춰 주세요.

6. Cron 등록 시 함께 바꿀 것

스크립트만 수정하고 Cron 경로를 그대로 두면 로그가 엉뚱한 곳에 쌓일 수 있습니다.

# BACKUP_DIR을 /var/www/myservice.com/dbBackup 으로 바꿨다면:
0 0 * * * /usr/local/bin/supabase-daily-backup.sh >> /var/www/myservice.com/dbBackup/backup.log 2>&1

7. 설치 후 확인 체크리스트

  • docker ps에서 DB 컨테이너가 실행 중인가
  • 수동 실행 후 ✅ 백업 완료 로그가 남는가
  • BACKUP_DIR.sql.gz 파일이 생성되는가
  • find / ls의 파일 접두사가 BACKUP_FILE과 일치하는가
  • Cron에 등록했고, crontab -l로 확인했는가
  • Cron 로그 경로가 BACKUP_DIR/backup.log와 같은가

Cron 설정

등록된 작업

0 0 * * * /usr/local/bin/supabase-daily-backup.sh >> /var/www/example.com/dbBackup/backup.log 2>&1

확인 / 편집

# 등록 확인
crontab -l

# 편집
crontab -e

스케줄 변경 예시

# 매일 새벽 3시
0 3 * * * /usr/local/bin/supabase-daily-backup.sh >> /var/www/example.com/dbBackup/backup.log 2>&1

# 매일 오전 9시
0 9 * * * /usr/local/bin/supabase-daily-backup.sh >> /var/www/example.com/dbBackup/backup.log 2>&1

수동 실행

/usr/local/bin/supabase-daily-backup.sh

성공 시 로그에 다음과 같은 메시지가 기록됩니다.

✅ 백업 완료: example-backup-YYYYMMDD-HHMMSS.sql
✅ 압축 완료: example-backup-YYYYMMDD-HHMMSS.sql.gz
백업 완료

모니터링

# 최근 로그
tail -50 /var/www/example.com/dbBackup/backup.log

# 실시간 로그
tail -f /var/www/example.com/dbBackup/backup.log

# 백업 파일 목록
ls -lh /var/www/example.com/dbBackup/

# 최신 백업 1개
ls -t /var/www/example.com/dbBackup/example-backup-*.sql.gz | head -1

# 디렉토리 사용량
du -sh /var/www/example.com/dbBackup/

정상 여부 체크리스트

  • 오늘(또는 전날) 날짜의 .sql.gz 파일이 있는가
  • backup.log❌ 백업 실패가 없는가
  • supabase-db 컨테이너가 실행 중인가 (docker ps | grep supabase-db)

복원 (Restore)

일일 백업(example-backup-*.sql.gz)을 Self-hosted Supabase DB(supabase-db)에 복원하는 방법입니다.

주의

  • 복원은 현재 DB의 스키마/데이터를 덮어씁니다 (--clean --if-exists 덤프).
  • 운영 중이면 앱/API 트래픽을 잠시 중단한 뒤 진행하세요.
  • 복원 직전에 현재 상태 안전 백업을 먼저 남기는 것을 권장합니다.

복원 개요

항목 내용
대상 컨테이너 supabase-db
대상 DB postgres / 사용자 postgres
입력 파일 /var/www/example.com/dbBackup/example-backup-*.sql.gz
복원 명령 gunzip -c ... | docker exec -i supabase-db psql ...
예상 소요 DB 크기에 따라 수 초 ~ 수 분

사전 준비

# 1) DB 컨테이너 상태 확인
docker ps --filter name=supabase-db

# 2) 사용 가능한 백업 목록 확인
ls -lht /var/www/example.com/dbBackup/example-backup-*.sql.gz

# 3) (권장) 복원 전 현재 DB 안전 백업
/usr/local/bin/supabase-daily-backup.sh

1) 최신 백업으로 복원

LATEST=$(ls -t /var/www/example.com/dbBackup/example-backup-*.sql.gz | head -1)
echo "복원 대상: $LATEST"

gunzip -c "$LATEST" | docker exec -i supabase-db psql -U postgres -d postgres

성공 시 DROP / CREATE / COPY / ALTER 등이 연속 출력됩니다.
끝부분에서 major error 없이 프롬프트가 돌아오면 기본 복원은 완료된 것입니다.

2) 특정 시점 백업으로 복원

# 예: 2026-07-16 09:00 백업
BACKUP_FILE="/var/www/example.com/dbBackup/example-backup-20260716-090001.sql.gz"

gunzip -c "$BACKUP_FILE" | docker exec -i supabase-db psql -U postgres -d postgres

3) 압축 해제 후 복원 (디버깅용)

로그를 파일로 남기거나 오류를 자세히 볼 때 사용합니다.

BACKUP_GZ="/var/www/example.com/dbBackup/example-backup-20260716-090001.sql.gz"
BACKUP_SQL="/tmp/example-restore-$(date +%Y%m%d-%H%M%S).sql"
RESTORE_LOG="/tmp/example-restore-$(date +%Y%m%d-%H%M%S).log"

gunzip -c "$BACKUP_GZ" > "$BACKUP_SQL"
docker exec -i supabase-db psql -U postgres -d postgres < "$BACKUP_SQL" \
  > "$RESTORE_LOG" 2>&1

echo "복원 로그: $RESTORE_LOG"
tail -50 "$RESTORE_LOG"

# 임시 SQL 삭제 (선택)
rm -f "$BACKUP_SQL"

4) 로컬 PC에서 받은 백업으로 복원

서버에 파일을 올린 뒤 동일하게 복원합니다.

# 로컬 PC → 서버 업로드
scp -P 2222 ./example-backup-YYYYMMDD-HHMMSS.sql.gz \
  deploy@example.com:/tmp/

# 서버에서 복원
gunzip -c /tmp/example-backup-YYYYMMDD-HHMMSS.sql.gz | \
  docker exec -i supabase-db psql -U postgres -d postgres

복원 후 확인

# 테이블 목록
docker exec supabase-db psql -U postgres -d postgres -c "\dt public.*"

# 주요 테이블 건수 예시
docker exec supabase-db psql -U postgres -d postgres -c "
SELECT
  (SELECT COUNT(*) FROM profiles) AS profiles,
  (SELECT COUNT(*) FROM posts) AS posts,
  (SELECT COUNT(*) FROM comments) AS comments;
"

# DB 접속 후 수동 확인
docker exec -it supabase-db psql -U postgres -d postgres
# 예: \dt
# 예: SELECT COUNT(*) FROM profiles;
# 예: \q

복원 체크리스트

  • 복원 전 안전 백업을 생성했는가
  • 선택한 .sql.gz 파일명(시점)이 맞는가
  • supabase-db가 실행 중인가
  • 복원 후 \dt로 테이블이 보이는가
  • 주요 테이블 row 수가 기대한 수준인가
  • Studio/앱에서 로그인·조회가 정상인가

복원 시 자주 보는 메시지

메시지 의미 조치
ERROR: ... does not exist (DROP 중) --if-exists로 대부분 무시 가능 보통 무시
ERROR: role "..." does not exist 덤프의 역할이 로컬에 없음 --no-owner 덤프라 보통 영향 적음. 기능 이상 시 역할 생성 검토
FATAL: database "postgres" does not exist 컨테이너/DB 이름 문제 docker ps, compose 상태 확인
Cannot connect to the Docker daemon Docker 미기동 systemctl status docker
복원 중 연결 끊김 타임아웃/리소스 부족 압축 해제 후 파일 복원 방식 사용, 로그 확인

복원 실패 시 롤백

복원 직전에 만든 안전 백업으로 다시 되돌립니다.

# 방금 만든 최신 안전 백업 확인
ls -lht /var/www/example.com/dbBackup/example-backup-*.sql.gz | head -5

# 해당 파일로 재복원
gunzip -c /var/www/example.com/dbBackup/example-backup-YYYYMMDD-HHMMSS.sql.gz | \
  docker exec -i supabase-db psql -U postgres -d postgres

백업 파일 다운로드 (로컬 PC)

SSH 포트: 2222 (환경에 맞게 변경)

# 전체 백업 디렉토리
scp -P 2222 -r deploy@example.com:/var/www/example.com/dbBackup ./example-dbBackup

# 최신 DB 백업만
ssh -p 2222 deploy@example.com \
  "ls -t /var/www/example.com/dbBackup/example-backup-*.sql.gz | head -1" \
  | xargs -I {} scp -P 2222 deploy@example.com:{} .

스크립트 설정 요약

「스크립트 복사해서 사용하기」 절차를 따랐다면, 이후 변경은 상단 변수만 수정하면 됩니다.

변수 기본값 설명
BACKUP_DIR /var/www/example.com/dbBackup 백업 저장 경로
LOG_FILE $BACKUP_DIR/backup.log 로그 파일
RETENTION_DAYS 3 보관 일수
파일명 prefix example-backup- 생성 파일 이름 접두사 (find/ls 패턴과 동일해야 함)

보관 기간을 7일로 바꾸는 예:

RETENTION_DAYS=7

장애 대응

1. supabase-db 미실행

cd /opt/supabase/docker
docker compose ps
docker compose up -d db

2. 백업 디렉토리 권한/경로 문제

mkdir -p /var/www/example.com/dbBackup
chmod 755 /var/www/example.com/dbBackup

3. 디스크 부족

df -h /var/www/example.com
du -sh /var/www/example.com/dbBackup/*
# 필요 시 RETENTION_DAYS 축소 또는 오래된 파일 수동 삭제

4. Cron이 실행되지 않음

systemctl status cron
crontab -l
grep CRON /var/log/syslog | tail -20

보안 유의사항

  • 백업에는 DB 스키마·데이터가 포함되므로 접근 권한을 제한하세요.
  • 비밀번호·API 키를 스크립트나 문서에 평문으로 추가하지 마세요.
  • 로컬로 다운로드한 백업도 안전하게 보관·폐기하세요.

주파수 소통방 (0)

로딩 중...