diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a8c1359 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +.env +.env.* +!.env.example +.git +.gitignore +.next +.pnpm-store +.turbo +.DS_Store +.idea +.claude +coverage +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* +Dockerfile +docker-compose.yml +README.md +docs diff --git a/.env.example b/.env.example index d8c33dc..b69b47b 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,15 @@ DATABASE_URL="mysql://USER:PASSWORD@127.0.0.1:3306/chocomae" AUTH_SECRET="replace-with-local-secret" NEXTAUTH_SECRET="replace-with-local-secret" NEXTAUTH_URL="http://localhost:3001" + +# Production example (.env.production) +# DATABASE_URL="mysql://USER:PASSWORD@AWS_EC2_HOST:3306/chocomae" +# AUTH_SECRET="replace-with-production-secret" +# NEXTAUTH_SECRET="replace-with-production-secret" +# NEXTAUTH_URL="http://NAS_HOST_OR_DOMAIN:3001" + +# Stage example (.env.stage) +# DATABASE_URL="mysql://USER:PASSWORD@STAGE_DB_HOST:3306/chocomae" +# AUTH_SECRET="replace-with-stage-secret" +# NEXTAUTH_SECRET="replace-with-stage-secret" +# NEXTAUTH_URL="http://STAGE_HOST_OR_DOMAIN:3001" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b31a25e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM node:22-alpine AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable +WORKDIR /app + +FROM base AS deps +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV DATABASE_URL="mysql://build:build@127.0.0.1:3306/chocomae" +ENV AUTH_SECRET="build-time-placeholder" +ENV NEXTAUTH_SECRET="build-time-placeholder" +ENV NEXTAUTH_URL="http://localhost:3000" +RUN pnpm prisma generate +RUN pnpm build + +FROM node:22-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV HOSTNAME=0.0.0.0 +ENV PORT=3000 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +CMD ["node", "server.js"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ebd5c07 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +services: + choco-admin: + build: + context: . + dockerfile: Dockerfile + image: choco-admin:latest + container_name: choco-admin + ports: + - "3001:3000" + env_file: + - ${ENV_FILE:-.env.production} + environment: + NODE_ENV: production + PORT: 3000 + HOSTNAME: 0.0.0.0 + restart: unless-stopped diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..9845b64 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,76 @@ +# choco-admin Deployment + +## 1. Production Environment + +Create `.env.production` on the production host. Do not commit it. + +```bash +DATABASE_URL="mysql://CHCOCO_ADMIN_USER:PASSWORD@AWS_EC2_HOST:3306/chocomae" +AUTH_SECRET="replace-with-production-secret" +NEXTAUTH_SECRET="replace-with-production-secret" +NEXTAUTH_URL="http://NAS_HOST_OR_DOMAIN:3001" +``` + +Create `.env.stage` on the stage host with the same keys and stage-specific values. + +`AUTH_SECRET` and `NEXTAUTH_SECRET` should use the same strong random value per environment for Auth.js compatibility. + +## 2. Synology Container Manager + +1. Copy the repository to the NAS. +2. Place `.env.production` in the repository root on the NAS. +3. In Container Manager, create a project from `docker-compose.yml`. +4. Build and start the project. +5. Open `http://NAS_HOST_OR_DOMAIN:3001/login`. + +Equivalent CLI command: + +```bash +docker compose up -d --build +``` + +The Docker build uses placeholder build-time environment variables only so Next.js can compile without committing secrets. Runtime values are read from `.env.production` by default. + +To run with stage settings: + +```bash +ENV_FILE=.env.stage docker compose up -d --build +``` + +## 3. AWS EC2 Security Group + +Allow MariaDB only from the Synology NAS public IP. + +- Type: `MYSQL/Aurora` +- Protocol: `TCP` +- Port: `3306` +- Source: `NAS_PUBLIC_IP/32` +- Description: `choco-admin Synology NAS` + +Do not open `3306` to `0.0.0.0/0`. + +## 4. Read-Only Rehearsal + +Before enabling approval/rejection operations against production DB: + +1. Create or use a DB account with read-only permissions. +2. Set `DATABASE_URL` in `.env.production` to that read-only account. +3. Start the container. +4. Verify login, maestro list, extension request list, and upgrade request list. +5. Switch to the production write-capable choco-admin DB account only after read screens work. + +## 5. Smoke Checks + +After container start: + +```bash +docker compose ps +docker compose logs -f choco-admin +``` + +Verify these routes in the browser: + +- `/login` +- `/maestros` +- `/extension-requests` +- `/upgrade-requests` diff --git a/docs/plan/project-plan.md b/docs/plan/project-plan.md index a5c4070..4caf2fb 100644 --- a/docs/plan/project-plan.md +++ b/docs/plan/project-plan.md @@ -346,11 +346,11 @@ choco-admin/ ### Phase 7. 배포 환경 구성 -- [ ] Dockerfile (프로덕션용 pnpm 빌드) -- [ ] `docker-compose.yml` (Synology NAS Container Manager용) -- [ ] AWS EC2 보안그룹 인바운드 설정 (NAS IP → 3306 허용) -- [ ] Synology NAS에서 컨테이너 실행 테스트 -- [ ] 운영 DB read-only 리허설 후 변경 기능 활성화 +- [x] Dockerfile (프로덕션용 pnpm 빌드) +- [x] `docker-compose.yml` (Synology NAS Container Manager용) +- [ ] AWS EC2 보안그룹 인바운드 설정 (NAS IP → 3306 허용) — `docs/deployment.md`에 절차 문서화 +- [ ] Synology NAS에서 컨테이너 실행 테스트 — `docs/deployment.md`에 절차 문서화 +- [ ] 운영 DB read-only 리허설 후 변경 기능 활성화 — `docs/deployment.md`에 절차 문서화 ### Phase 8. 검증 및 안정화 diff --git a/next.config.ts b/next.config.ts index e9ffa30..68a6c64 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: "standalone", }; export default nextConfig; diff --git a/package.json b/package.json index f2dfaa3..f2d3eb4 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "choco-admin", "version": "0.1.0", "private": true, + "packageManager": "pnpm@10.29.3", "scripts": { "dev": "next dev --port 3001", "build": "next build",