chore: simplify NAS to production-only deployment

This commit is contained in:
2026-06-19 11:45:40 +09:00
parent 0c74f994fd
commit bb5f663e48
10 changed files with 278 additions and 321 deletions

View File

@@ -1,4 +1,3 @@
PLAYUNIARE_TEST_HTTP_PORT=41808
PLAYUNIARE_PRODUCTION_HTTP_PORT=41809
VITE_CARD_ASSET_BASE_URL=/card-assets
VITE_SOCKET_SERVER_URL=http://localhost:31873
VITE_SOCKET_SERVER_URL=http://playuniare.sori.studio

View File

@@ -157,9 +157,13 @@ function createGameCardInstances(importedCards) {
- 운영 환경에서는 Socket.IO 포트를 브라우저에 직접 노출하지 않고 같은
도메인의 `/socket.io` 경로로 리버스 프록시한다.
- 컨테이너 내부 포트 중복은 허용되지만 NAS 호스트 공개 포트 중복은 금지한다.
- 테스트·운영 HTTP 호스트 포트는 Compose에 숫자를 직접 추가하지 않고
`PLAYUNIARE_TEST_HTTP_PORT`, `PLAYUNIARE_PRODUCTION_HTTP_PORT` 환경변수로
관리한다.
- 운영 HTTP 호스트 포트는 Compose에 숫자를 직접 추가하지 않고
`PLAYUNIARE_PRODUCTION_HTTP_PORT` 환경변수로 관리한다.
- 기능 검증은 로컬에서 수행하고 NAS에는 운영 컨테이너만 배포한다.
- 운영 데이터가 생기면 마이그레이션 코드는 Git으로 관리하고, 백업과 재실행
안전성을 확인한 뒤 실행한다.
- NAS에서 즉석 데이터 변경 명령을 사용한 경우 같은 변경을 정식
마이그레이션 코드와 문서로 남긴다.
- URL 길이 문제를 고려해 장기적으로 덱 코드 또는 `deckId` 방식을 도입한다.
- URL, 로그, Git에 사용자 토큰이나 비밀값을 남기지 않는다.
- 오류가 있는 카드 한 장 때문에 전체 덱 불러오기가 중단되지 않도록 오류
@@ -174,6 +178,7 @@ function createGameCardInstances(importedCards) {
- 화면 변경 → `docs/map.md`
- 배포 변경 → `docs/deploy.md`
- 개발 명령 변경 → `docs/development.md`
- 데이터 구조·마이그레이션 변경 → `docs/migrations.md`
문서 업데이트 누락을 금지한다.

View File

@@ -23,5 +23,6 @@ npm run dev
- 개발: `docs/development.md`
- NAS 배포: `docs/deploy.md`
- 데이터 마이그레이션: `docs/migrations.md`
- 프로젝트 명세: `docs/spec.md`
- AI 작업 규칙: `AGENTS.md`

View File

@@ -1,7 +0,0 @@
services:
playuniare-test-web:
volumes:
- type: bind
source: /volume1/docker/projects/apps/uniare/config/www/images
target: /srv/uniare-card-images
read_only: true

View File

@@ -1,11 +0,0 @@
name: playuniare-test
services:
playuniare-test-web:
build:
context: .
dockerfile: Dockerfile
container_name: playuniare-test-web
restart: unless-stopped
ports:
- '${PLAYUNIARE_TEST_HTTP_PORT:-41808}:80'

View File

@@ -1,325 +1,256 @@
# UGREEN NAS Docker 배포 안내
# UGREEN NAS 운영 배포 안내
## 문서 목적
## 운영 원칙
이 문서는 터미널과 Docker에 익숙하지 않은 사용자가 Play Uniare의 테스트
버전과 운영 버전을 UGREEN NAS에서 실행하고 업데이트하는 절차를 설명한다.
- 기능 테스트는 개발 Mac에서 수행한다.
- NAS에는 운영 컨테이너만 실행한다.
- 로컬 빌드와 Docker 검증을 통과한 커밋만 NAS에 배포한다.
- NAS에서 소스 코드를 직접 수정하지 않는다.
- 운영 주소는 현재 `http://playuniare.sori.studio`다.
- HTTPS는 인증서와 리버스 프록시 구성이 완료된 뒤 전환한다.
현재 프로젝트는 Vue 정적 프런트엔드만 포함한다. 실시간 서버가 추가되면
`compose.yaml`에 백엔드 서비스와 상태 저장소를 함께 추가한다.
현재 프로젝트는 Vue 정적 프런트엔드만 포함한다. 실시간 서버가 추가되면 같은
Compose 프로젝트 안에 Node.js 서비스를 추가한다.
## 배포 방식
## 배포 구조
```text
사용자 브라우저
↓
NAS 포트 또는 리버스 프록시
http://playuniare.sori.studio
↓
Play Uniare 컨테이너의 Nginx
NAS 리버스 프록시
↓
127.0.0.1:41809
↓
Play Uniare Nginx 컨테이너
├── / → Vue 빌드 결과
└── /card-assets/ → 기존 Uniare 카드 이미지
```
Docker 이미지를 만들 때 Node.js로 Vue를 빌드하고, 실행할 때는 가벼운
Nginx가 빌드 결과를 제공한다.
카드 이미지는 다음 기존 폴더를 복사하지 않고 읽기 전용으로 연결한다.
카드 이미지는 복사하지 않고 다음 폴더를 읽기 전용으로 연결한다.
```text
/volume1/docker/projects/apps/uniare/config/www/images
```
## NAS에서 처음 한 번 준비하기
## 로컬 배포 전 검증
### 1. Docker 앱 설치
개발 Mac의 프로젝트 폴더에서 실행한다.
UGREEN NAS의 앱 센터에서 Docker 또는 Container Manager에 해당하는 앱을
설치한다. 모델과 UGOS 버전에 따라 앱 이름이 다를 수 있다.
### 2. 프로젝트 저장 폴더 준비
NAS 공유 폴더에 다음과 같은 폴더를 만든다.
```text
/volume1/docker/projects/apps/
├── uniare/
│ └── config/
│ └── www/
│ ├── index.html
│ └── images/
└── playuniare/
```bash
npm install
npm run build
docker compose build
```
이 프로젝트의 NAS 경로는 다음으로 고정한다.
필요하면 로컬 Docker 컨테이너도 실행한다.
```text
/volume1/docker/projects/apps/playuniare
```bash
docker compose up -d
```
### 3. SSH 사용 여부 결정
브라우저에서 다음 주소를 확인한다.
UGREEN 관리 화면에서 Docker Compose 프로젝트를 직접 등록할 수 있다면 GUI를
우선 사용한다. GUI에서 Git 저장소를 직접 받을 수 없거나 업데이트가 어려운
경우에만 SSH 터미널을 사용한다.
```text
http://localhost:41809
```
SSH를 활성화할 때는 관리자 화면에서 포트와 계정을 확인한다. NAS 비밀번호를
문서, `.env`, Git 저장소에 기록하지 않는다.
로컬 Mac에는 NAS 카드 폴더가 없으므로 `/card-assets` 확인은 NAS 배포 후
수행한다.
## 테스트 버전 실행
검증이 끝나면 로컬 컨테이너를 종료한다.
테스트 버전은 외부 도메인에 연결하기 전에 NAS 내부에서 확인하는 용도다.
```bash
docker compose down
```
### 1. NAS에 프로젝트 받기
## NAS 최초 설치
NAS 터미널에서 프로젝트 저장 폴더로 이동한다.
### 1. SSH로 NAS 접속
개발 Mac 터미널에서 실행한다.
```bash
ssh NAS사용자명@192.168.50.146
```
실제 SSH 사용자명은 NAS 설정에 맞게 사용한다.
### 2. 저장소 복제
NAS 터미널에서 실행한다.
```bash
cd /volume1/docker/projects/apps
```
처음 한 번만 저장소를 복제한다.
```bash
git clone https://git.sori.studio/zenn/playuniare.sori.studio.git playuniare
cd playuniare
```
비공개 저장소가 인증을 요구하면 비밀번호 대신 Git 서버에서 발급한 접근
토큰을 사용하는 것을 권장한다. 토큰을 명령문이나 문서에 저장하지 않는다.
비공개 저장소가 인증을 요구하면 Git 서버의 접근 토큰을 사용한다. 토큰을
문서나 `.env`에 저장하지 않는다.
### 2. NAS 전용 환경변수 파일 만들기
프로젝트를 처음 설치한 경우 예시 파일을 복사한다.
### 3. NAS 환경변수 파일 생성
```bash
cp .env.example .env
```
기본 포트는 다음과 같다.
기본 내용:
```text
PLAYUNIARE_TEST_HTTP_PORT=41808
PLAYUNIARE_PRODUCTION_HTTP_PORT=41809
VITE_CARD_ASSET_BASE_URL=/card-assets
VITE_SOCKET_SERVER_URL=http://playuniare.sori.studio
```
NAS에서 이미 사용 중인 포트라면 `.env`를 열어 앞쪽 숫자만 다른 값으로
변경한다. `.env`는 Git에 올라가지 않으므로 NAS별 설정을 안전하게 유지할 수
있다.
현재 Socket.IO 서버는 구현 전이므로 `VITE_SOCKET_SERVER_URL`은 아직 실제
기능에 사용되지 않는다.
현재 Docker 컨테이너가 공개하고 있는 포트는 다음 명령으로 확인한다.
### 4. 포트 사용 여부 확인
```bash
docker ps --format "table {{.Names}}\t{{.Ports}}"
```
사용 중인 숫자를 피해서 `1024`보다 큰 포트를 선택한다.
### 3. 테스트 컨테이너 빌드 및 실행
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
up -d --build
```
이 명령은 다음 작업을 한 번에 수행한다.
- 최신 소스 코드로 Docker 이미지 생성
- 테스트 컨테이너 실행
- NAS의 `PLAYUNIARE_TEST_HTTP_PORT`와 컨테이너의 80 포트 연결
### 4. 브라우저에서 확인
NAS의 내부 IP가 `192.168.0.20`이라면 다음 주소로 접속한다.
`41809`가 이미 사용 중이면 `.env`의 값을 다른 포트로 바꾼다.
```text
http://192.168.0.20:41808
PLAYUNIARE_PRODUCTION_HTTP_PORT=41819
```
실제 NAS IP와 `.env`에 입력한 테스트 포트에 맞춰 접속한다.
이 경우 리버스 프록시 대상 포트도 같은 값으로 변경해야 한다.
### 5. 테스트 로그 확인
화면이 열리지 않으면 다음을 실행한다.
### 5. 운영 컨테이너 실행
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
ps
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
logs --tail=100
```
`Up` 또는 `running` 상태인지 확인한다.
### 6. 테스트 버전 중지
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
down
```
컨테이너는 제거되지만 소스 코드와 Docker 이미지는 남아 있다.
## 운영 버전 최초 배포
### 1. 배포 전 로컬 검증
개발 컴퓨터에서 다음 명령이 성공해야 한다.
```bash
npm install
npm run build
```
그 후 변경 사항을 커밋하고 원격 저장소에 푸시한다.
### 2. NAS에서 운영 컨테이너 실행
NAS 프로젝트 폴더에서 실행한다.
```bash
cd /volume1/docker/projects/apps/playuniare
git pull
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
up -d --build
```
운영 컨테이너는 기본적으로 NAS의 `41809` 포트를 사용한다. `.env`의
`PLAYUNIARE_PRODUCTION_HTTP_PORT`를 변경했다면 해당 포트를 사용한다.
상태 확인:
```text
http://NAS_IP:41809
```bash
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
ps
```
### 3. 도메인 연결
`playuniare-web`이 `healthy` 또는 `running`이면 정상이다.
UGREEN NAS 또는 사용 중인 리버스 프록시에서 다음과 같이 연결한다.
### 6. 리버스 프록시 연결
현재 연결 기준:
```text
외부 도메인: https://playuniare.sori.studio
외부 주소: http://playuniare.sori.studio
내부 대상: http://127.0.0.1:41809
```
- 외부 접속은 HTTPS를 사용한다.
- `.env`에서 운영 포트를 변경했다면 리버스 프록시의 내부 대상 포트도 같은
값으로 변경한다.
- 공유기에서 컨테이너 포트를 그대로 외부에 노출하기보다 리버스 프록시를
사용한다.
- 인증서 자동 갱신이 설정되어 있는지 확인한다.
`.env`에서 포트를 변경했다면 내부 대상 포트도 변경한다.
### 4. 카드 이미지 연결 확인
### 7. 배포 확인
브라우저에서 실제 카드 파일 하나를 직접 연다.
메인 화면:
```text
http://NAS_IP:41809/card-assets/ua03/EX01BT_HTR-2-001.png
http://playuniare.sori.studio
```
이미지가 보이면 읽기 전용 마운트와 Nginx 경로가 정상이다. 파일명이 실제로
존재하는 카드인지 먼저 확인한다.
카드 이미지:
이미지가 보이지 않으면 NAS 터미널에서 원본 경로를 확인한다.
```bash
ls /volume1/docker/projects/apps/uniare/config/www/images
```text
http://playuniare.sori.studio/card-assets/ua03/EX01BT_HTR-2-001.png
```
시리즈 폴더가 보여야 한다. 다음 명령으로 컨테이너 내부 연결도 확인한다.
```bash
docker exec playuniare-web ls /srv/uniare-card-images
```
이 경로 역시 같은 시리즈 폴더를 보여야 한다.
Play Uniare의 Nginx 작업 프로세스는 기존 카드 파일 소유자와 같은 UID
`1000`의 `cardreader` 사용자로 실행된다. 원본 카드 폴더는 계속 읽기 전용으로
마운트되므로 시뮬레이터가 파일을 수정하거나 삭제할 수 없다.
두 주소가 모두 열리면 현재 정적 배포와 카드 자산 연결은 정상이다.
## 새 버전 업데이트
### 안전한 권장 순서
### 개발 Mac
1. 개발 컴퓨터에서 `npm run build`를 실행한다.
2. 변경 내용을 Git에 커밋하고 푸시한다.
3. NAS에서 테스트 버전을 먼저 업데이트한다.
4. 테스트 주소에서 주요 기능을 확인한다.
5. 문제가 없으면 운영 버전을 업데이트한다.
1. 기능을 구현한다.
2. 관련 문서를 수정한다.
3. `npm run build`를 실행한다.
4. 필요한 경우 로컬 Docker로 검증한다.
5. Git에 커밋하고 푸시한다.
### 테스트 버전 업데이트
### NAS
```bash
cd /volume1/docker/projects/apps/playuniare
git pull
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
up -d --build
```
브라우저에서 `http://NAS_IP:41808`을 확인한다. `.env`에서 테스트 포트를
변경했다면 해당 포트로 접속한다.
### 운영 버전 업데이트
```bash
cd /volume1/docker/projects/apps/playuniare
git pull
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
up -d --build
```
브라우저에서 실제 도메인을 확인한다.
### 사용하지 않는 Docker 이미지 정리
업데이트를 여러 번 하면 이전 이미지가 NAS 용량을 차지할 수 있다.
배포 후 반드시 확인한다.
```bash
docker image prune
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
ps
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
logs --tail=100
```
삭제 대상 확인 질문이 나오면 내용을 읽고 `y`를 입력한다. `-a` 옵션이나
볼륨 삭제 명령은 데이터 유실 가능성이 있으므로 이 문서에서는 사용하지 않는다.
마지막으로 실제 도메인에서 메인 화면과 주요 기능을 확인한다.
## 기존 NAS 테스트 컨테이너 제거
이전에 `playuniare-test-web`을 실행했다면 한 번만 정리한다.
```bash
docker rm -f playuniare-test-web
```
컨테이너가 없다는 메시지가 나오면 이미 제거된 것이므로 무시해도 된다.
테스트용 Docker 네트워크가 남아 있다면 확인 후 제거한다.
```bash
docker network ls
docker network rm playuniare-test_default
```
네트워크가 사용 중이거나 존재하지 않으면 무리하게 삭제하지 않는다.
## 이전 버전으로 되돌리기
문제가 생기면 먼저 현재 커밋을 확인한다.
최근 커밋 확인:
```bash
git log --oneline -10
```
정상 동작했던 커밋 식별자를 확인한 뒤 다음처럼 전환한다.
정상 동작했던 커밋으로 전환:
```bash
git checkout 정상커밋식별자
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
up -d --build
```
복구가 끝난 뒤 개발 컴퓨터에서 문제를 수정하고 정상 브랜치에 새 커밋을
만든다. NAS에서 임의로 소스 파일을 직접 수정하지 않는다.
다시 최신 `main`으로 돌아갈 때:
복구 후 개발 Mac에서 문제를 수정해 새로운 커밋을 만든다. 다시 최신
`main`으로 돌아갈 때:
```bash
git checkout main
git pull
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
@@ -328,163 +259,113 @@ docker compose \
## 운영 점검 명령
상태:
```bash
docker compose -f compose.yaml -f compose.nas.yaml ps
```
최근 로그:
```bash
docker compose -f compose.yaml -f compose.nas.yaml logs --tail=100
```
재시작:
```bash
docker compose -f compose.yaml -f compose.nas.yaml restart
```
- `ps`: 실행 상태 확인
- `logs`: 최근 오류 확인
- `restart`: 같은 이미지로 컨테이너 재시작
중지 및 제거:
## `port is already allocated` 오류
```bash
docker compose -f compose.yaml -f compose.nas.yaml down
```
다음 오류는 Docker 이미지 빌드 문제가 아니라 NAS 호스트 포트 충돌이다.
## 포트 충돌
다음 오류는 NAS 호스트 포트가 다른 서비스에 사용 중이라는 뜻이다.
```text
Bind for 0.0.0.0:<포트> failed: port is already allocated
```
먼저 실패하며 생성된 테스트 컨테이너를 정리한다.
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
down
```
현재 사용 중인 Docker 포트를 확인한다.
사용 중인 포트 확인:
```bash
docker ps --format "table {{.Names}}\t{{.Ports}}"
```
`.env`에서 테스트 포트를 사용하지 않는 숫자로 변경한다.
`.env`의 `PLAYUNIARE_PRODUCTION_HTTP_PORT`를 사용하지 않는 숫자로 변경하고
다시 실행한다. 컨테이너 내부의 80 포트는 다른 컨테이너와 같아도 괜찮다.
```text
PLAYUNIARE_TEST_HTTP_PORT=41818
```
## 카드 이미지가 403인 경우
그다음 다시 실행한다.
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
up -d --build
```
컨테이너 내부의 80 포트는 다른 컨테이너와 같아도 괜찮다. `.env`에 설정하는
NAS 호스트 포트만 겹치지 않으면 된다.
## 카드 이미지가 `403 Forbidden`인 경우
로그에 다음 내용이 있으면 Nginx 작업 사용자가 NAS 카드 파일을 읽지 못한
것이다.
로그에 다음 내용이 있으면 NAS ACL에 의한 읽기 권한 문제다.
```text
open() "/srv/uniare-card-images/..." failed (13: Permission denied)
```
현재 Docker 이미지는 기존 카드 파일 소유자 UID `1000`에 맞춘 `cardreader`
사용자를 사용한다. 최신 소스를 받은 후 이미지를 반드시 다시 빌드한다.
현재 이미지는 카드 파일 소유자 UID `1000`과 맞춘 `cardreader` 사용자를
사용한다. 최신 소스를 받고 반드시 이미지를 다시 빌드한다.
```bash
cd /volume1/docker/projects/apps/playuniare
git pull
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
down
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
up -d --build
docker compose -f compose.yaml -f compose.nas.yaml up -d --build
```
Nginx 워커 사용자를 확인하려면:
Nginx 워커 확인:
```bash
docker exec playuniare-test-web ps aux
docker exec playuniare-web ps aux
```
`nginx: worker process`가 `cardreader`로 표시되어야 한다.
카드 파일 읽기를 직접 확인하려면:
카드 읽기 확인:
```bash
docker exec playuniare-test-web \
docker exec playuniare-web \
su -s /bin/sh cardreader -c \
'head -c 10 /srv/uniare-card-images/ua03/EX01BT_HTR-2-001.png >/dev/null && echo READ_OK'
```
`READ_OK`가 나오면 다음 주소에서 카드가 보여야 한다.
## 데이터 마이그레이션
```text
http://NAS_IP:41808/card-assets/ua03/EX01BT_HTR-2-001.png
```
현재는 데이터베이스가 없으므로 마이그레이션할 데이터도 없다. 방 상태 저장,
사용자 계정 또는 덱 저장 데이터베이스가 추가되면
`docs/migrations.md`의 원칙을 따른다.
NAS에서 카드 파일 소유자의 UID가 `1000`이 아닌 경우에는 임의로 폴더 권한을
`777`로 변경하지 말고, 실제 UID를 확인한 뒤 Docker 실행 사용자 정책을
조정한다.
NAS에서 SQL이나 임시 명령을 직접 입력하는 방식은 초기에는 빠르지만, 같은
변경을 다시 재현하거나 복구하기 어렵다. 운영 데이터가 생긴 뒤에는 반드시
Git에 포함된 버전 관리 마이그레이션 코드를 실행하는 방식으로 전환한다.
## 배포 체크리스트
- [ ] 로컬 `npm run build` 성공
- [ ] 필요한 로컬 Docker 검증 성공
- [ ] 관련 문서 업데이트
- [ ] Git 커밋과 푸시 완료
- [ ] 테스트 컨테이너 재빌드
- [ ] 테스트 URL 주요 기능 확인
- [ ] NAS `git pull` 완료
- [ ] 운영 컨테이너 재빌드
- [ ] 실제 도메인 HTTPS 접속 확인
- [ ] `/card-assets/<series>/<fileName>` 이미지 접속 확인
- [ ] 카드 이미지 마운트가 읽기 전용인지 확인
- [ ] 브라우저 새로고침과 직접 URL 접근 확인
- [ ] 오류 로그 확인
- [ ] 컨테이너 상태와 로그 확인
- [ ] `http://playuniare.sori.studio` 확인
- [ ] `/card-assets/<series>/<fileName>` 확인
- [ ] `.env`, 토큰, 비밀번호가 Git에 없는지 확인
## 향후 실시간 서버 추가 시
## 향후 실시간 서버
프런트엔드만 있을 때는 Nginx 컨테이너 하나면 충분하다. Socket.IO 서버가
추가되면 다음 항목이 필요하다.
- Node.js 서버 컨테이너
- WebSocket을 지원하는 리버스 프록시 설정
- 프런트엔드와 서버의 환경변수
- 방 상태 백업 또는 Redis 도입 여부
- 컨테이너 재시작 시 방이 종료된다는 안내
로컬 개발 서버의 기본 포트는 `31873`으로 정한다. Docker 네트워크 안에서는
다른 컨테이너가 같은 내부 포트를 사용해도 문제가 없다. 충돌하는 것은 NAS
호스트에 공개한 포트다.
운영 환경에서는 Socket.IO 서버 포트를 NAS 외부에 직접 공개하지 않는 것을
원칙으로 한다.
Socket.IO 서버가 추가되면 브라우저에 서버 포트를 직접 공개하지 않는다.
```text
브라우저
→ https://playuniare.sori.studio/socket.io
→ http://playuniare.sori.studio/socket.io
→ Play Uniare Nginx
→ Docker 내부 Socket.IO 서버:31873
```
이 구조에서는 NAS의 다른 서비스가 호스트 포트 `31873`을 사용 중이어도
Socket.IO 컨테이너가 해당 포트를 호스트에 공개하지 않는 한 충돌하지 않는다.
실시간 서버 구현 시 `expose`만 사용하고 `ports`는 추가하지 않는 방향을
Docker 내부에서는 다른 컨테이너와 같은 포트를 사용해도 충돌하지 않는다.
Socket.IO 서비스는 `ports` 대신 내부 통신용 `expose`를 사용하는 방향을
우선한다.
이 구조가 변경되면 반드시 이 문서와 `docs/history.md`를 함께 수정한다.
## NAS 전용 Compose 파일을 분리한 이유
- `compose.yaml`: 어느 컴퓨터에서도 검사하고 빌드할 수 있는 공통 운영 설정
- `compose.nas.yaml`: NAS에만 존재하는 카드 이미지 절대 경로 연결
- `compose.test.yaml`: 공통 테스트 설정
- `compose.test.nas.yaml`: 테스트 컨테이너의 NAS 카드 이미지 연결
개발 Mac에는 `/volume1/...` 경로가 없으므로 NAS 전용 파일을 사용하지 않는다.
NAS에서는 반드시 공통 파일과 NAS 전용 파일을 함께 지정한다.

View File

@@ -24,7 +24,8 @@
- UGREEN NAS에서 재현 가능한 배포를 위해 Docker Compose를 사용한다.
- Vue 빌드에는 Node.js를 사용하고 실행 이미지는 Nginx를 사용한다.
- 테스트와 운영 컨테이너를 서로 다른 포트로 분리한다.
- 초기에는 테스트와 운영 컨테이너를 분리했으며 이후 운영 단일 구조로
단순화했다.
## 2026-06-19: 기존 카드 이미지 읽기 전용 공유
@@ -58,3 +59,11 @@
- 카드 파일 소유자 UID가 `1000`인 것을 확인했다.
- Nginx 워커를 UID `1000`의 `cardreader` 사용자로 실행하도록 변경했다.
- Vue 정적 파일에는 빌드 단계에서 명시적인 읽기 권한을 적용했다.
## 2026-06-19: 로컬 테스트와 NAS 운영 단일화
- 기능 및 Docker 검증은 개발 Mac에서 수행한다.
- NAS에는 운영 컨테이너 하나만 유지한다.
- 테스트 Compose 파일과 테스트 포트 환경변수를 제거했다.
- 운영 주소를 `http://playuniare.sori.studio`로 확인했다.
- 영구 데이터가 생길 때 적용할 마이그레이션 운영 원칙을 문서화했다.

67
docs/migrations.md Normal file
View File

@@ -0,0 +1,67 @@
# 데이터 마이그레이션 운영 원칙
## 현재 상태
현재 Play Uniare에는 데이터베이스와 영구 서버 데이터가 없다. 따라서 지금은
마이그레이션 명령을 실행할 필요가 없다.
## 마이그레이션이 필요한 시점
다음 기능 중 하나가 추가되면 마이그레이션 체계를 도입한다.
- 사용자 계정
- 저장된 덱
- 게임 기록
- 장기 유지되는 방 상태
- Redis 외의 영구 데이터베이스
- 기존 저장 데이터의 구조 변경
## 기본 원칙
1. 모든 마이그레이션 코드는 Git으로 관리한다.
2. 파일명에 실행 순서와 목적이 드러나야 한다.
3. 같은 마이그레이션을 두 번 실행해도 데이터가 망가지지 않아야 한다.
4. 운영 실행 전에 데이터베이스를 백업한다.
5. 로컬 또는 복제 데이터로 먼저 검증한다.
6. 성공 여부를 기록하는 마이그레이션 테이블을 사용한다.
7. NAS에서 즉석 SQL을 직접 입력하는 것은 긴급 복구 외에는 금지한다.
8. 긴급 명령을 사용했다면 같은 내용을 정식 마이그레이션 코드로 남긴다.
## 예정 디렉터리
데이터베이스 기술이 결정되면 다음과 같은 구조를 사용한다.
```text
server/
└── migrations/
├── 0001_create_rooms.js
└── 0002_add_room_expiration.js
```
실제 도구가 Prisma, Drizzle 또는 다른 마이그레이션 시스템으로 결정되면 해당
도구의 표준 디렉터리를 우선한다.
## 배포 순서
일반적인 운영 배포 순서:
1. 데이터베이스 백업
2. 새 코드 가져오기
3. 마이그레이션 실행
4. 애플리케이션 컨테이너 재빌드
5. 상태와 로그 확인
6. 주요 데이터 조회 검증
마이그레이션과 애플리케이션 코드의 선후 관계가 다르면 해당 릴리스의
`docs/update.md`에 정확한 순서를 기록한다.
## 파괴적 변경
열 삭제, 데이터 타입 축소, 대량 데이터 재작성은 한 번에 수행하지 않는다.
1. 새 필드 추가
2. 기존 데이터를 새 필드로 복사
3. 애플리케이션을 새 필드로 전환
4. 충분한 확인 기간 후 이전 필드 제거
복구 불가능한 변경은 사용자 승인과 백업 확인 없이 실행하지 않는다.

View File

@@ -77,11 +77,12 @@
- 로컬 Socket.IO 서버 기본 포트는 `31873`이다.
- Docker 컨테이너 내부 포트는 다른 컨테이너와 같아도 충돌하지 않는다.
- NAS 호스트에 공개하는 포트는 다른 서비스와 겹치면 안 된다.
- 테스트 HTTP 호스트 포트 기본값은 `41808`, 운영 기본값은 `41809`다.
- HTTP 호스트 포트는 `.env`의 `PLAYUNIARE_TEST_HTTP_PORT`와
- NAS 운영 HTTP 호스트 포트 기본값은 `41809`다.
- 운영 HTTP 호스트 포트는 `.env`의
`PLAYUNIARE_PRODUCTION_HTTP_PORT`로 변경한다.
- 기능 테스트는 로컬에서 수행하고 NAS에는 운영 컨테이너만 배포한다.
- 운영 브라우저는 Socket.IO 포트를 직접 호출하지 않고
`https://playuniare.sori.studio/socket.io`처럼 같은 도메인의 리버스 프록시
`http://playuniare.sori.studio/socket.io`처럼 같은 도메인의 리버스 프록시
경로를 사용한다.
- 포트 번호는 코드에 하드코딩하지 않고 환경변수와 Compose 설정으로 관리한다.
@@ -89,7 +90,7 @@
```text
VITE_CARD_ASSET_BASE_URL=/card-assets
VITE_SOCKET_SERVER_URL=https://playuniare.sori.studio
VITE_SOCKET_SERVER_URL=http://playuniare.sori.studio
```
실제 운영 값은 `.env`에 두고 Git에 커밋하지 않는다.
@@ -120,3 +121,11 @@ Play Uniare 컨테이너 내부:
`cardreader` 사용자로 실행한다.
- 카드 이미지 URL은 `/card-assets/<series>/<fileName>` 형식을 사용한다.
- 같은 Play Uniare 도메인으로 제공되므로 별도의 CORS 설정이 필요하지 않다.
## 배포 환경
- 로컬 개발: Vite 개발 서버
- 로컬 배포 검증: Docker Compose
- NAS 운영: `compose.yaml`과 `compose.nas.yaml`
- 운영 주소: `http://playuniare.sori.studio`
- NAS 테스트 컨테이너는 별도로 유지하지 않는다.

View File

@@ -17,3 +17,7 @@
변경하고 `port is already allocated` 복구 절차를 추가했다.
- NAS ACL로 인한 카드 이미지와 favicon의 403 오류를 해결하기 위해 Nginx
워커 UID와 정적 파일 권한을 조정했다.
- 테스트는 로컬에서 수행하고 NAS에는 운영 컨테이너만 배포하도록 Compose와
배포 문서를 단순화했다.
- 운영 주소 `http://playuniare.sori.studio`와 향후 데이터 마이그레이션
원칙을 문서화했다.