diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ac59dce --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.gitignore +.DS_Store +node_modules +frontend/node_modules +frontend/dist +backend/node_modules +backend/.sessions +backend/uploads +docker-compose.yml +docs diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..9e39de2 --- /dev/null +++ b/.env.production.example @@ -0,0 +1,5 @@ +MARIADB_ROOT_PASSWORD=change-this-root-password +MARIADB_DATABASE=tier_cursor +MARIADB_USER=tier_cursor +MARIADB_PASSWORD=change-this-db-password +SESSION_SECRET=change-this-session-secret diff --git a/README.md b/README.md index 80d26ec..da79ed7 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,18 @@ VITE_API_ORIGIN=http://localhost:5179 npm run dev 자세한 내용은 [docs/local-mariadb.md](/Users/bicute/Desktop/zenn.dev/tier-cursor/docs/local-mariadb.md)를 참고하세요. +## UGREEN NAS 운영 배포 + +운영용은 `MariaDB + backend + frontend` 3컨테이너 구조를 권장합니다. + +```bash +cp .env.production.example .env.production +docker compose --env-file .env.production -f docker-compose.prod.yml up -d --build +``` + +- 프로덕션 컴포즈: [docker-compose.prod.yml](/Users/bicute/Desktop/zenn.dev/tier-cursor/docker-compose.prod.yml) +- 배포 가이드: [docs/ugreen-nas-deploy.md](/Users/bicute/Desktop/zenn.dev/tier-cursor/docs/ugreen-nas-deploy.md) + ## 사용 흐름(현재 구현) - **게임 선택**: `/`에서 게임 클릭 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..283e0bf --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,15 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY backend/package*.json ./ +RUN npm ci --omit=dev + +COPY backend/ ./ + +ENV NODE_ENV=production +ENV PORT=5179 + +EXPOSE 5179 + +CMD ["node", "index.js"] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..32c8b0a --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,79 @@ +services: + mariadb: + image: mariadb:11.4 + container_name: tmaker-mariadb + restart: unless-stopped + environment: + MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD} + MARIADB_DATABASE: ${MARIADB_DATABASE} + MARIADB_USER: ${MARIADB_USER} + MARIADB_PASSWORD: ${MARIADB_PASSWORD} + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + volumes: + - tmaker_mariadb_data:/var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "mariadb-admin ping -h 127.0.0.1 -u$$MARIADB_USER -p$$MARIADB_PASSWORD --silent"] + interval: 10s + timeout: 5s + retries: 10 + + backend: + build: + context: . + dockerfile: backend/Dockerfile + container_name: tmaker-backend + restart: unless-stopped + depends_on: + mariadb: + condition: service_healthy + environment: + PORT: 5179 + DB_HOST: mariadb + DB_PORT: 3306 + DB_USER: ${MARIADB_USER} + DB_PASSWORD: ${MARIADB_PASSWORD} + DB_NAME: ${MARIADB_DATABASE} + SESSION_SECRET: ${SESSION_SECRET} + SESSION_COOKIE_SECURE: "true" + SESSION_COOKIE_SAME_SITE: "lax" + CORS_ORIGINS: https://tmaker.sori.studio + TRUST_PROXY: 1 + volumes: + - tmaker_uploads:/app/uploads + - tmaker_sessions:/app/.sessions + + frontend: + build: + context: . + dockerfile: frontend/Dockerfile + args: + VITE_API_ORIGIN: https://tmaker.sori.studio + container_name: tmaker-frontend + restart: unless-stopped + depends_on: + - backend + ports: + - "8080:80" + + phpmyadmin: + image: phpmyadmin:5.2-apache + container_name: tmaker-phpmyadmin + restart: unless-stopped + profiles: ["admin"] + depends_on: + mariadb: + condition: service_healthy + environment: + PMA_HOST: mariadb + PMA_PORT: 3306 + PMA_USER: ${MARIADB_USER} + PMA_PASSWORD: ${MARIADB_PASSWORD} + ports: + - "8081:80" + +volumes: + tmaker_mariadb_data: + tmaker_uploads: + tmaker_sessions: diff --git a/docs/history.md b/docs/history.md index dca3023..1b2567b 100644 --- a/docs/history.md +++ b/docs/history.md @@ -98,3 +98,7 @@ ## 2026-03-26 v0.1.26 - 아이콘 크기는 사용자 취향 차이가 큰 요소이므로, 고정값 하나보다 기본 `80px`에 단계형 크기 선택을 제공하는 편이 더 낫다고 판단했다. + +## 2026-03-26 v0.1.27 +- NAS 운영은 리버스 프록시 설정을 단순하게 유지하는 편이 좋으므로, 프런트 컨테이너 하나만 외부 공개하고 `/api`, `/uploads`는 내부 프록시로 넘기는 구조를 채택했다. +- 운영은 로컬 개발 컴포즈와 분리된 전용 `docker-compose.prod.yml`을 두고, 환경변수는 `.env.production`으로 분리해 관리하기로 결정했다. diff --git a/docs/spec.md b/docs/spec.md index 3fead77..62361e8 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -6,6 +6,7 @@ - 데이터 저장소: MariaDB(MySQL 호환) - 세션 저장소: `session-file-store` 기반 파일 세션 - 업로드 저장소: 로컬 파일 시스템(`backend/uploads/`) +- 운영 배포: `frontend(Nginx 정적 서빙 + /api,/uploads 프록시) + backend + mariadb` Docker Compose 구조 ## 데이터 저장 구조 - 메인 데이터베이스: MariaDB `tier_cursor` (기본값) @@ -138,6 +139,11 @@ - `SESSION_COOKIE_SECURE`: `true`면 HTTPS 전용 쿠키 - `SESSION_COOKIE_SAME_SITE`: 기본 `lax` +## 운영 배포 메모 +- 프로덕션 컴포즈 파일은 [docker-compose.prod.yml](/Users/bicute/Desktop/zenn.dev/tier-cursor/docker-compose.prod.yml)이다. +- 외부 도메인 `tmaker.sori.studio`는 `frontend` 컨테이너의 `8080` 포트로 리버스 프록시하고, `/api`, `/uploads`, `/health`는 프런트 Nginx가 내부 `backend:5179`로 전달한다. +- 운영 볼륨은 MariaDB 데이터, 업로드 파일, 세션 파일을 각각 분리해 유지한다. + ## NAS 배포 메모 - 현재 구조는 MariaDB/MySQL 계열이므로 NAS에 MariaDB를 올리면 phpMyAdmin 또는 Adminer로 직접 데이터 확인이 가능하다. - 추천 구성: diff --git a/docs/ugreen-nas-deploy.md b/docs/ugreen-nas-deploy.md new file mode 100644 index 0000000..18509ce --- /dev/null +++ b/docs/ugreen-nas-deploy.md @@ -0,0 +1,78 @@ +# UGREEN NAS 배포 가이드 + +## 개요 +- 운영 기본 컨테이너는 `mariadb`, `backend`, `frontend` 3개다. +- `phpmyadmin`은 필요할 때만 `admin` 프로필로 추가 실행한다. +- 외부 공개는 `frontend` 컨테이너 하나만 하고, `/api`, `/uploads`, `/health`는 내부적으로 `backend`로 프록시한다. +- 도메인은 `https://tmaker.sori.studio` 기준으로 설정한다. + +## 파일 +- 프로덕션 컴포즈: [docker-compose.prod.yml](/Users/bicute/Desktop/zenn.dev/tier-cursor/docker-compose.prod.yml) +- 백엔드 이미지: [backend/Dockerfile](/Users/bicute/Desktop/zenn.dev/tier-cursor/backend/Dockerfile) +- 프런트 이미지: [frontend/Dockerfile](/Users/bicute/Desktop/zenn.dev/tier-cursor/frontend/Dockerfile) +- 프런트 Nginx 프록시: [frontend/nginx.conf](/Users/bicute/Desktop/zenn.dev/tier-cursor/frontend/nginx.conf) +- 환경변수 예시: [.env.production.example](/Users/bicute/Desktop/zenn.dev/tier-cursor/.env.production.example) + +## 1. 프로젝트 업로드 +- NAS 작업 폴더에 현재 프로젝트를 그대로 업로드한다. +- 기존 로컬 MariaDB 내용은 무시하고 새 운영 DB로 시작해도 된다. + +## 2. 환경변수 파일 준비 +- 루트에서 `.env.production.example`를 복사해 `.env.production`으로 만든다. +- 최소 변경값: + - `MARIADB_ROOT_PASSWORD` + - `MARIADB_PASSWORD` + - `SESSION_SECRET` + +예시: + +```env +MARIADB_ROOT_PASSWORD=very-strong-root-password +MARIADB_DATABASE=tier_cursor +MARIADB_USER=tier_cursor +MARIADB_PASSWORD=very-strong-app-password +SESSION_SECRET=very-strong-random-session-secret +``` + +## 3. 컨테이너 실행 + +```bash +docker compose --env-file .env.production -f docker-compose.prod.yml up -d --build +``` + +- phpMyAdmin까지 같이 띄우려면: + +```bash +docker compose --env-file .env.production -f docker-compose.prod.yml --profile admin up -d --build +``` + +## 4. NAS 리버스 프록시 +- 외부 도메인: `tmaker.sori.studio` +- 내부 대상 프로토콜: `http` +- 내부 대상 호스트: NAS IP 또는 `localhost` +- 내부 대상 포트: `8080` + +즉 NAS 리버스 프록시는 `frontend` 컨테이너의 `8080`만 바라보면 된다. + +## 5. HTTPS / 쿠키 +- 현재 프로덕션 컴포즈는 `SESSION_COOKIE_SECURE=true`를 사용한다. +- 따라서 `tmaker.sori.studio`에는 HTTPS 인증서가 연결되어 있어야 한다. +- NAS 리버스 프록시가 HTTPS 종료를 하고 내부는 `http://frontend:80` 또는 `localhost:8080`으로 전달하면 된다. + +## 6. 데이터 위치 +- MariaDB 데이터: Docker volume `tmaker_mariadb_data` +- 업로드 파일: Docker volume `tmaker_uploads` +- 세션 파일: Docker volume `tmaker_sessions` + +## 7. 점검 포인트 +- 메인 접속: `https://tmaker.sori.studio` +- 헬스체크: `https://tmaker.sori.studio/health` +- 관리자 로그인 후: + - 게임 생성 + - 썸네일 업로드 + - 티어표 저장 + - 이미지 다운로드 + +## 8. 참고 +- 현재 업로드 이미지는 서버 저장 전에 리사이즈/압축하지 않는다. +- 운영 중 원본 이미지가 많이 쌓이면 이후 `sharp` 기반 최적화 단계를 추가하는 것이 좋다. diff --git a/docs/update.md b/docs/update.md index ba056a8..e206fe9 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,10 @@ # 업데이트 로그 +## 2026-03-26 v0.1.27 +- **UGREEN NAS 배포 파일 추가**: `backend`, `frontend`용 Dockerfile과 프런트 Nginx 프록시 설정, 프로덕션 전용 `docker-compose.prod.yml` 추가 +- **운영 환경 예시 추가**: `.env.production.example`로 MariaDB/세션 시크릿 환경변수 템플릿 제공 +- **배포 문서화**: `tmaker.sori.studio` 기준 NAS 리버스 프록시, 컨테이너 실행, 볼륨 구성 가이드를 문서에 정리 + ## 2026-03-26 v0.1.26 - **아이콘 크기 조절 추가**: 티어표 편집기에서 `48 / 60 / 80 / 100 / 120` 단계로 아이콘 크기를 직접 바꿀 수 있도록 추가 - **기본 아이콘 크기 상향**: 기본 `.thumb` 크기를 `80px` 기준으로 조정하고, 보드와 우측 아이템 목록에 함께 반영되도록 정리 diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..55c979a --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,22 @@ +FROM node:20-alpine AS build + +WORKDIR /app + +COPY frontend/package*.json ./ +RUN npm ci + +COPY frontend/ ./ + +ARG VITE_API_ORIGIN=https://tmaker.sori.studio +ENV VITE_API_ORIGIN=${VITE_API_ORIGIN} + +RUN npm run build + +FROM nginx:1.27-alpine + +COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..1c10ce8 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,38 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://backend:5179/api/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /uploads/ { + proxy_pass http://backend:5179/uploads/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /health { + proxy_pass http://backend:5179/health; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +}