Files
chocoadmin/docs/deployment.md
T

224 lines
8.7 KiB
Markdown

# chocoadmin 배포
## 1. 환경 파일
Synology NAS에서 환경 파일을 생성합니다. 실제 `.env.*` 파일은 커밋하지 마세요.
스테이지 파일 경로:
```bash
/volume1/docker/service/jinaju/chocoadmin/.env.stage
```
스테이지 예시:
```bash
DATABASE_URL="mysql://USER:PASSWORD@mariadb.jisangs.com:30001/chocomae"
AUTH_SECRET="replace-with-stage-secret"
NEXTAUTH_SECRET="replace-with-stage-secret"
AUTH_URL="https://chocoadmin-stage.jisangs.com"
NEXTAUTH_URL="https://chocoadmin-stage.jisangs.com"
```
운영 파일 경로:
```bash
/volume1/docker/service/jinaju/chocoadmin/.env.production
```
운영 예시:
```bash
DATABASE_URL="mysql://CHCOCO_ADMIN_USER:PASSWORD@chocomae.jinaju.com:3306/chocomae"
AUTH_SECRET="replace-with-production-secret"
NEXTAUTH_SECRET="replace-with-production-secret"
AUTH_URL="https://chocoadmin.jinaju.com"
NEXTAUTH_URL="https://chocoadmin.jinaju.com"
```
`AUTH_SECRET``NEXTAUTH_SECRET`은 Auth.js 호환을 위해 각 환경 내에서 동일한 강력한 랜덤 값이어야 합니다. 운영과 스테이지 사이에는 서로 다른 값을 사용하세요.
`APP_ENV`(`production` / `stage`)는 `docker-compose*.yml``environment` 블록에서 설정합니다 — env 파일에 넣지 **마세요**. 이 값이 세션 쿠키 이름(`chocoadmin-${APP_ENV}.session-token`)을 결정하며, 브라우저를 공유하더라도 운영과 스테이지 쿠키를 분리해 줍니다.
공개 URL이 HTTPS를 사용하면 `AUTH_URL``NEXTAUTH_URL` 모두 외부 HTTPS URL과 정확히 일치해야 합니다.
NAS에서 시크릿 생성:
```bash
openssl rand -base64 32
```
## 2. Synology 스테이지 배포
운영과 스테이지 모두 `/volume1/docker/service/jinaju/chocoadmin`의 동일한 Git 체크아웃을 공유합니다. 여기에서 pull하세요:
```bash
cd /volume1/docker/service/jinaju/chocoadmin-stage
git pull --ff-only
```
스테이지는 `docker-compose.stage.yml`, `chocoadmin-stage` 컨테이너, 외부 Docker 네트워크 `proxy-network`를 사용합니다.
네트워크 확인 또는 생성:
```bash
docker network inspect proxy-network >/dev/null 2>&1 || docker network create proxy-network
```
스테이지 시작 또는 업데이트:
```bash
docker compose -f docker-compose.stage.yml up -d --build
docker compose -f docker-compose.stage.yml ps
docker compose -f docker-compose.stage.yml logs -f
```
compose 파일에서 서비스 이름을 바꾼 경우, 오래된 컨테이너를 제거하기 위해 `--remove-orphans`를 한 번 추가하세요.
재배포 후 NPM의 업스트림 DNS 캐시를 비우기 위해 재로드합니다:
```bash
docker exec npm nginx -s reload
```
`docker-compose.stage.yml`은 호스트 포트 `3000`을 바인딩하는 대신 컨테이너 포트 `3000``proxy-network`에 노출합니다 — 호스트 포트 `3000`이 Gitea 같은 다른 서비스에서 이미 사용 중일 수 있기 때문입니다.
Nginx Proxy Manager 설정:
- Scheme: `http`
- Forward Hostname / IP: `chocoadmin-stage`
- Forward Port: `3000`
- Websockets Support: 활성화
- SSL: 활성화
- Force SSL: 활성화
스테이지 URL:
```text
https://chocoadmin-stage.jisangs.com
```
`AUTH_URL`, `NEXTAUTH_URL` 또는 쿠키 관련 설정을 변경한 뒤에는 스테이지 도메인의 브라우저 쿠키를 지우거나 시크릿 창에서 테스트하세요.
## 3. 운영 배포
운영은 `docker-compose.yml`을 사용하며 기본적으로 `.env.production`을 읽습니다:
```bash
cd /volume1/docker/service/jinaju/chocoadmin
git pull --ff-only
docker compose up -d --build
docker compose ps
docker compose logs -f chocoadmin
```
재배포 후 NPM 재로드:
```bash
docker exec npm nginx -s reload
```
Nginx Proxy Manager 설정:
- Scheme: `http`
- Forward Hostname / IP: `chocoadmin`
- Forward Port: `3000`
- Websockets Support: 활성화
- SSL: 활성화
- Force SSL: 활성화
운영 URL:
```text
https://chocoadmin.jinaju.com
```
Docker 빌드는 시크릿을 커밋하지 않고도 Next.js가 컴파일될 수 있도록, 빌드 시점 환경 변수를 플레이스홀더로만 사용합니다. 런타임 값은 compose의 `env_file`에서 읽습니다.
## 4. AWS EC2 보안 그룹
MariaDB는 Synology NAS 공인 IP에서만 접근하도록 허용합니다.
- Type: `MYSQL/Aurora`
- Protocol: `TCP`
- Port: `3306`
- Source: `NAS_PUBLIC_IP/32`
- Description: `chocoadmin Synology NAS`
`3306``0.0.0.0/0`으로 열지 마세요.
## 5. 읽기 전용 리허설
운영 DB에 대해 승인/거절 작업을 활성화하기 전:
1. 읽기 전용 권한을 가진 DB 계정을 생성하거나 사용합니다.
2. `.env.production``DATABASE_URL`을 해당 읽기 전용 계정으로 설정합니다.
3. 컨테이너를 시작합니다.
4. 로그인, 마에스트로 목록, 연장 신청 목록, 업그레이드 신청 목록을 확인합니다.
5. 읽기 화면이 정상 동작한 뒤에만 쓰기 가능한 운영 chocoadmin DB 계정으로 전환합니다.
## 6. 스모크 체크
배포 후 매번:
```bash
docker compose -f docker-compose.stage.yml ps # 스테이지
docker compose ps # 운영
```
브라우저에서 다음 경로들을 확인합니다:
- `/login`
- `/maestros`
- `/extension-requests`
- `/upgrade-requests`
## 7. 트러블슈팅
### 로그인 후 ERR_TOO_MANY_REDIRECTS
미들웨어가 `/login`으로 리다이렉트하고, 로그인 페이지가 다시 `/`로 리다이렉트 — 무한 루프입니다. 다음 체크리스트를 순서대로 진행하세요:
1. **`AUTH_URL` / `NEXTAUTH_URL` 확인** — 스킴을 포함해 공개 HTTPS URL과 정확히 일치해야 합니다.
2. **NPM 스킴 확인** — NPM은 컨테이너로 `https`가 아닌 `http` 스킴으로 포워드해야 합니다. 앱은 들어오는 요청이 아니라 `AUTH_URL`로 HTTPS를 감지합니다.
3. **쿠키 이름 일관성 확인**`auth.ts`(`cookies.sessionToken.name`)와 `proxy.ts`(`getToken({ cookieName })`) 모두 동일한 `chocoadmin-${APP_ENV}.session-token` 값을 사용해야 합니다. `proxy.ts`가 기본 이름을 사용하는데 `auth.ts`는 커스텀 이름을 쓰면, 미들웨어는 세션을 절대 찾지 못합니다.
4. **`__Secure-` 접두사 확인** — 붙이지 마세요. 미들웨어는 Node.js 런타임에서 실행되고 Nginx로부터 HTTP를 받습니다. `__Secure-` 쿠키는 HTTP 상에서 조용히 거부됩니다.
5. 해당 도메인의 브라우저 **쿠키를 삭제**하고 시크릿 창에서 테스트합니다.
6. `.env.*`를 변경한 뒤에는 컨테이너를 **재생성**합니다.
### 운영과 스테이지 트래픽이 섞임
증상: `chocoadmin.jinaju.com`에 로그인했는데 스테이지 UI가 표시되거나, 요청이 두 컨테이너를 번갈아가며 도달함.
원인: Docker는 각 `services:` 키를 `proxy-network`의 DNS 별칭으로 등록합니다. 스테이지와 운영이 같은 서비스 이름을 공유하면, NPM의 업스트림이 라운드 로빈으로 두 컨테이너 모두로 매핑됩니다.
네트워크 확인:
```bash
docker network inspect proxy-network --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
```
기대 결과: `chocoadmin``chocoadmin-stage`가 서로 다른 IP로 나타남. `chocoadmin`이 두 번 나타나면, 오래된 컨테이너가 그 별칭을 사용 중입니다.
수정: `docker-compose.stage.yml``services: chocoadmin-stage:`(`chocoadmin` 아님)를 사용하는지 확인하고, 오래된 컨테이너를 정리하기 위해 `--remove-orphans`로 스테이지를 한 번 재배포한 뒤 NPM을 재로드합니다.
### 재배포 후 Failed to find Server Action
증상: 새 빌드를 배포한 뒤 버튼(예: 로그아웃)을 클릭하면 404 또는 `Failed to find Server Action` 에러가 발생함.
원인: 액션이 인라인 `"use server"` 클로저로 정의되었습니다. 클로저는 빌드마다 새로운 액션 ID를 얻습니다. 브라우저는 오래된 ID를 캐싱합니다.
수정: 강제 새로고침(`Cmd+Shift+R` / `Ctrl+Shift+R`)으로 오래된 액션 ID가 담긴 캐시 페이지를 폐기합니다. 배포마다 문제가 반복된다면, 액션을 별도의 `actions.ts` 파일에서 모듈 레벨 named export로 추출해야 합니다.
### 컨테이너가 시작하자마자 종료됨
```bash
docker compose logs chocoadmin
```
일반적인 원인:
- 누락되었거나 형식이 잘못된 `DATABASE_URL` 연결 문자열.
- `AUTH_SECRET`이 설정되지 않음 — NextAuth가 시작 시점에 예외를 던집니다.
- 포트가 이미 할당됨 — 다른 서비스가 호스트 포트 3000을 사용 중인지 확인하세요. 두 compose 파일 모두 포트 3000에 `ports`가 아닌 `expose`를 사용하므로, compose 파일이 수정되지 않은 한 이런 문제는 발생하지 않아야 합니다.