From d9df4e66b484a25f338eb5078a0224792b02303c Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 10 Feb 2026 17:00:32 +0700 Subject: [PATCH] 260210:1700 Start Delpoy --- .dockerignore | 63 ++++++ backend/.dockerignore | 47 +++++ backend/Dockerfile | 82 ++++++++ frontend/.dockerignore | 46 +++++ frontend/Dockerfile | 77 +++++++ specs/08-infrastructure/09_app_deployment.md | 193 ++++++++++++++++++ specs/08-infrastructure/README.md | 24 ++- .../08-infrastructure/docker-compose-app.yml | 121 +++++++++++ 8 files changed, 642 insertions(+), 11 deletions(-) create mode 100644 .dockerignore create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile create mode 100644 specs/08-infrastructure/09_app_deployment.md create mode 100644 specs/08-infrastructure/docker-compose-app.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bb682f0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,63 @@ +# ============================================================ +# Root .dockerignore — pnpm workspace monorepo +# Build context is workspace root for both backend and frontend +# ============================================================ + +# Dependencies (re-installed inside Docker) +node_modules +**/node_modules + +# Git +.git +.github +.gitignore + +# IDE / Editor +.vscode +.idea +.agent +.gemini +.specify +*.swp + +# Build artifacts (rebuilt inside Docker) +backend/dist +frontend/.next + +# Specs & docs (not needed in image) +specs +docs +diagrams +examples + +# Test artifacts +coverage +**/*.spec.ts +**/*.test.ts +**/*.test.tsx + +# Misc +*.log +*.md +!backend/README.md +!frontend/README.md +.prettierrc +.prettierignore +.spectral.yml +.cursorignore +.aignore +lcbp3.code-workspace +lcbp3_dev.session.sql +bfg.jar +fix_links.py +verify_links.py +link_audit_results*.txt +2git.ps1 +scripts + +# OS +.DS_Store +Thumbs.db + +# Environment files +**/.env.local diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..122b6a2 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,47 @@ +# Dependencies +node_modules + +# Build output +dist + +# Git +.git +.gitignore + +# IDE / Editor +.vscode +.idea +*.swp +*.swo + +# Test +test +coverage +**/*.spec.ts +**/*.test.ts +jest.config.* +docker-compose.test.yml + +# Documentation +*.md +!README.md +docs +documentation + +# Docker +Dockerfile +.dockerignore +docker-compose*.yml + +# Environment +.env +.env.* + +# OS +.DS_Store +Thumbs.db + +# Misc +*.log +*.tgz +bfg.jar diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..c92a37b --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,82 @@ +# ============================================================ +# LCBP3 Backend — NestJS Production Dockerfile +# Multi-stage build: deps → build → production +# Target: QNAP TS-473A (Container Station) +# ============================================================ +# Build context: workspace root (nap-dms.lcbp3/) +# Usage: docker build -f backend/Dockerfile -t lcbp3-backend:latest . +# ============================================================ + +# ========================= +# Stage 1: Install Dependencies +# ========================= +FROM node:22-alpine AS deps + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +# Copy workspace root manifests +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY backend/package.json ./backend/ + +# Install backend deps only using pnpm workspace filter +RUN pnpm install --frozen-lockfile --filter backend... + +# ========================= +# Stage 2: Build Application +# ========================= +FROM node:22-alpine AS build + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +# Copy workspace structure +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/backend/node_modules ./backend/node_modules + +# Copy backend source +COPY backend/ ./backend/ + +# Build NestJS → backend/dist +RUN cd backend && pnpm run build + +# Prune dev dependencies +RUN pnpm prune --prod --filter backend... + +# ========================= +# Stage 3: Production Runtime +# ========================= +FROM node:22-alpine AS production + +# Install curl for healthcheck +RUN apk add --no-cache curl + +WORKDIR /app + +ENV TZ=Asia/Bangkok +ENV NODE_ENV=production + +# Create non-root user +RUN addgroup -g 1001 -S nestjs && \ + adduser -S nestjs -u 1001 + +# Copy production artifacts only +COPY --from=build --chown=nestjs:nestjs /app/backend/dist ./dist +COPY --from=build --chown=nestjs:nestjs /app/backend/node_modules ./node_modules +COPY --from=build --chown=nestjs:nestjs /app/backend/package.json ./ + +# Create uploads directory (Two-Phase Storage) +RUN mkdir -p /app/uploads/temp /app/uploads/permanent && \ + chown -R nestjs:nestjs /app/uploads + +USER nestjs + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=30s \ + CMD curl -f http://localhost:3000/health || exit 1 + +CMD ["node", "dist/main"] diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..895e91b --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,46 @@ +# Dependencies +node_modules +.ignored_node_modules + +# Build output +.next + +# Git +.git +.gitignore + +# IDE / Editor +.vscode +.idea +*.swp +*.swo + +# Test +**/*.test.ts +**/*.test.tsx +**/*.spec.ts +vitest.config.ts +vitest.setup.ts +coverage + +# Documentation +*.md +!README.md + +# Docker +Dockerfile +.dockerignore + +# Environment (local dev only) +.env.local + +# OS +.DS_Store +Thumbs.db + +# Build logs +*.log +build-output.txt +build-detailed.txt +tsc.log +tsconfig.tsbuildinfo diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..2baa3d0 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,77 @@ +# ============================================================ +# LCBP3 Frontend — Next.js Production Dockerfile +# Multi-stage build: deps → build → production (standalone) +# Target: QNAP TS-473A (Container Station) +# ============================================================ +# Build context: workspace root (nap-dms.lcbp3/) +# Usage: docker build -f frontend/Dockerfile -t lcbp3-frontend:latest . +# ============================================================ + +# ========================= +# Stage 1: Install Dependencies +# ========================= +FROM node:22-alpine AS deps + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +# Copy workspace root manifests +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY frontend/package.json ./frontend/ + +# Install frontend deps only +RUN pnpm install --frozen-lockfile --filter lcbp3-frontend... + +# ========================= +# Stage 2: Build Application +# ========================= +FROM node:22-alpine AS build + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +# Copy workspace structure +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/frontend/node_modules ./frontend/node_modules + +# Copy frontend source +COPY frontend/ ./frontend/ + +# NEXT_PUBLIC_* vars must be set at BUILD TIME (baked into client bundle) +ARG NEXT_PUBLIC_API_URL=https://backend.np-dms.work/api +ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} + +# Build Next.js → frontend/.next/standalone +RUN cd frontend && pnpm run build + +# ========================= +# Stage 3: Production Runtime +# ========================= +FROM node:22-alpine AS production + +WORKDIR /app + +ENV TZ=Asia/Bangkok +ENV NODE_ENV=production +ENV HOSTNAME=0.0.0.0 +ENV PORT=3000 + +# Create non-root user +RUN addgroup -g 1001 -S nextjs && \ + adduser -S nextjs -u 1001 + +# Copy standalone output from build +COPY --from=build --chown=nextjs:nextjs /app/frontend/.next/standalone ./ +COPY --from=build --chown=nextjs:nextjs /app/frontend/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=20s \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1 + +CMD ["node", "server.js"] diff --git a/specs/08-infrastructure/09_app_deployment.md b/specs/08-infrastructure/09_app_deployment.md new file mode 100644 index 0000000..b2ba91f --- /dev/null +++ b/specs/08-infrastructure/09_app_deployment.md @@ -0,0 +1,193 @@ +# การ Deploy Application (Backend + Frontend) บน QNAP + +> 📍 **Version:** v1.7.0 +> 🖥️ **Server:** QNAP TS-473A (Container Station) +> 🔗 **Docker Compose Path:** `/share/np-dms/app/docker-compose.yml` + +--- + +## 📋 Prerequisites + +ก่อน deploy ต้องมี services เหล่านี้รันอยู่แล้ว: + +| Service | Container Name | Docker Compose | Status | +| :------------- | :------------- | :--------------------------------- | :----- | +| MariaDB | `mariadb` | `lcbp3-db` (MariaDB_setting.md) | ✅ | +| Redis | `cache` | `services` (04_Service_setting.md) | ✅ | +| Elasticsearch | `search` | `services` (04_Service_setting.md) | ✅ | +| NPM | `npm` | `lcbp3-npm` (NPM_setting.md) | ✅ | +| Docker Network | `lcbp3` | `docker network create lcbp3` | ✅ | + +--- + +## 1. Build Docker Images + +### Option A: Build บน Dev Machine แล้ว Transfer + +```bash +# อยู่ที่ workspace root (nap-dms.lcbp3/) + +# Build Backend +docker build -f backend/Dockerfile -t lcbp3-backend:latest . + +# Build Frontend (NEXT_PUBLIC_API_URL bake เข้าไปตอน build) +docker build -f frontend/Dockerfile \ + --build-arg NEXT_PUBLIC_API_URL=https://backend.np-dms.work/api \ + -t lcbp3-frontend:latest . + +# Export เป็น .tar เพื่อ Transfer +docker save lcbp3-backend:latest | gzip > lcbp3-backend.tar.gz +docker save lcbp3-frontend:latest | gzip > lcbp3-frontend.tar.gz + +# Transfer ไปยัง QNAP (ผ่าน SCP หรือ Shared Folder) +scp lcbp3-*.tar.gz admin@192.168.10.8:/share/np-dms/app/ +``` + +### Option B: Build บน QNAP โดยตรง (SSH) + +```bash +# SSH เข้า QNAP +ssh admin@192.168.10.8 + +# Clone หรือ Pull code จาก Gitea +cd /share/np-dms/app/source +git pull origin main + +# Build images +docker build -f backend/Dockerfile -t lcbp3-backend:latest . +docker build -f frontend/Dockerfile \ + --build-arg NEXT_PUBLIC_API_URL=https://backend.np-dms.work/api \ + -t lcbp3-frontend:latest . +``` + +--- + +## 2. Load Images บน QNAP (เฉพาะ Option A) + +```bash +# SSH เข้า QNAP +ssh admin@192.168.10.8 + +# Load images +docker load < /share/np-dms/app/lcbp3-backend.tar.gz +docker load < /share/np-dms/app/lcbp3-frontend.tar.gz + +# ตรวจสอบ +docker images | grep lcbp3 +``` + +--- + +## 3. สร้าง Directories และกำหนดสิทธิ์ + +```bash +# สร้าง directories สำหรับ volumes +mkdir -p /share/dms-data/uploads/temp +mkdir -p /share/dms-data/uploads/permanent +mkdir -p /share/dms-data/logs/backend +mkdir -p /share/np-dms/app + +# กำหนดสิทธิ์ให้ non-root user ใน container (UID 1001) +chown -R 1001:1001 /share/dms-data/uploads +chown -R 1001:1001 /share/dms-data/logs/backend +chmod -R 750 /share/dms-data/uploads +``` + +--- + +## 4. Deploy ผ่าน Container Station + +### 4.1 Copy docker-compose.yml + +คัดลอกไฟล์ `specs/08-infrastructure/docker-compose-app.yml` ไปยัง QNAP: + +```bash +# วางไฟล์ที่ path +/share/np-dms/app/docker-compose.yml +``` + +### 4.2 สร้าง Application ใน Container Station + +1. เปิด **Container Station** บน QNAP Web UI +2. ไปที่ **Applications** → **Create** +3. เลือก **Create Application** +4. ตั้งชื่อ Application: `lcbp3-app` +5. วาง (Paste) เนื้อหาจาก `docker-compose-app.yml` +6. แก้ไข Environment Variables ตามต้องการ (โดยเฉพาะ Secrets) +7. กด **Create** เพื่อ deploy + +> ⚠️ **สำคัญ:** ตรวจสอบ environment variables ก่อน deploy: +> - `DB_PASSWORD` — Password ของ MariaDB +> - `REDIS_PASSWORD` — Password ของ Redis +> - `JWT_SECRET` — Secret key สำหรับ JWT Tokens +> - `AUTH_SECRET` — Secret key สำหรับ NextAuth + +### 4.3 ตรวจสอบ Container Status + +ใน Container Station → Applications → `lcbp3-app`: +- ✅ `backend` — Status: **Running** (healthy) +- ✅ `frontend` — Status: **Running** (healthy) + +--- + +## 5. Verify Deployment + +### ตรวจสอบ Health + +```bash +# Backend health (จากภายใน Docker network) +docker exec frontend wget -qO- http://backend:3000/health + +# Frontend (ผ่าน NPM) +curl -I https://lcbp3.np-dms.work + +# Backend API (ผ่าน NPM) +curl -I https://backend.np-dms.work/api +``` + +### ตรวจสอบ Logs + +```bash +# ดู logs ใน Container Station UI +# หรือผ่าน CLI: +docker logs -f backend +docker logs -f frontend +``` + +--- + +## 6. Update / Re-deploy + +เมื่อต้องการ deploy version ใหม่: + +```bash +# 1. Build images ใหม่ (บน Dev Machine) +docker build -f backend/Dockerfile -t lcbp3-backend:latest . +docker build -f frontend/Dockerfile -t lcbp3-frontend:latest . + +# 2. Export & Transfer +docker save lcbp3-backend:latest | gzip > lcbp3-backend.tar.gz +docker save lcbp3-frontend:latest | gzip > lcbp3-frontend.tar.gz +scp lcbp3-*.tar.gz admin@192.168.10.8:/share/np-dms/app/ + +# 3. Load บน QNAP +ssh admin@192.168.10.8 +docker load < /share/np-dms/app/lcbp3-backend.tar.gz +docker load < /share/np-dms/app/lcbp3-frontend.tar.gz + +# 4. Restart ใน Container Station +# Applications → lcbp3-app → Restart +``` + +--- + +## 📦 Resource Summary + +| Service | Image | CPU Limit | Memory Limit | Port | +| :----------- | :---------------------- | :-------- | :----------- | :--- | +| **backend** | `lcbp3-backend:latest` | 2.0 | 1.5 GB | 3000 | +| **frontend** | `lcbp3-frontend:latest` | 2.0 | 2 GB | 3000 | + +> 📖 NPM Proxy Hosts ตั้งค่าเรียบร้อยแล้ว: +> - `lcbp3.np-dms.work` → `frontend:3000` +> - `backend.np-dms.work` → `backend:3000` diff --git a/specs/08-infrastructure/README.md b/specs/08-infrastructure/README.md index 820b3fd..e6da5db 100644 --- a/specs/08-infrastructure/README.md +++ b/specs/08-infrastructure/README.md @@ -181,13 +181,14 @@ graph TB ### Core Services (QNAP) -| ไฟล์ | Application | Services | Path บน QNAP | -| :--------------------------------------- | :---------- | :---------------------------------------- | :------------------------ | -| [MariaDB_setting.md](MariaDB_setting.md) | `lcbp3-db` | `mariadb`, `pma` | `/share/np-dms/mariadb/` | -| [NPM_setting.md](NPM_setting.md) | `lcbp3-npm` | `npm`, `landing` | `/share/np-dms/npm/` | -| [Service_setting.md](Service_setting.md) | `services` | `cache` (Redis), `search` (Elasticsearch) | `/share/np-dms/services/` | -| [Gitea_setting.md](Gitea_setting.md) | `git` | `gitea` | `/share/np-dms/gitea/` | -| [n8n_setting.md](n8n_setting.md) | `n8n` | `n8n` | `/share/np-dms/n8n/` | +| ไฟล์ | Application | Services | Path บน QNAP | +| :----------------------------------------------- | :---------- | :---------------------------------------- | :------------------------ | +| [MariaDB_setting.md](MariaDB_setting.md) | `lcbp3-db` | `mariadb`, `pma` | `/share/np-dms/mariadb/` | +| [NPM_setting.md](NPM_setting.md) | `lcbp3-npm` | `npm`, `landing` | `/share/np-dms/npm/` | +| [Service_setting.md](Service_setting.md) | `services` | `cache` (Redis), `search` (Elasticsearch) | `/share/np-dms/services/` | +| [Gitea_setting.md](Gitea_setting.md) | `git` | `gitea` | `/share/np-dms/gitea/` | +| [n8n_setting.md](n8n_setting.md) | `n8n` | `n8n` | `/share/np-dms/n8n/` | +| [docker-compose-app.yml](docker-compose-app.yml) | `lcbp3-app` | `backend` (NestJS), `frontend` (Next.js) | `/share/np-dms/app/` | ### Infrastructure Services (ASUSTOR) @@ -333,10 +334,11 @@ docker exec mariadb mysqldump -u root -p lcbp3 > backup.sql ## 📚 เอกสารเสริม -| ไฟล์ | คำอธิบาย | -| :------------------------------- | :------------------------------------------------ | -| [Git_command.md](Git_command.md) | คำสั่ง Git + Gitea Cheat Sheet | -| [lcbp3-db.md](lcbp3-db.md) | Docker Compose สำหรับ MariaDB (alternative version) | +| ไฟล์ | คำอธิบาย | +| :------------------------------------------- | :-------------------------------------------------------- | +| [Git_command.md](Git_command.md) | คำสั่ง Git + Gitea Cheat Sheet | +| [lcbp3-db.md](lcbp3-db.md) | Docker Compose สำหรับ MariaDB (alternative version) | +| [09_app_deployment.md](09_app_deployment.md) | ขั้นตอน Deploy Backend + Frontend บน QNAP Container Station | --- diff --git a/specs/08-infrastructure/docker-compose-app.yml b/specs/08-infrastructure/docker-compose-app.yml new file mode 100644 index 0000000..c6fb791 --- /dev/null +++ b/specs/08-infrastructure/docker-compose-app.yml @@ -0,0 +1,121 @@ +# File: /share/np-dms/app/docker-compose.yml +# DMS Container v1.7.0: Application Stack (Backend + Frontend) +# Application name: lcbp3-app +# ============================================================ +# ⚠️ ใช้งานร่วมกับ services อื่นที่รันอยู่แล้วบน QNAP: +# - mariadb (lcbp3-db) +# - cache (services) +# - search (services) +# - npm (lcbp3-npm) +# ============================================================ + +x-restart: &restart_policy + restart: unless-stopped + +x-logging: &default_logging + logging: + driver: 'json-file' + options: + max-size: '10m' + max-file: '5' + +networks: + lcbp3: + external: true + +services: + # ---------------------------------------------------------------- + # 1. Backend API (NestJS) + # Service Name: backend (ตามที่ NPM อ้างอิง → backend:3000) + # ---------------------------------------------------------------- + backend: + <<: [*restart_policy, *default_logging] + image: lcbp3-backend:latest + container_name: backend + stdin_open: true + tty: true + deploy: + resources: + limits: + cpus: '2.0' + memory: 1536M + reservations: + cpus: '0.5' + memory: 512M + environment: + TZ: 'Asia/Bangkok' + NODE_ENV: 'production' + # --- Database --- + DB_HOST: 'mariadb' + DB_PORT: '3306' + DB_NAME: 'lcbp3' + DB_USER: 'center' + DB_PASSWORD: 'Center#2025' + # --- Redis --- + REDIS_HOST: 'cache' + REDIS_PORT: '6379' + REDIS_PASSWORD: 'Center2025' + # --- Elasticsearch --- + ELASTICSEARCH_HOST: 'search' + ELASTICSEARCH_PORT: '9200' + # --- JWT --- + JWT_SECRET: 'eebc122aa65adde8c76c6a0847d9649b2b67a06db1504693e6c912e51499b76e' + JWT_EXPIRES_IN: '8h' + # --- Numbering --- + NUMBERING_LOCK_TIMEOUT: '5000' + NUMBERING_RESERVATION_TTL: '300' + # --- File Upload --- + UPLOAD_TEMP_DIR: '/app/uploads/temp' + UPLOAD_PERMANENT_DIR: '/app/uploads/permanent' + MAX_FILE_SIZE: '52428800' + networks: + - lcbp3 + volumes: + # Two-Phase Storage: จัดเก็บไฟล์นอก container + - '/share/dms-data/uploads/temp:/app/uploads/temp' + - '/share/dms-data/uploads/permanent:/app/uploads/permanent' + - '/share/dms-data/logs/backend:/app/logs' + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + + # ---------------------------------------------------------------- + # 2. Frontend Web App (Next.js) + # Service Name: frontend (ตามที่ NPM อ้างอิง → frontend:3000) + # ---------------------------------------------------------------- + frontend: + <<: [*restart_policy, *default_logging] + image: lcbp3-frontend:latest + container_name: frontend + stdin_open: true + tty: true + deploy: + resources: + limits: + cpus: '2.0' + memory: 2G + reservations: + cpus: '0.25' + memory: 512M + environment: + TZ: 'Asia/Bangkok' + NODE_ENV: 'production' + HOSTNAME: '0.0.0.0' + PORT: '3000' + # --- NextAuth --- + AUTH_SECRET: 'eebc122aa65adde8c76c6a0847d9649b2b67a06db1504693e6c912e51499b76e' + AUTH_URL: 'https://lcbp3.np-dms.work' + networks: + - lcbp3 + healthcheck: + test: ['CMD-SHELL', 'wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1'] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + depends_on: + backend: + condition: service_healthy