# 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 파일이 수정되지 않은 한 이런 문제는 발생하지 않아야 합니다.