Synology DSM 설치
배포 토폴로지는 단일 앱 컨테이너 + postgres + redis 3개뿐입니다. web·media·알림 워커는 한 이미지(
app)의 세 프로세스로 돌고 포트 3000 하나만 노출합니다. 브라우저는PUBLIC_URL/media/*로 미디어에 접근하고, 컨테이너 내부에서 Next 가localhost:3001로 프록시합니다. 별도 리버스 프록시 컨테이너가 필요 없습니다.
요구 사항
- DSM 7.2+
- Container Manager 패키지
- 공유 폴더
docker(또는 원하는 이름) - amd64 시놀로지 (현재 이미지는
linux/amd64전용 — ARM 모델은arm64이미지 추가 전까지 미지원)
1. 폴더 준비
File Station 으로 /volume1/docker/bebe-moment 아래에 미리 만들어 두면 권한 꼬임이 적습니다.
/volume1/docker/bebe-moment/
data/ # 사진 원본 + 파생물
pg/ # Postgres 데이터
redis/ # 큐(임시)
backups/ # 백업 번들
2. 프로젝트 만들기
- Container Manager → 프로젝트 → 만들기
- 이름:
bebe-moment, 경로:/volume1/docker/bebe-moment - 소스:
docker-compose.yml 만들기→ 아래 내용 붙여넣기
services:
app:
image: ghcr.io/svrforum/bebe-moment/app:latest
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://bebe:${POSTGRES_PASSWORD}@postgres:5432/bebe
DATABASE_URL_WEB: postgres://bebe_web:${BEBE_WEB_DB_PASSWORD}@postgres:5432/bebe
DATABASE_URL_MEDIA: postgres://bebe_media:${BEBE_MEDIA_DB_PASSWORD}@postgres:5432/bebe
REDIS_URL: redis://redis:6379
SECRET_KEY: ${SECRET_KEY}
PUBLIC_URL: ${PUBLIC_URL}
# 알림 방해금지·다이제스트·추억 푸시는 컨테이너 로컬 시각으로 판단 → 가족 시간대로.
TZ: ${TZ:-Asia/Seoul}
# web → 같은 컨테이너의 media(런타임) + Next /media rewrite(빌드). 단일 컨테이너라 localhost.
MEDIA_INTERNAL_URL: http://localhost:3001
# 보통 미설정으로 두면 PUBLIC_URL 로 폴백된다(동일 오리진 /media/*). :3001 등을 넣지 말 것.
NEXT_PUBLIC_MEDIA_BASE_URL: ${PUBLIC_URL}
MEDIA_PUBLIC_BASE_URL: ${PUBLIC_URL}
MEDIA_SERVICE_TOKEN: ${MEDIA_SERVICE_TOKEN}
MEDIA_JWT_SECRET: ${MEDIA_JWT_SECRET}
STORAGE_MODE: local
STORAGE_PATH: /data
BACKUP_DIR: /backups
# DSM admin: uid 1026 / gid users(100). 다른 사용자면 SSH 후 `id <user>` 로 확인.
PUID: ${PUID:-1026}
PGID: ${PGID:-100}
ADMIN_USER_EMAIL: ${ADMIN_USER_EMAIL:-}
LOG_LEVEL: ${LOG_LEVEL:-info}
BEBE_WEB_DB_PASSWORD: ${BEBE_WEB_DB_PASSWORD}
BEBE_MEDIA_DB_PASSWORD: ${BEBE_MEDIA_DB_PASSWORD}
# 얼굴 인식(옵트인) ML 사이드카. ml 컨테이너를 안 띄우면 호출도 없다.
FACE_ML_URL: ${FACE_ML_URL:-http://ml:8000}
volumes:
- /volume1/docker/bebe-moment/data:/data
- /volume1/docker/bebe-moment/backups:/backups
depends_on:
postgres: { condition: service_healthy }
redis: { condition: service_healthy }
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s
postgres:
# pgvector 포함(얼굴 임베딩 벡터검색).
image: pgvector/pgvector:pg17
environment:
POSTGRES_DB: bebe
POSTGRES_USER: bebe
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- /volume1/docker/bebe-moment/pg:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bebe"]
interval: 10s
timeout: 5s
retries: 5
redis:
# Valkey (Redis BSD 포크). 서비스명은 redis 유지.
image: valkey/valkey:9-alpine
volumes:
- /volume1/docker/bebe-moment/redis:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
표준 compose 와 동일한 구조에 시놀로지 절대경로·PUID/PGID 만 맞춘 것입니다. 리소스 한도·얼굴인식
ml서비스는 표준 compose 를 참고해 그대로 옮길 수 있습니다(아래 "얼굴 인식" 참고).
3. 환경변수 (env 탭)
프로젝트 생성 화면의 환경 변수(.env) 탭에 입력합니다. 시크릿은 반드시 여기(런타임 env)에 넣습니다 — 이미지에는 들어가지 않습니다.
SECRET_KEY=<openssl rand -hex 32>
POSTGRES_PASSWORD=<복잡한 문자열>
PUBLIC_URL=https://bebe.mydomain.synology.me
ADMIN_USER_EMAIL=<관리자 이메일(선택)>
TZ=Asia/Seoul
# 서비스 간 인증/서명 (각 32바이트+) — 생성: openssl rand -hex 32
MEDIA_SERVICE_TOKEN=<랜덤 hex>
MEDIA_JWT_SECRET=<랜덤 hex>
# DB role 분리 (web=public 스키마, media=media 스키마). entrypoint 가 이 비번으로 역할 생성.
BEBE_WEB_DB_PASSWORD=<복잡한 문자열>
BEBE_MEDIA_DB_PASSWORD=<복잡한 문자열>
⚠️
MEDIA_PUBLIC_BASE_URL/NEXT_PUBLIC_MEDIA_BASE_URL에:3001같은 값을 넣지 마세요. 노출되지 않은 포트를 가리켜 사진이 안 뜹니다. 미설정(또는PUBLIC_URL과 동일)이 정답입니다.
4. 시작 & 첫 로그인
빌드/시작 후:
PUBLIC_URL(또는 NAShttp://<NAS-IP>:3000) 접속 → 첫 사용자 가입 → 온보딩(가족·아기 설정)- 첫 가입자가 곧 관리자(owner) 입니다. 이후 구성원은 초대 링크로만 합류합니다.
- 관리 화면은
/admin, 또는 설정 → 관리자.
5. TLS / 외부 공개 — DSM 리버스 프록시
내부에 별도 프록시가 없으므로 DSM 리버스 프록시로 TLS 종단만 하면 됩니다.
- DSM → 제어판 → 로그인 포털 → 고급 → 리버스 프록시 → 만들기
- Source:
bebe.mydomain.synology.me, 443, HTTPS - Destination:
localhost, 3000, HTTP
- Source:
- 사용자 정의 헤더에서 WebSocket 켜기(
Create > WebSocket), 타임아웃 ≥ 600초(큰 tus 업로드·SSE 가 끊기지 않도록). PUBLIC_URL은 이 공개 도메인(https://…)으로 맞춥니다. https 로 바뀌면 세션 쿠키가__Secure-접두사로 전환되어 한 번 재로그인이 필요할 수 있습니다.- 인증서는 DSM → 보안 → 인증서에서 Let's Encrypt 로 발급해 이 리버스 프록시에 적용합니다.
6. 얼굴 인식 (옵트인)
기본은 꺼져 있습니다. 켜려면 표준 compose 의 ml 서비스 블록(profile faces, 이미지 ghcr.io/svrforum/bebe-moment/ml)을 위 compose 에 추가하고, FACE_ML_URL=http://ml:8000 으로 둔 뒤 관리자 → 기능에서 얼굴 인식을 켜면 됩니다. 모델팩은 첫 기동 때 받아 볼륨에 캐시됩니다(amd64 CPU 추론, 메모리 ~3GB 권장).
7. 백업 — Hyper Backup
앱 자체 백업(설정 → 관리자 → 백업, 전체·증분·원격 S3)을 권장하되, NAS 레벨로도 다음을 백업할 수 있습니다.
/volume1/docker/bebe-moment/data— 사진 원본 + 파생물/volume1/docker/bebe-moment/pg— 메타데이터 DB/volume1/docker/bebe-moment/backups— 앱 백업 번들
redis 는 큐 임시 상태라 백업이 불필요합니다.
8. 업데이트
Container Manager → 프로젝트 bebe-moment → 이미지 → 내려받기(pull) → 다시 빌드. 기동 시 prisma migrate deploy 가 자동 실행되어 스키마가 최신으로 맞춰집니다.
트러블슈팅
- 사진 페이지가 500 / 로그에
MEDIA_SERVICE_TOKEN env required— env 탭에MEDIA_SERVICE_TOKEN·MEDIA_JWT_SECRET가 빠졌습니다./api/health는 미디어를 안 거쳐 200 으로 남으니 실제 사진 페이지로 확인하세요. - 페이지는 뜨는데 썸네일이 깨짐 —
MEDIA_PUBLIC_BASE_URL/NEXT_PUBLIC_MEDIA_BASE_URL에:3001등 노출 안 된 포트를 넣은 경우입니다. 미설정(PUBLIC_URL 폴백)으로 두세요. - pg16 → pg17 업그레이드 — 데이터 디렉터리 비호환입니다.
pg_dump(구버전)→restore(pg17) 후pg/를 비우고 시작하거나, 앱 백업/복구를 사용하세요.