feat:CI/CD Gitea 워크플로우 등 누락 파일 반영

This commit is contained in:
2026-06-18 13:39:35 +09:00
parent 9d19d8283e
commit fa8dec1780
33 changed files with 4696 additions and 2011 deletions

View File

@@ -1,10 +1,10 @@
node_modules node_modules
dist dist
build build
.git .git
.gitignore .gitignore
.env .env
npm-debug.log npm-debug.log
uploads uploads
*.xlsx *.xlsx
*.log *.log

17
.env.example Normal file
View File

@@ -0,0 +1,17 @@
# Database Configuration
DB_HOST=172.16.8.151
DB_PORT=3306
DB_USER=itam_admin
DB_PASS=itam1234
DB_NAME=itam
# Application Configuration
NODE_ENV=development
PORT=3000
# Logging (optional)
LOG_LEVEL=info
# Security (for production)
# API_KEY=your_api_key_here
# JWT_SECRET=your_jwt_secret_here

7
.gitea/coverage.json Normal file
View File

@@ -0,0 +1,7 @@
{
"Path": "./backend/coverage.out",
"Thresholds": {
"baron-sso-backend/internal/handler": 10,
"baron-sso-backend/internal/service": 10
}
}

View File

@@ -0,0 +1,34 @@
name: ITAM Code Check
on:
push:
branches:
- Dockerizing
- main
pull_request:
workflow_dispatch:
jobs:
build-and-config-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Frontend TypeScript and Vite build
run: npm run build
- name: Validate test compose
run: docker compose -f docker-compose.test.yaml config
- name: Validate prod compose
run: docker compose -f docker-compose.prod.yaml config

View File

@@ -0,0 +1,69 @@
name: ITAM Docker Build Check
on:
push:
branches:
- Dockerizing
- main
paths:
- "Dockerfile.frontend.prod"
- "Dockerfile.backend.prod"
- "docker-compose.prod.yaml"
- "docker-compose.test.yaml"
- "docker/**"
- "src/**"
- "server.js"
- "package.json"
- "package-lock.json"
- "vite.config.ts"
- "index.html"
- "img/**"
- "public/**"
pull_request:
paths:
- "Dockerfile.frontend.prod"
- "Dockerfile.backend.prod"
- "docker-compose.prod.yaml"
- "docker-compose.test.yaml"
- "docker/**"
- "src/**"
- "server.js"
- "package.json"
- "package-lock.json"
- "vite.config.ts"
- "index.html"
- "img/**"
- "public/**"
workflow_dispatch:
jobs:
docker-build-check:
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
COMPOSE_DOCKER_CLI_BUILD: "1"
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Prepare CI env file
run: |
cat <<'EOF' > .env
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=itam_ci
DB_PASS=itam_ci_password
DB_NAME=itam
NODE_ENV=production
PORT=3000
LOG_LEVEL=info
EOF
- name: Build backend production image
run: docker build -f Dockerfile.backend.prod -t itam-backend:ci .
- name: Build frontend production image
run: docker build -f Dockerfile.frontend.prod -t itam-frontend:ci .
- name: Validate production compose with CI env
run: docker compose -f docker-compose.prod.yaml config

View File

@@ -0,0 +1,137 @@
name: ITAM Production Deploy
on:
workflow_dispatch:
inputs:
target_branch:
description: "Branch to deploy"
required: true
default: "Dockerizing"
type: string
jobs:
deploy-production:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup SSH agent
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
- name: Validate required production variables
env:
PROD_HOST: ${{ vars.PROD_HOST }}
PROD_USER: ${{ vars.PROD_USER }}
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
PROD_GIT_URL: ${{ vars.PROD_GIT_URL }}
DB_HOST: ${{ vars.PROD_DB_HOST }}
DB_PORT: ${{ vars.PROD_DB_PORT }}
DB_USER: ${{ vars.PROD_DB_USER }}
DB_PASS: ${{ secrets.PROD_DB_PASS }}
DB_NAME: ${{ vars.PROD_DB_NAME }}
run: |
set -euo pipefail
required_keys="PROD_HOST PROD_USER PROD_DEPLOY_PATH PROD_GIT_URL DB_HOST DB_PORT DB_USER DB_PASS DB_NAME"
for key in ${required_keys}; do
if [ -z "${!key:-}" ]; then
echo "::error::Missing required variable or secret: ${key}"
exit 1
fi
done
- name: Create production env file
env:
DB_HOST: ${{ vars.PROD_DB_HOST }}
DB_PORT: ${{ vars.PROD_DB_PORT }}
DB_USER: ${{ vars.PROD_DB_USER }}
DB_PASS: ${{ secrets.PROD_DB_PASS }}
DB_NAME: ${{ vars.PROD_DB_NAME }}
LOG_LEVEL: ${{ vars.PROD_LOG_LEVEL }}
run: |
set -euo pipefail
EFFECTIVE_LOG_LEVEL="${LOG_LEVEL:-info}"
cat > .env.deploy <<EOF
DB_HOST=${DB_HOST}
DB_PORT=${DB_PORT}
DB_USER=${DB_USER}
DB_PASS=${DB_PASS}
DB_NAME=${DB_NAME}
NODE_ENV=production
PORT=3000
LOG_LEVEL=${EFFECTIVE_LOG_LEVEL}
EOF
- name: Deploy to production host
env:
PROD_HOST: ${{ vars.PROD_HOST }}
PROD_USER: ${{ vars.PROD_USER }}
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
PROD_BACKUP_ROOT: ${{ vars.PROD_BACKUP_ROOT }}
PROD_GIT_URL: ${{ vars.PROD_GIT_URL }}
TARGET_BRANCH: ${{ github.event.inputs.target_branch }}
run: |
set -euo pipefail
ssh-keyscan -H "${PROD_HOST}" >> ~/.ssh/known_hosts
ssh "${PROD_USER}@${PROD_HOST}" "mkdir -p '${PROD_DEPLOY_PATH}'"
EFFECTIVE_BACKUP_ROOT="${PROD_BACKUP_ROOT:-${PROD_DEPLOY_PATH%/}_backups}"
ssh "${PROD_USER}@${PROD_HOST}" "export DEPLOY_PATH='${PROD_DEPLOY_PATH}' BACKUP_ROOT='${EFFECTIVE_BACKUP_ROOT}'; sh -eu -s" <<'REMOTE_BACKUP'
case "$BACKUP_ROOT" in
"$DEPLOY_PATH"|"$DEPLOY_PATH"/*)
echo "Backup path must be outside deploy path: $BACKUP_ROOT"
exit 1
;;
esac
if [ -d "$DEPLOY_PATH/.git" ]; then
mkdir -p "$BACKUP_ROOT"
cd "$DEPLOY_PATH"
if [ -f Makefile ] && [ -f scripts/backup.sh ]; then
make predeploy-backup BACKUP_ROOT="$BACKUP_ROOT"
else
echo "Skipping pre-deploy backup because current deployed revision does not contain Makefile backup tooling."
fi
else
echo "Skipping pre-deploy backup because no existing deployment was found."
fi
REMOTE_BACKUP
ssh "${PROD_USER}@${PROD_HOST}" "if [ ! -d '${PROD_DEPLOY_PATH}/.git' ]; then git clone '${PROD_GIT_URL}' '${PROD_DEPLOY_PATH}'; else cd '${PROD_DEPLOY_PATH}' && git remote set-url origin '${PROD_GIT_URL}'; fi"
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && git fetch origin '${TARGET_BRANCH}' && git checkout -B '${TARGET_BRANCH}' FETCH_HEAD && git reset --hard FETCH_HEAD && git clean -fd"
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && mkdir -p uploads logs/nginx"
scp .env.deploy "${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_PATH}/.env"
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && chmod 600 .env && docker compose -f docker-compose.prod.yaml config && docker compose -f docker-compose.prod.yaml up -d --build"
- name: Post-deploy status check
env:
PROD_HOST: ${{ vars.PROD_HOST }}
PROD_USER: ${{ vars.PROD_USER }}
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
run: |
set -euo pipefail
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && docker compose -f docker-compose.prod.yaml ps"
- name: Post-deploy smoke checks
env:
PROD_HOST: ${{ vars.PROD_HOST }}
PROD_USER: ${{ vars.PROD_USER }}
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
run: |
set -euo pipefail
ssh "${PROD_USER}@${PROD_HOST}" "curl -fsS http://localhost/health"
ssh "${PROD_USER}@${PROD_HOST}" "curl -fsS http://localhost/ > /dev/null"
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && docker compose -f docker-compose.prod.yaml exec -T backend curl -fsS http://localhost:3000/ready"
- name: Cleanup generated env file
if: ${{ always() }}
run: rm -f .env.deploy

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ dist/
*.log *.log
.DS_Store .DS_Store
Thumbs.db Thumbs.db
backups/

View File

@@ -1,12 +1,12 @@
FROM node:20-alpine FROM node:20-alpine
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm ci RUN npm ci
COPY . . COPY . .
EXPOSE 3000 EXPOSE 3000
CMD ["npm", "run", "server"] CMD ["npm", "run", "server"]

48
Dockerfile.backend.prod Normal file
View File

@@ -0,0 +1,48 @@
FROM node:20-alpine
LABEL maintainer="ITAM Team <devops@itam.local>"
# Set production environment
ENV NODE_ENV=production
WORKDIR /app
# Install curl for health checks and dumb-init for proper signal handling
RUN apk add --no-cache curl dumb-init
# Copy package files
COPY package*.json ./
# Install production dependencies only
RUN npm ci --only=production
# Copy application code
COPY server.js ./
COPY src ./src
# Create non-root user 'appuser' with UID 1001 (1000 already in use by node image)
RUN addgroup -g 1001 appuser && \
adduser -D -u 1001 -G appuser appuser
# Set ownership of application files to appuser
RUN chown -R appuser:appuser /app
# Create logs directory
RUN mkdir -p /app/logs && \
chown -R appuser:appuser /app/logs
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 3000
# Health check - backend should implement /health endpoint
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Use dumb-init from PATH to avoid distro-specific absolute path issues
ENTRYPOINT ["dumb-init", "--"]
# Run application
CMD ["npm", "run", "server"]

View File

@@ -1,12 +1,12 @@
FROM node:20-alpine FROM node:20-alpine
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm ci RUN npm ci
COPY . . COPY . .
EXPOSE 8080 EXPOSE 8080
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

62
Dockerfile.frontend.prod Normal file
View File

@@ -0,0 +1,62 @@
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY tsconfig*.json ./
COPY vite.config.ts ./
# Install all dependencies (including devDependencies for build)
RUN npm ci
# Copy source code
COPY src ./src
COPY public ./public
COPY index.html ./
# Build application
RUN npm run build
# Verify build output
RUN ls -la dist/ && echo "Build completed successfully"
# Stage 2: Runtime
FROM nginx:stable-alpine
LABEL maintainer="ITAM Team <devops@itam.local>"
# Install curl for health checks
RUN apk add --no-cache curl
WORKDIR /usr/share/nginx/html
# Copy built assets from builder
COPY --from=builder /app/dist .
# Copy static image assets referenced by literal /img/... paths
COPY img ./img
# Copy root-level logo asset referenced directly by index.html
COPY ["image 92.png", "./image 92.png"]
# Copy Nginx static file serving configuration (not reverse proxy)
COPY docker/frontend/default.conf /etc/nginx/conf.d/default.conf
# Create nginx runtime user and directories
RUN mkdir -p /var/log/nginx && \
chown -R nginx:nginx /usr/share/nginx/html && \
chown -R nginx:nginx /var/log/nginx && \
chown -R nginx:nginx /var/cache/nginx && \
chown -R nginx:nginx /etc/nginx/conf.d
# Expose port
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \
CMD curl -f http://localhost:80/ || exit 1
# Run nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,103 @@
# 자산관리 시스템 운영 오픈 보고 요약
## 1. 운영 방식
```mermaid
flowchart LR
A[개발 완료] --> B[개발 완료 검증]
B --> C[운영 서버 반영]
C --> D[최종 점검]
D --> E[서비스 오픈]
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
| 구분 | 운영 방향 |
| --- | --- |
| 반영 기준 | 개발 완료 및 검증 후 운영에 반영 |
| 반영 방식 | 운영 담당자 기준 통제 절차 |
| 데이터 연계 | 기존 외부 DB와 연동 |
| 안정성 확보 | 반영 전 점검, 반영 전 백업, 반영 후 확인 |
임의 반영 배제, 개발 완료 및 검증 결과 기준 운영 반영 방식.
---
## 2. 운영 배포 절차
```mermaid
flowchart TD
A[배포 준비 완료] --> B[사전 점검]
B --> C[백업 수행]
C --> D[운영 배포 실행]
D --> E[접속 및 기능 확인]
E --> F[오픈]
B1[환경 설정 확인]
B2[외부 DB 연결 확인]
B3[배포 파일 확인]
B --> B1
B --> B2
B --> B3
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
| 단계 | 목적 | 담당 |
| --- | --- | --- |
| 사전 점검 | 운영 반영에 필요한 조건 확인 | 개발팀 + 운영 |
| 백업 수행 | 문제 발생 시 신속 복구 대비 | 운영 |
| 운영 배포 실행 | 운영 서버에 검증 완료 결과 반영 | 운영 |
| 접속 및 기능 확인 | 주요 화면과 서비스 상태 확인 | 개발팀 + 운영 |
| 오픈 | 사용자 대상 서비스 오픈 | 운영 |
---
## 3. 예상 일정
```mermaid
gantt
title ITAM 운영 오픈 일정
dateFormat YYYY-MM-DD
axisFormat %m/%d
section 준비 단계
배포 구성 정리 및 환경 보완 :done, a1, 2026-06-18, 2026-06-24
section 검증 단계
테스트 배포 및 점검 :a2, 2026-06-25, 2026-06-27
오픈 전 최종 확인 :a3, 2026-06-28, 2026-06-30
section 오픈
운영 오픈 :milestone, a4, 2026-07-01, 1d
```
| 구간 | 일정 | 주요 내용 |
| --- | --- | --- |
| 준비 단계 | 6월 18일 ~ 6월 24일 | 운영 환경 정리 및 배포 준비 완료 |
| 검증 단계 | 6월 25일 ~ 6월 30일 | 테스트 배포, 접속 확인, 최종 보완 |
| 오픈 시점 | 7월 1일 | 운영 서비스 시작 |
6월 30일까지 준비 및 검증 완료, 7월 1일 오픈.
---
## 4. 보고 요약
| 항목 | 보고 내용 |
| --- | --- |
| 통제된 배포 | 개발 완료 및 검증 후 운영 반영 |
| 운영 안정성 | 점검과 백업 후 반영 |
| 데이터 연속성 | 기존 외부 DB와 연계 유지 |
| 오픈 목표 | 7월 1일 서비스 개시 |
1. 통제 절차 기반 운영 반영.
2. 오픈 전 점검 및 백업 기반 리스크 관리.
3. 6월 30일까지 준비 완료, 7월 1일 오픈.
---
## 5. 결론
자산관리 시스템, 6월 30일까지 운영 배포 준비 및 검증 완료, 7월 1일 오픈.

33
Makefile Normal file
View File

@@ -0,0 +1,33 @@
SHELL := /bin/sh
ENV_FILE ?= .env
BACKUP_ROOT ?= backups
RETENTION_DAYS ?= 14
BACKUP_SCRIPT := scripts/backup.sh
.PHONY: help db-dump files-backup full-backup predeploy-backup cleanup-backups
help:
@echo "Usage: make <target> [ENV_FILE=.env BACKUP_ROOT=backups RETENTION_DAYS=14]"
@echo ""
@echo "Targets:"
@echo " db-dump Create a gzip-compressed MySQL dump from .env settings"
@echo " files-backup Archive runtime files such as uploads/, map_config.json, and .env"
@echo " full-backup Run both db-dump and files-backup"
@echo " predeploy-backup Alias for the backup step executed before production deploy"
@echo " cleanup-backups Delete backup files older than RETENTION_DAYS"
db-dump:
@ENV_FILE="$(ENV_FILE)" BACKUP_ROOT="$(BACKUP_ROOT)" sh "$(BACKUP_SCRIPT)" db
files-backup:
@ENV_FILE="$(ENV_FILE)" BACKUP_ROOT="$(BACKUP_ROOT)" sh "$(BACKUP_SCRIPT)" files
full-backup:
@ENV_FILE="$(ENV_FILE)" BACKUP_ROOT="$(BACKUP_ROOT)" sh "$(BACKUP_SCRIPT)" full
predeploy-backup:
@ENV_FILE="$(ENV_FILE)" BACKUP_ROOT="$(BACKUP_ROOT)" sh "$(BACKUP_SCRIPT)" full
cleanup-backups:
@BACKUP_ROOT="$(BACKUP_ROOT)" RETENTION_DAYS="$(RETENTION_DAYS)" sh "$(BACKUP_SCRIPT)" cleanup

108
TEST_LOCAL.md Normal file
View File

@@ -0,0 +1,108 @@
# 로컬 Docker 테스트 가이드
## 준비 사항
- Docker & Docker Compose 설치 (WSL2 Ubuntu 권장)
- Node.js 20 (로컬 빌드 테스트 시)
## 테스트 단계
### 1. 파일 구조 확인
```bash
# docker-compose.test.yaml이 다음을 사용 확인
ls -la docker/nginx/default.conf
ls -la docker/frontend/default.conf
ls -la Dockerfile.backend.prod
ls -la Dockerfile.frontend.prod
ls -la package.json
ls -la src/
ls -la public/
ls -la index.html
```
### 2. Compose 파일 검증
```bash
docker compose -f docker-compose.test.yaml config
```
### 3. 이미지 빌드 테스트
```bash
# 개별 빌드 테스트
docker build -f Dockerfile.backend.prod -t itam-backend:test .
docker build -f Dockerfile.frontend.prod -t itam-frontend:test .
```
### 4. 컨테이너 시작
```bash
# WSL 터미널에서
cd /mnt/c/Users/user/Desktop/안건\ 파일/itam
# 또는 docker-compose.test.yaml을 사용하여 전체 스택 시작
docker compose -f docker-compose.test.yaml up --build
# 백그라운드에서 실행
docker compose -f docker-compose.test.yaml up -d --build
```
### 5. 컨테이너 상태 확인
```bash
docker compose -f docker-compose.test.yaml ps
docker logs itam-backend-test
docker logs itam-frontend-test
docker logs itam-nginx-test
```
### 6. 브라우저 테스트
```
http://localhost:8080/ # Frontend 접근
http://localhost:8080/api/ # Backend API 테스트
http://localhost:3000/health # 직접 backend health check
```
### 7. 정리
```bash
docker compose -f docker-compose.test.yaml down
```
## 예상되는 포트 매핑
- 8080: Nginx reverse proxy (frontend route to static + /api to backend)
- 3000: Backend Express API (내부, frontend와 nginx를 통해 접근)
- 80: Frontend Nginx (내부, 정적 파일 서빙)
## 테스트 체크리스트
- [ ] docker compose config 성공
- [ ] docker build 성공 (backend)
- [ ] docker build 성공 (frontend)
- [ ] 컨테이너 모두 실행 중 (ps 확인)
- [ ] http://localhost:8080 접근 가능 (frontend 페이지 로드)
- [ ] http://localhost:8080/api 응답 확인 (backend proxy 동작)
- [ ] backend health check 성공 (docker logs에서 /health 요청 확인)
## 문제 해결
### npm run build 실패
- Dockerfile.frontend.prod에서 tsc && vite build 실행
- package.json과 tsconfig.json 확인
### nginx 포트 이미 사용 중
```bash
docker ps
docker stop container_name
```
### DB 연결 실패
- 정상 동작 (NODE_ENV 때문에 /health는 200 반환)
- 실제 API 호출 시 DB 오류 예상
### 권한 문제
- logs/ 디렉토리 소유권 확인
- 필요 시 mkdir -p logs/nginx && chmod 777 logs

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

730
doc_readme3.md Normal file
View File

@@ -0,0 +1,730 @@
# ITAM Linux 운영 배포 가이드
## 1. 문서 목적
이 문서는 현재 ITAM 저장소를 기준으로 Linux 환경에서 운영 배포하는 방법을 정리한 가이드다.
핵심 전제는 아래와 같다.
1. 저장소 구조를 크게 재편하지 않는다.
2. 현재 워크스페이스 기준 파일 구조를 그대로 활용한다.
3. `docker-compose.test.yaml``docker-compose.prod.yaml` 모두 현재 저장소 루트 기준으로 동작한다.
4. DB는 Docker 내부가 아니라 외부 MySQL을 사용한다.
즉, 이 문서는 `/srv/itam` 같은 별도 운영 디렉터리 구조를 강제하는 문서가 아니라, 현재 저장소 구조를 기준으로 운영 전환하는 방법을 설명한다.
---
## 2. 현재 운영 관련 파일
현재 저장소에서 운영 전환과 직접 관련된 파일은 아래와 같다.
1. `docker-compose.yaml`
2. `docker-compose.test.yaml`
3. `docker-compose.prod.yaml`
4. `Dockerfile.frontend.prod`
5. `Dockerfile.backend.prod`
6. `docker/nginx/default.conf`
7. `docker/frontend/default.conf`
8. `.env.example`
9. `server.js`
각 파일의 역할은 아래와 같다.
1. `docker-compose.yaml`: 개발 재현용 구성
2. `docker-compose.test.yaml`: 운영형 Dockerfile과 reverse proxy 구조를 로컬에서 검증하는 테스트용 구성
3. `docker-compose.prod.yaml`: 현재 저장소 기준 운영용 구성
4. `Dockerfile.frontend.prod`: 프런트 정적 빌드 및 Nginx 서빙 이미지 정의
5. `Dockerfile.backend.prod`: 백엔드 API 운영 이미지 정의
6. `docker/nginx/default.conf`: reverse proxy 설정
7. `docker/frontend/default.conf`: frontend 컨테이너 내부 정적 파일 서빙 설정
8. `.env.example`: 운영/테스트 환경변수 템플릿
9. `server.js`: `/health`, `/ready` 엔드포인트 포함
---
## 3. 현재 기준 운영 아키텍처
현재 구조에서의 요청 흐름은 아래와 같다.
1. 외부 요청은 Nginx 컨테이너로 들어온다.
2. `/` 요청은 frontend 컨테이너로 전달된다.
3. `/api/``/uploads/` 요청은 backend 컨테이너로 전달된다.
4. backend는 외부 MySQL에 연결한다.
5. `uploads`, `map_config.json`, `.env`, 로그는 현재 저장소 기준 상대 경로를 사용한다.
```mermaid
flowchart LR
U["User Browser"] --> RP["Reverse Proxy Nginx 80"]
RP -->|root| FE["Frontend Container Nginx Static 80"]
RP -->|api| BE["Backend Container Node 3000"]
RP -->|uploads| BE
BE --> DB["External MySQL 3306"]
BE --> UP["./uploads"]
BE --> CFG["./map_config.json"]
BE --> ENV["./.env"]
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
현재 저장소 기준 파일/컨테이너 관계는 아래와 같다.
```mermaid
flowchart TB
subgraph REPO["Current Repository Root"]
ENV[".env"]
UP["uploads/"]
MAP["map_config.json"]
LOGS["logs/nginx/"]
CONF["docker/nginx/default.conf"]
end
subgraph CTR["Containers"]
NGINX["itam-nginx"]
FRONT["itam-frontend"]
BACK["itam-backend"]
end
DB["External MySQL"]
CONF --> NGINX
LOGS --> NGINX
ENV --> BACK
UP --> BACK
MAP --> BACK
NGINX --> FRONT
NGINX --> BACK
BACK --> DB
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
---
## 4. 개발용, 테스트용, 운영용 차이
### 4.1 `docker-compose.yaml`
용도:
1. 개발 재현
2. 소스 수정과 빠른 확인
3. Vite dev server 기반 실행
특징:
1. bind mount 중심
2. 프런트는 개발 서버 기반
3. 운영 배포보다는 개발 생산성에 초점
### 4.2 `docker-compose.test.yaml`
용도:
1. 운영형 Dockerfile 테스트
2. reverse proxy 동작 테스트
3. 로컬/WSL에서 8080 포트 검증
특징:
1. frontend/backend를 `build`로 생성
2. nginx는 8080 포트로 노출
3. 현재 저장소 상대 경로를 그대로 사용
### 4.3 `docker-compose.prod.yaml`
용도:
1. 현재 저장소 구조 기준 운영 배포
2. 운영 모드 환경변수와 health check 사용
3. 현재 구조를 바꾸지 않고 운영 전환
특징:
1. frontend/backend를 `build + image` 방식으로 정의
2. `.env`, `uploads`, `map_config.json`, `logs/nginx`를 현재 저장소 기준 상대 경로로 사용
3. nginx는 80 포트를 사용
4. backend는 `NODE_ENV=production`으로 실행
---
## 5. 운영 파일 구조 기준
현재 운영 배포는 저장소 루트를 기준으로 아래 구조를 전제로 한다.
```text
itam/
.env
.env.example
docker-compose.prod.yaml
docker-compose.test.yaml
Dockerfile.frontend.prod
Dockerfile.backend.prod
map_config.json
uploads/
logs/
nginx/
docker/
nginx/
default.conf
frontend/
default.conf
```
운영에서 실제로 중요한 경로는 아래 네 가지다.
1. `./.env`
2. `./uploads`
3. `./map_config.json`
4. `./logs/nginx`
즉, 현재 구조를 유지하려면 이 경로들이 항상 함께 관리되어야 한다.
---
## 6. 운영 환경변수 정책
현재 기준 운영 환경변수는 저장소 루트의 `.env`를 사용한다.
`.env.example` 기준 예시는 아래와 같다.
```env
DB_HOST=172.16.8.151
DB_PORT=3306
DB_USER=itam_admin
DB_PASS=change-this
DB_NAME=itam
NODE_ENV=production
PORT=3000
LOG_LEVEL=info
```
운영 원칙은 아래와 같다.
1. 실제 운영 비밀번호가 들어간 `.env`는 Git에 올리지 않는다.
2. `.env.example`은 템플릿으로만 사용한다.
3. 운영 서버에서는 `.env` 파일 권한을 제한한다.
4. 운영 DB 계정과 개발 DB 계정은 분리한다.
권장 권한 예시는 아래와 같다.
```bash
chmod 600 .env
```
---
## 7. Frontend 운영 이미지 기준
`Dockerfile.frontend.prod`는 multi-stage build를 사용한다.
구성은 아래와 같다.
1. builder 단계에서 `npm ci` 수행
2. `npm run build` 수행
3. 결과물을 Nginx 이미지에 복사
4. `docker/frontend/default.conf`로 정적 파일 서빙
운영 관점에서의 장점은 아래와 같다.
1. runtime 이미지에 build 결과물만 포함된다.
2. dev server 없이 정적 파일만 제공한다.
3. frontend 컨테이너 자체도 health check 가능하다.
---
## 8. Backend 운영 이미지 기준
`Dockerfile.backend.prod`는 아래 기준으로 작성되어 있다.
1. `NODE_ENV=production`
2. production dependency만 설치
3. `appuser` 비루트 사용자 사용
4. `dumb-init` 사용
5. `/health` health check 사용
backend 컨테이너는 아래 자원을 사용한다.
1. `./.env`
2. `./uploads`
3. `./map_config.json`
4. 외부 MySQL
---
## 9. Reverse Proxy 기준
`docker/nginx/default.conf`는 현재 아래처럼 동작한다.
1. `/` -> `frontend:80`
2. `/api/` -> `backend:3000`
3. `/uploads/` -> `backend:3000`
추가로 아래 설정을 포함한다.
1. 기본 보안 헤더
2. access/error 로그
3. gzip 설정
4. health endpoint
중요한 점은, 현재 운영 기준에서 외부 사용자가 직접 frontend 컨테이너에 붙는 것이 아니라 반드시 nginx를 통해 들어간다는 점이다.
---
## 10. 현재 기준 운영 배포 절차
### 10.1 사전 점검
아래 항목을 먼저 확인한다.
1. `.env` 파일 존재 여부
2. `uploads/` 디렉터리 존재 여부
3. `logs/nginx/` 디렉터리 존재 여부
4. `map_config.json` 존재 여부
5. 외부 DB 접근 가능 여부
예시:
```bash
ls -la .env
ls -la uploads
ls -la logs/nginx
ls -la map_config.json
```
### 10.2 Compose 검증
```bash
docker compose -f docker-compose.prod.yaml config
```
### 10.3 운영 기동
```bash
docker compose -f docker-compose.prod.yaml up -d --build
docker compose -f docker-compose.prod.yaml ps
```
### 10.4 운영 중지
```bash
docker compose -f docker-compose.prod.yaml down
```
### 10.5 운영/배포 분기 흐름
현재 운영 반영은 자동 push 배포가 아니라, Gitea에 올라간 커밋을 기준으로 수동 workflow를 실행하는 방식이다.
즉 아래 원칙으로 이해하면 된다.
1. 로컬 수정본을 서버에 직접 복사하지 않는다.
2. 반드시 Gitea에 올라간 커밋을 기준으로 배포한다.
3. 운영 반영은 `.gitea/workflows/itam_production_deploy.yml` 수동 실행으로 진행한다.
4. 실패 후 재배포는 실패 지점에 따라 수정 위치가 달라진다.
운영 반영은 크게 세 상황으로 나뉜다.
1. 최초 운영 서버 구축 후 첫 배포
2. 코드 수정 후 일반 재배포
3. 배포 실패 또는 검증 실패 후 재배포
```mermaid
flowchart TD
START["배포 필요 발생"] --> CASE{"어떤 상황인가?"}
CASE -->|초기 구축| INIT["초기 운영 배포 준비"]
CASE -->|수정 반영| CHANGE["수정 후 재배포 준비"]
CASE -->|실패 후 재시도| RETRY["실패 원인 분석 후 재배포 준비"]
INIT --> INIT1["운영 서버 Docker / compose 확인"]
INIT1 --> INIT2["Gitea Variables / Secrets 등록"]
INIT2 --> INIT3["map_config.json / uploads 초기 데이터 준비"]
INIT3 --> MANUAL["Gitea에서 수동 배포 workflow 실행"]
CHANGE --> CHANGE1["로컬 수정 및 테스트"]
CHANGE1 --> CHANGE2["Gitea 커밋 / push"]
CHANGE2 --> CHANGE3["Code Check / Docker Build Check 통과"]
CHANGE3 --> MANUAL
RETRY --> RETRY1{"어디서 실패했는가?"}
RETRY1 -->|코드 체크 실패| FIX1["코드 또는 설정 수정"]
RETRY1 -->|배포 단계 실패| FIX2["서버 / 변수 / 권한 / 네트워크 수정"]
RETRY1 -->|Smoke Check 실패| FIX3["앱 기동 상태 / 프록시 / DB 상태 수정"]
FIX1 --> CHANGE2
FIX2 --> MANUAL
FIX3 --> MANUAL
MANUAL --> BACKUP["기존 운영 상태가 있으면 배포 전 백업"]
BACKUP --> DEPLOY["운영 서버 반영 수행"]
DEPLOY --> RESULT{"최종 검증 통과?"}
RESULT -->|예| DONE["운영 반영 완료"]
RESULT -->|아니오| RETRY
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
### 10.6 최초 운영 배포 플로우
최초 배포에서는 코드보다 운영 환경 준비가 먼저다.
순서는 아래와 같다.
1. 운영 서버에 Docker Engine과 `docker compose`를 설치한다.
2. 운영 서버에서 Gitea 저장소에 접근 가능한 SSH 키를 준비한다.
3. Gitea repository Variables / Secrets를 등록한다.
4. `PROD_DEPLOY_PATH` 경로를 확정한다.
5. `PROD_BACKUP_ROOT` 경로를 `PROD_DEPLOY_PATH` 바깥으로 확정한다.
6. `map_config.json`, `uploads/` 초기 데이터를 준비한다.
7. Gitea에서 `itam_production_deploy.yml`을 수동 실행한다.
8. 배포 후 `docker compose ps`, `/health`, `/`, `/ready`를 확인한다.
즉 최초 배포는 아래 순서다.
```text
서버 준비 완료
-> Gitea 변수 / 시크릿 등록 완료
-> 백업 경로 확정 완료
-> 초기 데이터 준비 완료
-> 수동 배포 실행
```
### 10.7 수정 후 일반 재배포 플로우
일반적인 수정 반영은 아래 흐름이다.
1. 개발자가 로컬에서 코드 또는 설정을 수정한다.
2. 로컬에서 필요한 테스트를 수행한다.
3. 변경사항을 Gitea에 커밋 후 push 한다.
4. `itam_code_check.yml`이 빌드와 compose 문법을 검사한다.
5. `itam_docker_build_check.yml`이 운영용 이미지 빌드 가능 여부를 검사한다.
6. 두 검증이 통과하면 운영자가 Gitea에서 `itam_production_deploy.yml`을 수동 실행한다.
7. 기존 운영 상태가 있으면 배포 전 백업을 먼저 수행한다.
8. 운영 서버가 최신 커밋으로 동기화되고 컨테이너가 다시 올라온다.
9. smoke check 통과 여부를 확인한다.
```mermaid
flowchart LR
DEV["로컬 수정"] --> TEST["로컬 확인"]
TEST --> PUSH["커밋 / push"]
PUSH --> CODE["ITAM Code Check"]
CODE --> BUILD["ITAM Docker Build Check"]
BUILD --> GATE{"검증 통과?"}
GATE -->|예| RUN["Gitea에서 수동 배포 실행"]
GATE -->|아니오| FIX["로컬 수정 후 재커밋"]
FIX --> PUSH
RUN --> BACKUP["배포 전 백업"]
BACKUP --> PROD["운영 서버 배포"]
PROD --> SMOKE{"Smoke Check 통과?"}
SMOKE -->|예| OK["배포 완료"]
SMOKE -->|아니오| FIXDEPLOY["원인 수정 후 재배포"]
FIXDEPLOY --> RUN
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
### 10.8 수동 배포 workflow 내부 실행 순서
Gitea에서 `itam_production_deploy.yml`을 수동 실행하면 내부적으로는 아래 순서로 진행된다.
1. SSH agent를 설정한다.
2. 필수 Variables / Secrets가 모두 있는지 확인한다.
3. 운영용 `.env.deploy` 파일을 생성한다.
4. 운영 서버에 접속한다.
5. `PROD_DEPLOY_PATH`를 생성한다.
6. 기존 운영 상태가 있으면 `make predeploy-backup`을 실행한다.
7. 저장소를 clone 또는 fetch 한다.
8. 선택한 브랜치의 최신 커밋으로 checkout, reset, clean 한다.
9. `uploads`, `logs/nginx` 디렉토리를 준비한다.
10. `.env.deploy`를 서버의 `.env`로 복사한다.
11. `docker compose -f docker-compose.prod.yaml config`를 수행한다.
12. `docker compose -f docker-compose.prod.yaml up -d --build`를 수행한다.
13. `docker compose ps`를 확인한다.
14. `/health`, `/`, backend `/ready` smoke check를 수행한다.
```mermaid
flowchart TD
A["수동 배포 시작"] --> B["SSH agent 설정"]
B --> C["Variables / Secrets 검증"]
C --> D{"필수 값 누락 여부"}
D -->|예| E["즉시 실패 후 설정 보완"]
D -->|아니오| F[".env.deploy 생성"]
F --> G["운영 서버 SSH 접속"]
G --> H["배포 경로 생성"]
H --> I["기존 운영 상태가 있으면 make predeploy-backup"]
I --> J["git clone 또는 fetch"]
J --> K["지정 브랜치 checkout / reset / clean"]
K --> L["uploads / logs/nginx 준비"]
L --> M[".env 업로드 및 권한 설정"]
M --> N["compose config 검증"]
N --> O{"compose config 성공?"}
O -->|아니오| P["설정 수정 후 재실행"]
O -->|예| Q["compose up -d --build"]
Q --> R["docker compose ps 확인"]
R --> S["/health, /, /ready smoke check"]
S --> T{"smoke check 성공?"}
T -->|예| U["운영 배포 완료"]
T -->|아니오| V["원인 분석 후 재배포"]
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
### 10.9 실패 후 검증 및 재배포 플로우
실패가 났다고 해서 같은 방식으로 바로 다시 배포하면 안 된다.
실패 지점별 판단은 아래처럼 나눈다.
1. Code Check 실패: TypeScript, build, compose 문법 문제를 먼저 수정한다.
2. Docker Build Check 실패: Dockerfile, 정적 자산 복사, 운영 빌드 컨텍스트 문제를 수정한다.
3. Deploy 단계 실패: SSH, Gitea 변수, 서버 권한, 경로, 백업 경로, git 접근, Docker 권한을 수정한다.
4. Smoke Check 실패: Nginx 프록시, backend readiness, 외부 DB 연결, 앱 런타임 오류를 수정한다.
즉 재배포 전 판단 기준은 아래와 같다.
```text
CI 실패 -> 로컬 코드 / 설정 수정 후 재커밋
배포 실패 -> 서버 환경 또는 배포 설정 수정 후 수동 재실행
Smoke Check 실패 -> 앱 / 프록시 / DB 상태 수정 후 수동 재실행
```
운영 관점에서는 아래 순서를 지키는 것이 안전하다.
1. 실패 지점 확인
2. 원인 수정
3. 같은 실패가 다시 나는지 좁은 범위로 재검증
4. 그 다음에만 수동 배포 재실행
---
## 11. 테스트 배포 절차
운영형 구성을 먼저 검증하려면 아래처럼 진행한다.
```bash
docker compose -f docker-compose.test.yaml up -d --build
docker compose -f docker-compose.test.yaml ps
```
접속 기준은 아래와 같다.
1. `http://localhost:8080` -> nginx reverse proxy
2. `http://localhost:3000/health` -> backend health 확인
테스트용은 운영과 매우 유사하지만, 외부 노출 포트와 일부 실행 목적이 다르다.
---
## 12. Health Check 및 상태 판정
backend에는 아래 두 엔드포인트가 있다.
1. `/health`
2. `/ready`
판정 기준은 아래와 같다.
1. `/health = 200`, `/ready = 200`: 정상 서비스 가능 상태
2. `/health = 200`, `/ready = 503`: 프로세스는 살아 있으나 DB 또는 외부 의존성 미준비
확인 예시는 아래와 같다.
```bash
curl http://localhost:3000/health
curl http://localhost:3000/ready
```
---
## 13. 운영 점검 체크리스트
### 13.1 애플리케이션
1. `docker compose -f docker-compose.prod.yaml config` 통과 여부
2. frontend 이미지 빌드 성공 여부
3. backend 이미지 빌드 성공 여부
4. 모든 컨테이너가 `Up` 상태인지
5. 메인 화면이 정상 렌더링되는지
6. 데이터 조회가 정상 동작하는지
### 13.2 데이터 및 파일
1. `uploads/`에 쓰기 가능한지
2. `map_config.json`을 backend가 읽을 수 있는지
3. `.env` 파일 권한이 적절한지
4. `logs/nginx/`에 로그가 쌓이는지
### 13.3 네트워크
1. 외부 DB 접근 가능한지
2. nginx에서 backend upstream 연결이 되는지
3. `/api` 요청이 정상 응답하는지
---
## 14. 로그 및 장애 대응
기본 점검 명령은 아래와 같다.
```bash
docker compose -f docker-compose.prod.yaml ps
docker compose -f docker-compose.prod.yaml logs --tail=200 nginx
docker compose -f docker-compose.prod.yaml logs --tail=200 backend
docker compose -f docker-compose.prod.yaml logs --tail=200 frontend
```
장애 확인 순서는 아래가 좋다.
1. 컨테이너가 살아 있는지
2. nginx가 frontend/backend로 프록시하는지
3. backend가 DB에 붙는지
4. 업로드/설정 파일 권한 문제는 없는지
추가 확인 예시는 아래와 같다.
```bash
curl -I http://localhost/
curl http://localhost:3000/health
curl http://localhost:3000/ready
```
---
## 15. 백업 기준
현재 구조 기준 최소 백업 대상은 아래와 같다.
1. `.env`
2. `uploads/`
3. `map_config.json`
4. 외부 MySQL 데이터베이스
현재 저장소에는 운영 백업을 직접 실행할 수 있도록 `Makefile``scripts/backup.sh`가 추가되어 있다.
기본 원칙은 아래와 같다.
1. DB dump와 런타임 파일 백업을 분리해서 실행할 수 있어야 한다.
2. 기본 백업 산출물은 `backups/` 디렉터리에 쌓는다.
3. DB 접속 정보는 `.env`를 기준으로 읽는다.
4. 오래된 백업 파일은 보존 주기에 따라 정리한다.
백업 실행 흐름은 아래와 같다.
```mermaid
flowchart TD
START["운영 백업 실행"] --> TARGET{"무엇을 백업할 것인가?"}
TARGET -->|DB| DB["make db-dump"]
TARGET -->|운영 파일| FILES["make files-backup"]
TARGET -->|전체| FULL["make full-backup"]
DB --> OUT1["backups/db/*.sql.gz"]
FILES --> OUT2["backups/files/*.tar.gz"]
FULL --> OUT1
FULL --> OUT2
OUT1 --> CLEAN["make cleanup-backups"]
OUT2 --> CLEAN
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
### 15.1 Make 명령 기준
사용 가능한 기본 명령은 아래와 같다.
```bash
make db-dump
make files-backup
make full-backup
make cleanup-backups
```
각 명령의 역할은 아래와 같다.
1. `make db-dump`: `.env` 기준 MySQL dump를 `backups/db/``.sql.gz`로 저장
2. `make files-backup`: `.env`, `uploads/`, `map_config.json``backups/files/``.tar.gz`로 저장
3. `make full-backup`: DB dump와 파일 백업을 한 번에 수행
4. `make cleanup-backups`: 기본 14일이 지난 백업 파일 정리
### 15.2 실행 예시
가장 단순한 전체 백업 예시는 아래와 같다.
```bash
make full-backup
```
DB dump만 별도로 실행하려면 아래처럼 사용한다.
```bash
make db-dump
```
운영 파일만 묶으려면 아래처럼 사용한다.
```bash
make files-backup
```
보존 주기를 30일로 바꿔 정리하려면 아래처럼 사용한다.
```bash
make cleanup-backups RETENTION_DAYS=30
```
백업 경로를 별도 디스크나 마운트 경로로 바꾸려면 아래처럼 사용한다.
```bash
make full-backup BACKUP_ROOT=/opt/itam-backups
```
### 15.3 현재 스크립트가 실제로 백업하는 대상
현재 `scripts/backup.sh`는 아래 규칙으로 동작한다.
1. `.env` 파일에서 `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASS`, `DB_NAME`을 읽는다.
2. `mysqldump --single-transaction --quick --routines --triggers` 옵션으로 dump를 생성한다.
3. DB dump는 gzip 압축본으로 저장한다.
4. 파일 백업은 `.env`, `uploads/`, `map_config.json` 중 실제로 존재하는 항목만 묶는다.
5. 백업 정리는 `find ... -mtime` 기준으로 수행한다.
즉 현재 스크립트는 운영 서버 또는 백업 서버에서 바로 실행 가능한 최소 백업 도구로 보면 된다.
### 15.4 운영 사용 권장 방식
운영에서는 아래 방식이 가장 현실적이다.
1. 매일 새벽 cron 또는 systemd timer로 `make full-backup` 실행
2. 백업 완료 후 `make cleanup-backups` 실행
3. `backups/` 또는 별도 `BACKUP_ROOT` 경로를 NAS 또는 외부 백업 스토리지로 추가 복사
4. 최소 월 1회 restore 테스트 수행
가장 단순한 예시는 아래와 같다.
```bash
make full-backup BACKUP_ROOT=/opt/itam-backups
make cleanup-backups BACKUP_ROOT=/opt/itam-backups RETENTION_DAYS=30
```
DB 백업 자체는 여전히 DB 서버 정책과 함께 관리하는 것이 가장 안전하지만, 현재 저장소 기준 운영 자동화 진입점은 위 `make` 명령으로 통일해도 된다.
---
## 16. 롤백 기준
현재 구조에서는 가장 단순한 롤백 방식이 아래와 같다.
1. 이전 정상 커밋 또는 파일 상태 확보
2. 이미지 재빌드 또는 이전 이미지 재사용
3. `docker compose -f docker-compose.prod.yaml up -d --build` 재실행
4. `/health`, `/ready`, 메인 화면, 핵심 API 재검증
즉, 현재 구조에서는 별도 디렉터리 재배치보다 현재 저장소 상태 관리와 compose 재기동이 롤백의 중심이 된다.
---
## 17. 결론
현재 ITAM 저장소는 별도 `/srv/itam` 구조로 옮기지 않아도, 지금 파일 구조를 유지한 채 운영형 배포 흐름으로 전환할 수 있다.
정리하면 아래와 같다.
1. test와 prod 모두 현재 저장소 구조 기준으로 통일한다.
2. `.env`, `uploads`, `map_config.json`, `logs/nginx`를 운영 핵심 경로로 본다.
3. reverse proxy는 현재 `docker/nginx/default.conf`를 기준으로 운영한다.
4. backend는 production 모드, health check, 외부 DB 연결 구조를 유지한다.
5. 큰 구조 변경 없이도 운영 전환이 가능하다.
남은 작업은 TLS, 로그 로테이션, CI/CD, 보안 점검을 현재 구조 기준으로 계속 보강하는 것이다.

57
docker-compose.prod.yaml Normal file
View File

@@ -0,0 +1,57 @@
services:
backend:
image: itam-backend:prod
build:
context: .
dockerfile: Dockerfile.backend.prod
container_name: itam-backend
working_dir: /app
env_file:
- .env
environment:
NODE_ENV: production
PORT: 3000
volumes:
- ./uploads:/app/uploads
- ./map_config.json:/app/map_config.json:ro
expose:
- "3000"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
frontend:
image: itam-frontend:prod
build:
context: .
dockerfile: Dockerfile.frontend.prod
container_name: itam-frontend
expose:
- "80"
restart: unless-stopped
nginx:
image: nginx:stable-alpine
container_name: itam-nginx
ports:
- "80:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./logs/nginx:/var/log/nginx
depends_on:
backend:
condition: service_healthy
frontend:
condition: service_started
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s

62
docker-compose.test.yaml Normal file
View File

@@ -0,0 +1,62 @@
# Local testing compose file - uses relative paths and build contexts
# Usage: docker compose -f docker-compose.test.yaml up --build
services:
backend:
build:
context: .
dockerfile: Dockerfile.backend.prod
container_name: itam-backend-test
working_dir: /app
env_file:
- .env
environment:
NODE_ENV: development
PORT: 3000
DB_HOST: ${DB_HOST:-172.16.8.151}
DB_PORT: ${DB_PORT:-3306}
DB_USER: ${DB_USER:-root}
DB_PASS: ${DB_PASS:-}
DB_NAME: ${DB_NAME:-itam}
ports:
- "3000:3000"
volumes:
- ./uploads:/app/uploads
- ./map_config.json:/app/map_config.json:ro
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
frontend:
build:
context: .
dockerfile: Dockerfile.frontend.prod
container_name: itam-frontend-test
expose:
- "80"
restart: unless-stopped
nginx:
image: nginx:stable-alpine
container_name: itam-nginx-test
ports:
- "8080:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./logs/nginx:/var/log/nginx
depends_on:
backend:
condition: service_healthy
frontend:
condition: service_started
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s

View File

@@ -1,48 +1,48 @@
services: services:
backend: backend:
build: build:
context: . context: .
dockerfile: Dockerfile.backend dockerfile: Dockerfile.backend
container_name: itam-backend container_name: itam-backend
working_dir: /app working_dir: /app
env_file: env_file:
- .env - .env
environment: environment:
DB_HOST: ${DB_HOST} DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT} DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER} DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS} DB_PASS: ${DB_PASS}
DB_NAME: ${DB_NAME} DB_NAME: ${DB_NAME}
PORT: 3000 PORT: 3000
ports: ports:
- "3000:3000" - "3000:3000"
volumes: volumes:
- ./:/app - ./:/app
- backend_node_modules:/app/node_modules - backend_node_modules:/app/node_modules
- ./uploads:/app/uploads - ./uploads:/app/uploads
- ./map_config.json:/app/map_config.json - ./map_config.json:/app/map_config.json
command: npm run server command: npm run server
restart: unless-stopped restart: unless-stopped
frontend: frontend:
build: build:
context: . context: .
dockerfile: Dockerfile.frontend dockerfile: Dockerfile.frontend
container_name: itam-frontend container_name: itam-frontend
working_dir: /app working_dir: /app
depends_on: depends_on:
- backend - backend
environment: environment:
CHOKIDAR_USEPOLLING: "true" CHOKIDAR_USEPOLLING: "true"
VITE_DEV_PROXY_TARGET: http://backend:3000 VITE_DEV_PROXY_TARGET: http://backend:3000
ports: ports:
- "8080:8080" - "8080:8080"
volumes: volumes:
- ./:/app - ./:/app
- frontend_node_modules:/app/node_modules - frontend_node_modules:/app/node_modules
command: npm run dev -- --host 0.0.0.0 command: npm run dev -- --host 0.0.0.0
restart: unless-stopped restart: unless-stopped
volumes: volumes:
backend_node_modules: backend_node_modules:
frontend_node_modules: frontend_node_modules:

View File

@@ -0,0 +1,55 @@
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
# Logging
access_log /var/log/nginx/frontend-access.log main;
error_log /var/log/nginx/frontend-error.log warn;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss
application/json application/javascript;
gzip_min_length 1000;
# Serve static files with SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets (60 days)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|webp|woff|woff2|ttf|eot)$ {
expires 60d;
add_header Cache-Control "public, immutable";
}
# Don't cache HTML files
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Health check
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
# Deny access to sensitive files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}

View File

@@ -1,16 +1,16 @@
# MySQL init directory # MySQL init directory
This directory is kept as a legacy hook for file-based MySQL initialization. This directory is kept as a legacy hook for file-based MySQL initialization.
Current production path in this repository is not file-based import. Current production path in this repository is not file-based import.
The live Docker flow uses the `db-bootstrap` service in `docker-compose.yaml` to stream data from the external source DB into the internal `db` container. The live Docker flow uses the `db-bootstrap` service in `docker-compose.yaml` to stream data from the external source DB into the internal `db` container.
Use this directory only if you intentionally switch back to `docker-entrypoint-initdb.d` style initialization. Use this directory only if you intentionally switch back to `docker-entrypoint-initdb.d` style initialization.
If you do that, typical naming would be: If you do that, typical naming would be:
- `01_schema.sql` - `01_schema.sql`
- `02_seed.sql` - `02_seed.sql`
- or a single `01_itam_dump.sql` - or a single `01_itam_dump.sql`
Remember that files in this directory are executed automatically by the MySQL container only on the first initialization of the data volume. Remember that files in this directory are executed automatically by the MySQL container only on the first initialization of the data volume.

101
docker/nginx/default.conf Normal file
View File

@@ -0,0 +1,101 @@
upstream backend {
server backend:3000;
}
upstream frontend {
server frontend:80;
}
server {
listen 80;
listen [::]:80;
server_name _;
# Client upload size limit (adjust as needed)
client_max_body_size 100M;
# Logging
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss
application/json application/javascript;
gzip_min_length 1000;
# Forward all app requests to the frontend container
location / {
proxy_pass http://frontend;
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;
proxy_http_version 1.1;
}
# API proxy to backend
location /api/ {
proxy_pass http://backend/api/;
# Preserve original request information
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;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Port $server_port;
# Connection settings
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffering settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
# Uploads proxy to backend
location /uploads/ {
proxy_pass http://backend/uploads/;
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;
# Cache uploads
expires 30d;
add_header Cache-Control "public";
}
# Health check endpoint (for monitoring)
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
# Deny access to sensitive files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
}

View File

@@ -1,330 +1,330 @@
# ITAM 도커라이징 작업 태스크 정리 # ITAM 도커라이징 작업 태스크 정리
## 1. 문서 목적 ## 1. 문서 목적
이 문서는 ITAM 자산관리 시스템의 도커라이징 작업을 실제 실행 단위로 쪼개서 정리한 태스크 문서다. 이 문서는 ITAM 자산관리 시스템의 도커라이징 작업을 실제 실행 단위로 쪼개서 정리한 태스크 문서다.
이 문서의 목표는 아래와 같다. 이 문서의 목표는 아래와 같다.
1. 내일까지 보여줄 시연 범위를 기준으로 우선순위를 정한다. 1. 내일까지 보여줄 시연 범위를 기준으로 우선순위를 정한다.
2. 시연용 작업과 운영형 전환 작업을 분리한다. 2. 시연용 작업과 운영형 전환 작업을 분리한다.
3. 개발 담당자가 바로 실행할 수 있는 체크리스트를 제공한다. 3. 개발 담당자가 바로 실행할 수 있는 체크리스트를 제공한다.
관련 배경과 구조 분석은 [doc_readme.md](c:/Users/user/Desktop/안건%20파일/itam/doc_readme.md) 문서를 기준으로 한다. 관련 배경과 구조 분석은 [doc_readme.md](c:/Users/user/Desktop/안건%20파일/itam/doc_readme.md) 문서를 기준으로 한다.
현재 구현/검증 상태: 현재 구현/검증 상태:
- `Dockerfile.frontend` 생성 완료 - `Dockerfile.frontend` 생성 완료
- `Dockerfile.backend` 생성 완료 - `Dockerfile.backend` 생성 완료
- `docker-compose.yaml` 생성 완료 - `docker-compose.yaml` 생성 완료
- `.dockerignore` 생성 완료 - `.dockerignore` 생성 완료
- WSL2 Ubuntu에서 `docker compose up --build -d` 검증 완료 - WSL2 Ubuntu에서 `docker compose up --build -d` 검증 완료
- frontend 8080 응답 확인 완료 - frontend 8080 응답 확인 완료
- backend `/api/assets/master` 응답 확인 완료 - backend `/api/assets/master` 응답 확인 완료
- 현재 DB는 external MySQL 기준이며, DB 컨테이너 추가 작업은 다음 단계로 남아 있음 - 현재 DB는 external MySQL 기준이며, DB 컨테이너 추가 작업은 다음 단계로 남아 있음
## 2. 이번 작업의 최우선 목표 ## 2. 이번 작업의 최우선 목표
이번 도커라이징의 1차 목표는 "운영 배포 완료"가 아니라 아래 상태를 재현하는 것이다. 이번 도커라이징의 1차 목표는 "운영 배포 완료"가 아니라 아래 상태를 재현하는 것이다.
1. frontend 컨테이너가 정상 기동한다. 1. frontend 컨테이너가 정상 기동한다.
2. backend 컨테이너가 정상 기동한다. 2. backend 컨테이너가 정상 기동한다.
3. backend가 기존 외부 MySQL 또는 MySQL 컨테이너에 정상 연결된다. 3. backend가 기존 외부 MySQL 또는 MySQL 컨테이너에 정상 연결된다.
4. 브라우저에서 화면이 열린다. 4. 브라우저에서 화면이 열린다.
5. 핵심 API 호출이 정상 동작한다. 5. 핵심 API 호출이 정상 동작한다.
6. 업로드 저장 경로가 유지된다. 6. 업로드 저장 경로가 유지된다.
7. 필요 시 DB까지 함께 포함된 재현 가능한 스택을 제공한다. 7. 필요 시 DB까지 함께 포함된 재현 가능한 스택을 제공한다.
## 3. 작업 범위 구분 ## 3. 작업 범위 구분
### 3.1 이번 시연 범위에 포함 ### 3.1 이번 시연 범위에 포함
- Dockerfile.frontend 초안 작성 - Dockerfile.frontend 초안 작성
- Dockerfile.backend 초안 작성 - Dockerfile.backend 초안 작성
- docker-compose.yaml 작성 - docker-compose.yaml 작성
- `.dockerignore` 작성 - `.dockerignore` 작성
- MySQL 컨테이너 추가 설계 - MySQL 컨테이너 추가 설계
- 초기 SQL dump 또는 init SQL 적재 방식 정의 - 초기 SQL dump 또는 init SQL 적재 방식 정의
- `uploads` 볼륨 처리 - `uploads` 볼륨 처리
- `map_config.json` 영속성 처리 방식 반영 - `map_config.json` 영속성 처리 방식 반영
- 컨테이너 기동 및 접속 확인 - 컨테이너 기동 및 접속 확인
- 핵심 API 및 화면 확인 - 핵심 API 및 화면 확인
### 3.2 이번 시연 범위에서 제외 ### 3.2 이번 시연 범위에서 제외
- DB 전체 마이그레이션 자동화 - DB 전체 마이그레이션 자동화
- nginx 기반 운영 배포 구조 - nginx 기반 운영 배포 구조
- 단일 이미지 운영 구조 전환 - 단일 이미지 운영 구조 전환
- CI/CD 연계 - CI/CD 연계
## 4. 선행 확인 태스크 ## 4. 선행 확인 태스크
아래 태스크는 실제 Docker 파일 작성 전에 먼저 확인해야 한다. 아래 태스크는 실제 Docker 파일 작성 전에 먼저 확인해야 한다.
### Task 1. 외부 MySQL 접근 가능 여부 확인 ### Task 1. 외부 MySQL 접근 가능 여부 확인
- 목적: 컨테이너에서 외부 DB 접속이 가능한지 확인 - 목적: 컨테이너에서 외부 DB 접속이 가능한지 확인
- 확인 항목: - 확인 항목:
- DB_HOST 접근 가능 여부 - DB_HOST 접근 가능 여부
- DB_PORT 3306 접속 가능 여부 - DB_PORT 3306 접속 가능 여부
- 계정 권한 정상 여부 - 계정 권한 정상 여부
- 완료 기준: - 완료 기준:
- backend 컨테이너 기준 DB 연결 에러가 발생하지 않음 - backend 컨테이너 기준 DB 연결 에러가 발생하지 않음
### Task 2. 기준 스키마 상태 확인 ### Task 2. 기준 스키마 상태 확인
- 목적: 현재 앱이 요구하는 테이블 구조가 실제 DB와 맞는지 확인 - 목적: 현재 앱이 요구하는 테이블 구조가 실제 DB와 맞는지 확인
- 확인 항목: - 확인 항목:
- `asset_core` - `asset_core`
- `asset_spec` - `asset_spec`
- `asset_location` - `asset_location`
- `asset_remote` - `asset_remote`
- `asset_history` - `asset_history`
- `hardware_components_master` - `hardware_components_master`
- `job_spec_standards` - `job_spec_standards`
- 완료 기준: - 완료 기준:
- `/api/assets/master` 호출 시 쿼리 에러가 발생하지 않음 - `/api/assets/master` 호출 시 쿼리 에러가 발생하지 않음
### Task 3. 파일 영속성 대상 확인 ### Task 3. 파일 영속성 대상 확인
- 목적: 컨테이너 재시작 이후에도 유지되어야 할 파일/폴더 식별 - 목적: 컨테이너 재시작 이후에도 유지되어야 할 파일/폴더 식별
- 대상: - 대상:
- `uploads` - `uploads`
- `map_config.json` - `map_config.json`
- 완료 기준: - 완료 기준:
- 볼륨 설계 대상이 명확하게 문서화됨 - 볼륨 설계 대상이 명확하게 문서화됨
### Task 4. DB 기준 데이터 소스 확정 ### Task 4. DB 기준 데이터 소스 확정
- 목적: MySQL 컨테이너 최초 기동 시 어떤 데이터로 초기화할지 결정 - 목적: MySQL 컨테이너 최초 기동 시 어떤 데이터로 초기화할지 결정
- 선택지: - 선택지:
- 기존 사내 DB에서 추출한 SQL dump 사용 - 기존 사내 DB에서 추출한 SQL dump 사용
- 정리된 스키마 SQL + seed SQL 사용 - 정리된 스키마 SQL + seed SQL 사용
- 수동 import 절차 사용 - 수동 import 절차 사용
- 완료 기준: - 완료 기준:
- `docker/mysql/init` 기준 적재 전략 또는 수동 복원 절차가 확정됨 - `docker/mysql/init` 기준 적재 전략 또는 수동 복원 절차가 확정됨
## 5. 시연용 도커라이징 태스크 ## 5. 시연용 도커라이징 태스크
### Task 5. 프런트 Dockerfile 작성 ### Task 5. 프런트 Dockerfile 작성
- 목적: Vite 개발 서버를 컨테이너에서 구동 - 목적: Vite 개발 서버를 컨테이너에서 구동
- 작업 내용: - 작업 내용:
- Node 20 계열 이미지 사용 - Node 20 계열 이미지 사용
- `package*.json` 복사 후 `npm install` - `package*.json` 복사 후 `npm install`
- 8080 포트 노출 - 8080 포트 노출
- `npm run dev -- --host 0.0.0.0` 실행 - `npm run dev -- --host 0.0.0.0` 실행
- 산출물: - 산출물:
- `Dockerfile.frontend` - `Dockerfile.frontend`
- 완료 기준: - 완료 기준:
- 컨테이너에서 8080 포트가 정상 listen 상태가 됨 - 컨테이너에서 8080 포트가 정상 listen 상태가 됨
### Task 6. 백엔드 Dockerfile 작성 ### Task 6. 백엔드 Dockerfile 작성
- 목적: Express API 서버를 컨테이너에서 구동 - 목적: Express API 서버를 컨테이너에서 구동
- 작업 내용: - 작업 내용:
- Node 20 계열 이미지 사용 - Node 20 계열 이미지 사용
- `package*.json` 복사 후 `npm install` - `package*.json` 복사 후 `npm install`
- 3000 포트 노출 - 3000 포트 노출
- `npm run server` 실행 - `npm run server` 실행
- 산출물: - 산출물:
- `Dockerfile.backend` - `Dockerfile.backend`
- 완료 기준: - 완료 기준:
- 컨테이너에서 3000 포트가 정상 listen 상태가 됨 - 컨테이너에서 3000 포트가 정상 listen 상태가 됨
### Task 7. MySQL Docker 구성 추가 ### Task 7. MySQL Docker 구성 추가
- 목적: DB까지 포함한 재현 가능한 스택 구성 - 목적: DB까지 포함한 재현 가능한 스택 구성
- 작업 내용: - 작업 내용:
- `mysql:8.0` 서비스 정의 - `mysql:8.0` 서비스 정의
- `MYSQL_DATABASE`, `MYSQL_USER`, `MYSQL_PASSWORD` 설정 - `MYSQL_DATABASE`, `MYSQL_USER`, `MYSQL_PASSWORD` 설정
- utf8mb4 문자셋 옵션 반영 - utf8mb4 문자셋 옵션 반영
- MySQL 데이터 volume 연결 - MySQL 데이터 volume 연결
- 초기 SQL 적재용 `docker/mysql/init` 디렉터리 설계 - 초기 SQL 적재용 `docker/mysql/init` 디렉터리 설계
- 산출물: - 산출물:
- `docker-compose.yaml``db` 서비스 또는 별도 DB compose 확장안 - `docker-compose.yaml``db` 서비스 또는 별도 DB compose 확장안
- 완료 기준: - 완료 기준:
- MySQL 컨테이너가 정상 기동하고 3306 포트에서 응답 가능 - MySQL 컨테이너가 정상 기동하고 3306 포트에서 응답 가능
### Task 8. backend DB 연결 전환 ### Task 8. backend DB 연결 전환
- 목적: backend가 external MySQL 대신 DB 컨테이너를 바라보도록 변경 - 목적: backend가 external MySQL 대신 DB 컨테이너를 바라보도록 변경
- 작업 내용: - 작업 내용:
- `DB_HOST``db`로 전환 - `DB_HOST``db`로 전환
- 필요 시 `.env.docker` 또는 compose 내부 환경변수 사용 - 필요 시 `.env.docker` 또는 compose 내부 환경변수 사용
- backend `depends_on`에 db 추가 - backend `depends_on`에 db 추가
- 산출물: - 산출물:
- DB 컨테이너용 backend 환경 정의 - DB 컨테이너용 backend 환경 정의
- 완료 기준: - 완료 기준:
- backend 로그에서 DB 연결 성공 확인 - backend 로그에서 DB 연결 성공 확인
### Task 9. docker-compose.yaml 확장 ### Task 9. docker-compose.yaml 확장
- 목적: frontend/backend를 함께 기동 - 목적: frontend/backend를 함께 기동
- 작업 내용: - 작업 내용:
- frontend 서비스 정의 - frontend 서비스 정의
- backend 서비스 정의 - backend 서비스 정의
- db 서비스 정의 - db 서비스 정의
- 포트 매핑 추가 - 포트 매핑 추가
- `.env` 또는 docker 전용 환경변수 연결 - `.env` 또는 docker 전용 환경변수 연결
- MySQL 데이터 볼륨 연결 - MySQL 데이터 볼륨 연결
- `uploads` 볼륨 연결 - `uploads` 볼륨 연결
- `map_config.json` 처리 방식 반영 - `map_config.json` 처리 방식 반영
- 산출물: - 산출물:
- `docker-compose.yaml` - `docker-compose.yaml`
- 완료 기준: - 완료 기준:
- `docker compose up --build` 한 번으로 세 서비스가 모두 올라옴 - `docker compose up --build` 한 번으로 세 서비스가 모두 올라옴
### Task 10. `.dockerignore` 작성 ### Task 10. `.dockerignore` 작성
- 목적: 불필요한 빌드 컨텍스트 제외 - 목적: 불필요한 빌드 컨텍스트 제외
- 제외 권장 항목: - 제외 권장 항목:
- `node_modules` - `node_modules`
- `dist` - `dist`
- `build` - `build`
- `.git` - `.git`
- `uploads` - `uploads`
- `*.xlsx` - `*.xlsx`
- 산출물: - 산출물:
- `.dockerignore` - `.dockerignore`
- 완료 기준: - 완료 기준:
- 이미지 빌드 컨텍스트가 과도하게 커지지 않음 - 이미지 빌드 컨텍스트가 과도하게 커지지 않음
## 6. 시연 검증 태스크 ## 6. 시연 검증 태스크
### Task 11. WSL 컨테이너 기동 검증 ### Task 11. WSL 컨테이너 기동 검증
- 실행 명령: - 실행 명령:
```bash ```bash
powershell -ExecutionPolicy Bypass -File .\start_docker_wsl.ps1 powershell -ExecutionPolicy Bypass -File .\start_docker_wsl.ps1
``` ```
- 확인 항목: - 확인 항목:
- frontend 로그 에러 여부 - frontend 로그 에러 여부
- backend 로그 에러 여부 - backend 로그 에러 여부
- db 로그 에러 여부 - db 로그 에러 여부
- backend와 db 연결 성공 여부 - backend와 db 연결 성공 여부
- 완료 기준: - 완료 기준:
- 세 컨테이너 모두 종료 없이 유지됨 - 세 컨테이너 모두 종료 없이 유지됨
### Task 12. 웹 접속 검증 ### Task 12. 웹 접속 검증
- 확인 항목: - 확인 항목:
- `http://localhost:8080` 접속 가능 여부 - `http://localhost:8080` 접속 가능 여부
- 첫 화면 로딩 여부 - 첫 화면 로딩 여부
- 콘솔 에러 여부 - 콘솔 에러 여부
- 완료 기준: - 완료 기준:
- 브라우저에서 초기 화면이 정상 표시됨 - 브라우저에서 초기 화면이 정상 표시됨
### Task 13. API 검증 ### Task 13. API 검증
- 확인 항목: - 확인 항목:
- `http://localhost:3000/api/assets/master` - `http://localhost:3000/api/assets/master`
- 프런트에서 `/api/assets/master` 호출 정상 여부 - 프런트에서 `/api/assets/master` 호출 정상 여부
- 완료 기준: - 완료 기준:
- 200 응답 또는 정상 데이터 응답 확인 - 200 응답 또는 정상 데이터 응답 확인
### Task 14. DB 초기 데이터 검증 ### Task 14. DB 초기 데이터 검증
- 확인 항목: - 확인 항목:
- MySQL 컨테이너 내부에 목표 DB가 생성되었는지 - MySQL 컨테이너 내부에 목표 DB가 생성되었는지
- 기준 테이블이 존재하는지 - 기준 테이블이 존재하는지
- 샘플 데이터 또는 실데이터가 적재되었는지 - 샘플 데이터 또는 실데이터가 적재되었는지
- 완료 기준: - 완료 기준:
- backend가 기대하는 최소 테이블과 데이터가 실제로 조회됨 - backend가 기대하는 최소 테이블과 데이터가 실제로 조회됨
### Task 15. 업로드/파일 저장 검증 ### Task 15. 업로드/파일 저장 검증
- 확인 항목: - 확인 항목:
- `/api/upload` 호출 정상 여부 - `/api/upload` 호출 정상 여부
- 업로드 파일이 `uploads`에 실제 저장되는지 - 업로드 파일이 `uploads`에 실제 저장되는지
- `map_config.json` 수정 내용이 유지되는지 - `map_config.json` 수정 내용이 유지되는지
- 완료 기준: - 완료 기준:
- 컨테이너 재시작 후에도 저장 데이터가 유지됨 - 컨테이너 재시작 후에도 저장 데이터가 유지됨
## 7. 시연 후 후속 태스크 ## 7. 시연 후 후속 태스크
### Task 16. 운영형 프런트 배포 구조 전환 ### Task 16. 운영형 프런트 배포 구조 전환
- 목표: Vite dev server 대신 정적 빌드 기반 구조로 전환 - 목표: Vite dev server 대신 정적 빌드 기반 구조로 전환
- 후보: - 후보:
- nginx 정적 서빙 - nginx 정적 서빙
- Express 정적 서빙 - Express 정적 서빙
### Task 17. DB 초기화/마이그레이션 전략 통합 ### Task 17. DB 초기화/마이그레이션 전략 통합
- 목표: 기준 스키마와 실행 순서를 단일 정책으로 통일 - 목표: 기준 스키마와 실행 순서를 단일 정책으로 통일
- 필요 작업: - 필요 작업:
- 기준 스키마 선정 - 기준 스키마 선정
- 초기화 스크립트 확정 - 초기화 스크립트 확정
- 마이그레이션 순서 정의 - 마이그레이션 순서 정의
### Task 18. `.env.example` 및 배포 환경 분리 ### Task 18. `.env.example` 및 배포 환경 분리
- 목표: 민감정보를 저장소에서 분리하고 배포별 설정 체계화 - 목표: 민감정보를 저장소에서 분리하고 배포별 설정 체계화
### Task 19. 운영 볼륨 및 백업 전략 정리 ### Task 19. 운영 볼륨 및 백업 전략 정리
- 목표: 업로드 파일과 설정 파일, MySQL 데이터의 장기 보존 정책 정리 - 목표: 업로드 파일과 설정 파일, MySQL 데이터의 장기 보존 정책 정리
### Task 20. DB 백업/복원 절차 문서화 ### Task 20. DB 백업/복원 절차 문서화
- 목표: 컨테이너 DB를 기준으로 dump/restore 절차를 문서화 - 목표: 컨테이너 DB를 기준으로 dump/restore 절차를 문서화
## 8. 우선순위 정리 ## 8. 우선순위 정리
### P0: 내일까지 반드시 필요한 작업 ### P0: 내일까지 반드시 필요한 작업
1. Task 1. 외부 MySQL 접근 가능 여부 확인 1. Task 1. 외부 MySQL 접근 가능 여부 확인
2. Task 2. 기준 스키마 상태 확인 2. Task 2. 기준 스키마 상태 확인
3. Task 4. DB 기준 데이터 소스 확정 3. Task 4. DB 기준 데이터 소스 확정
4. Task 7. MySQL Docker 구성 추가 4. Task 7. MySQL Docker 구성 추가
5. Task 8. backend DB 연결 전환 5. Task 8. backend DB 연결 전환
6. Task 9. docker-compose.yaml 확장 6. Task 9. docker-compose.yaml 확장
7. Task 11. WSL 컨테이너 기동 검증 7. Task 11. WSL 컨테이너 기동 검증
8. Task 12. 웹 접속 검증 8. Task 12. 웹 접속 검증
9. Task 13. API 검증 9. Task 13. API 검증
10. Task 14. DB 초기 데이터 검증 10. Task 14. DB 초기 데이터 검증
### P1: 시연 안정화를 위해 권장되는 작업 ### P1: 시연 안정화를 위해 권장되는 작업
1. Task 3. 파일 영속성 대상 확인 1. Task 3. 파일 영속성 대상 확인
2. Task 10. `.dockerignore` 작성 2. Task 10. `.dockerignore` 작성
3. Task 15. 업로드/파일 저장 검증 3. Task 15. 업로드/파일 저장 검증
### P2: 시연 이후 진행할 작업 ### P2: 시연 이후 진행할 작업
1. Task 16. 운영형 프런트 배포 구조 전환 1. Task 16. 운영형 프런트 배포 구조 전환
2. Task 17. DB 초기화/마이그레이션 전략 통합 2. Task 17. DB 초기화/마이그레이션 전략 통합
3. Task 18. `.env.example` 및 배포 환경 분리 3. Task 18. `.env.example` 및 배포 환경 분리
4. Task 19. 운영 볼륨 및 백업 전략 정리 4. Task 19. 운영 볼륨 및 백업 전략 정리
5. Task 20. DB 백업/복원 절차 문서화 5. Task 20. DB 백업/복원 절차 문서화
## 9. 개발자용 최종 작업 순서 제안 ## 9. 개발자용 최종 작업 순서 제안
개발 담당자에게는 아래 순서로 진행하라고 전달하면 된다. 개발 담당자에게는 아래 순서로 진행하라고 전달하면 된다.
1. 외부 DB 연결 가능 여부부터 확인 1. 외부 DB 연결 가능 여부부터 확인
2. 현재 DB 스키마가 앱 요구사항과 맞는지 확인 2. 현재 DB 스키마가 앱 요구사항과 맞는지 확인
3. DB 기준 dump 또는 init SQL 확보 3. DB 기준 dump 또는 init SQL 확보
4. MySQL 컨테이너 구성 추가 4. MySQL 컨테이너 구성 추가
5. backend의 DB 연결 대상을 `db`로 전환 5. backend의 DB 연결 대상을 `db`로 전환
6. WSL에서 `docker compose config` 확인 6. WSL에서 `docker compose config` 확인
7. WSL에서 컨테이너 기동 테스트 7. WSL에서 컨테이너 기동 테스트
8. 웹 접속 및 API 확인 8. 웹 접속 및 API 확인
9. 업로드 및 파일 영속성 확인 9. 업로드 및 파일 영속성 확인
10. 시연 완료 후 운영형 구조로 분리 작업 진행 10. 시연 완료 후 운영형 구조로 분리 작업 진행
## 10. 완료 판단 기준 ## 10. 완료 판단 기준
이번 도커라이징 1차 작업은 아래 조건을 만족하면 완료로 본다. 이번 도커라이징 1차 작업은 아래 조건을 만족하면 완료로 본다.
1. `docker compose up --build`로 프런트, 백엔드, DB가 모두 기동한다. 1. `docker compose up --build`로 프런트, 백엔드, DB가 모두 기동한다.
2. 브라우저에서 8080 화면이 열린다. 2. 브라우저에서 8080 화면이 열린다.
3. `/api/assets/master`가 정상 응답한다. 3. `/api/assets/master`가 정상 응답한다.
4. backend가 DB 컨테이너와 정상 연결된다. 4. backend가 DB 컨테이너와 정상 연결된다.
5. DB 초기 테이블과 데이터가 기대 상태로 적재된다. 5. DB 초기 테이블과 데이터가 기대 상태로 적재된다.
6. `uploads`, `map_config.json`, MySQL 데이터가 재시작 후에도 유지된다. 6. `uploads`, `map_config.json`, MySQL 데이터가 재시작 후에도 유지된다.
이 문서는 실제 구현 작업의 체크리스트로 사용한다. 이 문서는 실제 구현 작업의 체크리스트로 사용한다.

226
docs/itam_cicd_setup.md Normal file
View File

@@ -0,0 +1,226 @@
# ITAM CI/CD 설정 가이드
## 1. 문서 목적
이 문서는 현재 ITAM 저장소에 구성된 CI/CD 파일을 실제 운영에 연결하기 위해 필요한 기준을 정리한 가이드다.
대상 범위는 아래와 같다.
1. Gitea Actions workflow 역할
2. Gitea Variables / Secrets 설정값
3. 운영 서버 배포 디렉토리 기준
4. 운영 영속 경로 기준
5. production deploy 실행 전 확인 사항
---
## 2. 현재 CI/CD 구성
현재 `.gitea/workflows`에는 ITAM 관련 workflow만 남겨둔 상태다.
1. `itam_code_check.yml`
2. `itam_docker_build_check.yml`
3. `itam_production_deploy.yml`
각 workflow의 역할은 아래와 같다.
1. `itam_code_check.yml`: TypeScript/Vite build와 compose 문법 검증
2. `itam_docker_build_check.yml`: 운영용 Docker 이미지 빌드 가능 여부 검증
3. `itam_production_deploy.yml`: 운영 서버에 SSH 접속 후 실제 배포 수행
현재 배포 흐름은 아래와 같다.
```mermaid
flowchart LR
DEV["Developer Push or Manual Run"] --> CODE["ITAM Code Check"]
CODE --> BUILD["ITAM Docker Build Check"]
BUILD --> DEPLOY["ITAM Production Deploy"]
DEPLOY --> HOST["Production Host"]
HOST --> APP["docker compose up -d --build"]
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
---
## 3. Gitea Variables / Secrets 기준
`itam_production_deploy.yml`이 정상 동작하려면 아래 값이 필요하다.
### 3.1 Variables
아래 항목은 Gitea repository Variables에 등록한다.
| Key | 설명 | 예시 |
| --- | --- | --- |
| `PROD_HOST` | 운영 서버 SSH 접속 호스트 | `10.0.0.25` |
| `PROD_USER` | 운영 서버 SSH 사용자 | `deploy` |
| `PROD_DEPLOY_PATH` | 서버에서 저장소를 배포할 경로 | `/opt/itam` |
| `PROD_BACKUP_ROOT` | 배포 전 백업 저장 경로, 배포 경로 바깥이어야 함 | `/opt/itam-backups` |
| `PROD_GIT_URL` | 운영 서버에서 pull 가능한 저장소 주소 | `git@gitea.example.com:team/itam.git` |
| `PROD_DB_HOST` | 외부 MySQL 호스트 | `172.16.8.151` |
| `PROD_DB_PORT` | 외부 MySQL 포트 | `3306` |
| `PROD_DB_USER` | 운영 DB 계정 | `itam_admin` |
| `PROD_DB_NAME` | 운영 DB 이름 | `itam` |
| `PROD_LOG_LEVEL` | 애플리케이션 로그 레벨 | `info` |
### 3.2 Secrets
아래 항목은 Gitea repository Secrets에 등록한다.
| Key | 설명 |
| --- | --- |
| `PROD_SSH_PRIVATE_KEY` | 운영 서버 접속용 개인키 |
| `PROD_DB_PASS` | 운영 DB 비밀번호 |
### 3.3 운영 원칙
1. `PROD_DB_PASS`는 Variables가 아니라 Secrets에만 둔다.
2. `PROD_SSH_PRIVATE_KEY`는 배포 전용 계정 키를 사용한다.
3. `PROD_GIT_URL`은 운영 서버에서 직접 pull 가능한 주소여야 한다.
4. 운영 서버의 `known_hosts`는 workflow에서 자동 등록되지만, 최초 운영 전 수동 접속 검증도 함께 수행하는 것이 안전하다.
5. `PROD_BACKUP_ROOT``PROD_DEPLOY_PATH` 내부가 아니라 바깥 경로를 사용해야 한다.
---
## 4. 운영 서버 배포 디렉토리 기준
현재 `itam_production_deploy.yml`은 운영 서버에서 아래 흐름으로 배포를 수행한다.
1. `PROD_DEPLOY_PATH` 디렉토리를 생성한다.
2. 기존 운영 상태가 있으면 배포 전 백업을 수행한다.
3. 해당 경로에 저장소를 clone 또는 fetch 한다.
4. 지정 브랜치로 checkout 한다.
5. `uploads`, `logs/nginx` 디렉토리를 생성한다.
6. `.env.deploy`를 서버의 `.env`로 복사한다.
7. `docker compose -f docker-compose.prod.yaml up -d --build`를 실행한다.
권장 디렉토리 구조는 아래와 같다.
```text
/opt/itam/
.env
docker-compose.prod.yaml
Dockerfile.frontend.prod
Dockerfile.backend.prod
map_config.json
uploads/
logs/
nginx/
docker/
nginx/
default.conf
frontend/
default.conf
src/
public/
img/
/opt/itam-backups/
db/
files/
```
현재 구조 기준 배포 관계는 아래와 같다.
```mermaid
flowchart TB
subgraph HOST["Production Host"]
REPO["PROD_DEPLOY_PATH"]
ENV[".env"]
UP["uploads/"]
LOGS["logs/nginx/"]
MAP["map_config.json"]
end
subgraph CTR["Docker Services"]
NGINX["itam-nginx"]
FRONT["itam-frontend"]
BACK["itam-backend"]
end
DB["External MySQL"]
REPO --> NGINX
ENV --> BACK
UP --> BACK
MAP --> BACK
LOGS --> NGINX
REPO --> BAK["PROD_BACKUP_ROOT"]
NGINX --> FRONT
NGINX --> BACK
BACK --> DB
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
---
## 5. 영속 경로 기준
현재 `docker-compose.prod.yaml` 기준으로 운영에서 유지되어야 하는 경로는 아래와 같다.
1. `.env`
2. `uploads/`
3. `map_config.json`
4. `logs/nginx/`
5. `PROD_BACKUP_ROOT`
각 경로의 의미는 아래와 같다.
1. `.env`: backend 런타임 환경변수
2. `uploads/`: 업로드 파일 데이터
3. `map_config.json`: 위치/맵 구성 데이터
4. `logs/nginx/`: reverse proxy 접근 로그 및 에러 로그
5. `PROD_BACKUP_ROOT`: 배포 전 DB dump와 운영 파일 아카이브 저장 위치
운영 기준으로 보면 `uploads/``map_config.json`은 애플리케이션 데이터이고, `.env`는 환경 설정이며, `logs/nginx/`는 운영 추적 데이터다.
즉 서버 운영 시 컨테이너만 다시 띄우면 되는 구조가 아니라, 이 경로들이 유지되는 것을 전제로 배포가 성립한다.
---
## 6. 배포 전 체크리스트
`itam_production_deploy.yml` 실행 전 아래 항목을 먼저 확인한다.
1. 운영 서버에 Docker Engine과 `docker compose`가 설치되어 있어야 한다.
2. 운영 서버의 배포 계정이 Docker 실행 권한을 가져야 한다.
3. 운영 서버에서 `PROD_GIT_URL`로 직접 `git fetch`가 가능해야 한다.
4. 외부 MySQL 접속 정보가 실제 운영망 기준으로 열려 있어야 한다.
5. 운영 서버에 `map_config.json` 초기 파일이 존재해야 한다.
6. 방화벽 또는 보안 장비에서 80 포트 접근 정책이 정리되어 있어야 한다.
권장 확인 명령 예시는 아래와 같다.
```bash
docker --version
docker compose version
git ls-remote <PROD_GIT_URL>
test -f map_config.json
test -d uploads
```
---
## 7. 현재 구조에서의 해석
현재 ITAM CI/CD는 staging 없이도 운영 배포가 가능한 최소 구조로 정리되어 있다.
이 구조의 장점은 아래와 같다.
1. workflow 수가 적어서 관리가 단순하다.
2. 운영 배포에 필요한 변수와 시크릿 범위가 명확하다.
3. staging이 필요해지면 production deploy workflow를 복제해 별도 환경으로 확장하기 쉽다.
즉, 지금 단계에서는 production 기준을 먼저 고정하고, staging은 동일 패턴으로 추후 추가하는 전략이 적절하다.
---
## 8. 다음 권장 작업
현재 문서 기준으로 바로 이어서 할 작업은 아래 순서가 적절하다.
1. `PROD_DEPLOY_PATH` 실제 서버 경로 확정
2. 운영 서버 배포 계정 생성 및 SSH 키 등록
3. `map_config.json`, `uploads/` 초기 데이터 준비
4. production deploy workflow에 smoke check 추가
5. 로그 로테이션과 백업/복구 절차 문서화

View File

@@ -0,0 +1,247 @@
# ITAM 운영 배포 작업 로드맵
## 1. 문서 목적
이 문서는 ITAM 저장소를 Windows/WSL 시범 구동 상태에서 Linux 운영 서버 배포 상태로 전환하기 위한 구체적인 작업 목록과 우선순위를 정리한 로드맵이다.
현재 상태: 개발/시범용 Docker 구조 (Vite dev server + bind mount + external DB)
목표 상태: 운영 배포 구조 (정적 빌드 + 영속 스토리지 분리 + reverse proxy + external DB)
---
## 2. 작업 페이즈 분류
### 2.1 Phase 1: 핵심 배포 파일 (우선순위: 높음)
운영 환경에서 실제 배포를 가능하게 하는 기초 작업이다.
1.**Add production compose file** (`docker-compose.prod.yaml`)
- 목표: 운영 서버 기준 최종 compose 파일 작성
- 범위: backend, frontend, nginx 서비스 정의
- 입력: 외부 `.env`, 호스트 경로 마운트 정의
- 출력: `/deploy/docker-compose.prod.yaml`
- 완료 기준: `docker compose -f docker-compose.prod.yaml config` 성공
2.**Create production frontend Dockerfile (multi-stage build)**
- 목표: 정적 자산 기반 프런트엔드 이미지 생성
- 범위: Stage 1 = 빌드 (Node.js + npm build), Stage 2 = 정적 서빙 (Nginx)
- 입력: 현재 `Dockerfile.frontend` 참고, `npm run build` 검증
- 출력: `Dockerfile.frontend.prod` 또는 분기 처리
- 완료 기준: 로컬에서 `npm run build` 성공, 이미지 빌드 성공, 정적 파일 8080 서빙 확인
3.**Harden backend Dockerfile for production**
- 목표: production 환경 최적화된 백엔드 이미지
- 범위: NODE_ENV=production, production 의존성만, 비루트 사용자, health check 추가
- 입력: 현재 `Dockerfile.backend`, `server.js` 검토
- 출력: 수정된 `Dockerfile.backend` 또는 `.prod` 버전
- 완료 기준: 이미지 빌드 성공, 시작 후 health endpoint 응답 200
4.**Define host paths and named volumes for persistence**
- 목표: Linux 서버의 운영 디렉터리 구조 정의
- 범위: `/srv/itam/{app,env,config,uploads,logs,deploy}` 마운트 정책 확정
- 입력: doc_readme3.md 섹션 7 참고
- 출력: docker-compose.prod.yaml에 적용, 볼륨 마운트 확인
- 완료 기준: compose 파일에 경로 마운트 명시, 영속성 테스트 (컨테이너 재시작 후 데이터 유지)
---
### 2.2 Phase 2: 네트워킹 및 보안 (우선순위: 높음)
외부 접근 경로와 보안을 담당하는 작업이다.
5.**Provide Nginx reverse-proxy and frontend static config**
- 목표: Nginx 설정파일 작성 (frontend 정적 서빙 + API 프록시)
- 범위: `/` → frontend, `/api/` → backend:3000, 기본 보안 헤더, gzip
- 입력: doc_readme3.md 섹션 12 참고 (예시 개념)
- 출력: `/deploy/nginx/default.conf`
- 완료 기준: `docker compose -f docker-compose.prod.yaml up -d``http://localhost/api/assets/master` 응답 200
6.**Externalize and secure environment variables (.env.example + secrets guidance)**
- 목표: 민감정보 보호 기준 문서화
- 범위: `.env.example` 생성, Git 제외 확인, 운영 환경 분리 지침
- 입력: 현재 `.env` 파일, `.gitignore` 점검
- 출력: `.env.example`, env 관리 가이드 추가 (doc_readme3.md 또는 SECURITY.md)
- 완료 기준: `.env``.gitignore`에 등록, `.env.example` 배포 파일 작성됨
7.**Define TLS certificate handling strategy (Let's Encrypt / mount certs)**
- 목표: HTTPS 인증서 관리 정책 확정
- 범위: 자동 갱신 (certbot + Let's Encrypt) 또는 마운트 기반 수동 관리 선택
- 입력: 사내 정책 확인, 운영 도메인 확인
- 출력: `deploy/nginx/tls-strategy.md` 또는 compose 파일 주석으로 정리
- 완료 기준: 선택 방식 문서화, nginx 설정 적용 준비
8.**Security review: non-root users, image scan, secret rotation**
- 목표: 보안 체크리스트 작성 및 초기 적용
- 범위: Dockerfile non-root 사용자 추가, 이미지 취약점 스캔 지침, 비밀 로테이션 정책
- 입력: doc_readme3.md 섹션 13.3 (보안) 참고
- 출력: 수정된 Dockerfile, `SECURITY.md` 또는 운영 가이드 추가
- 완료 기준: 백엔드/프런트엔드 모두 비루트 사용자로 실행 확인
---
### 2.3 Phase 3: 모니터링 및 운영 준비 (우선순위: 중간)
배포 후 운영을 원활하게 하기 위한 작업이다.
9.**Add healthcheck and readiness endpoint in backend**
- 목표: backend 헬스 체크 엔드포인트 추가
- 범위: `GET /health` 또는 `/ready` 엔드포인트 추가 (DB 연결 확인)
- 입력: `server.js` 현재 코드 검토
- 출력: backend 에서 health 응답, docker-compose.prod.yaml에 healthcheck 설정
- 완료 기준: `curl http://localhost:3000/health` 응답 200, 컨테이너 헬스 상태 healthy 표시
10.**Add logging and log rotation guidance**
- 목표: 컨테이너 로그 관리 정책 문서화
- 범위: Docker logging driver 설정, log rotation 정책, 저장소 경로 정의
- 입력: `/srv/itam/logs` 마운트 계획
- 출력: docker-compose.prod.yaml에 로깅 설정, docs에 로그 확인 가이드
- 완료 기준: 로그 파일이 `/srv/itam/logs`에 저장됨, rotation 정책 명시
11.**Document backup and restore procedures for DB and uploads**
- 목표: 운영 데이터 백업/복구 절차 문서화
- 범위: 외부 MySQL 백업 정책, `/srv/itam/uploads` 백업, 복구 절차 스크립트 예시
- 입력: doc_readme3.md 섹션 14 참고
- 출력: `BACKUP_RESTORE.md` 또는 운영 가이드 추가 섹션
- 완료 기준: 백업 스크립트 예시 작성, 복구 절차 테스트 완료
12.**Add smoke tests and post-deploy checks**
- 목표: 배포 후 빠른 검증 스크립트 작성
- 범위: 컨테이너 상태 확인, API 응답 테스트, 파일 업로드 테스트, DB 연결 확인
- 입력: doc_readme3.md 섹션 13 (점검 체크리스트) 참고
- 출력: `scripts/smoke-test.sh` 또는 배포 후 확인 스크립트
- 완료 기준: 스크립트 실행 후 모든 검사 통과
---
### 2.4 Phase 4: 자동화 및 CI/CD (우선순위: 중간)
장기 운영을 위한 자동화 작업이다.
13.**Prepare CI/CD build & push scripts (image registry)**
- 목표: 이미지 빌드 및 레지스트리 푸시 자동화
- 범위: `docker build`, `docker tag`, `docker push` 스크립트 또는 GitHub Actions/GitLab CI 예시
- 입력: 이미지 레지스트리 주소 확인 (e.g., registry.example.com, Docker Hub, etc.)
- 출력: `.github/workflows/build.yml` 또는 `scripts/build-push.sh`
- 완료 기준: 수동 빌드/푸시 스크립트 작동 확인, 이미지 태그 정책 확정
14.**Create deploy directory with compose.prod and nginx configs**
- 목표: 운영 배포 디렉터리 정리
- 범위: `/deploy/docker-compose.prod.yaml`, `/deploy/nginx/default.conf`, 기타 설정 파일 조직화
- 입력: Phase 1-3 결과물
- 출력: 다음 구조로 정리됨:
```
deploy/
docker-compose.prod.yaml
nginx/
default.conf
tls-strategy.md
scripts/
smoke-test.sh
backup.sh
```
- 완료 기준: 디렉터리 구조 확정, 모든 파일 위치 일관성 있음
---
### 2.5 Phase 5: 절차 및 문서화 (우선순위: 중간)
운영 절차와 문서를 정리하는 작업이다.
15. ✅ **Write rollout and rollback procedures (steps, checklist)**
- 목표: 배포 및 복구 절차 문서화
- 범위: 배포 전 체크리스트, 배포 단계별 절차, 장애 시 롤백 절차
- 입력: doc_readme3.md 섹션 16 참고
- 출력: `DEPLOYMENT_PROCEDURES.md` 또는 운영 가이드 통합
- 완료 기준: 절차 문서 완성, 체크리스트 확인 가능
---
## 3. 작업 우선순위 및 권장 순서
### 3.1 필수 우선 작업 (Phase 1 완료 필수)
1. Add production compose file
2. Create production frontend Dockerfile (multi-stage build)
3. Harden backend Dockerfile for production
4. Define host paths and named volumes for persistence
**목표**: 기본 배포 구조 완성, Linux 서버에서 최소 기동 가능 상태
### 3.2 보안 필수 작업 (Phase 2 완료 권장)
5. Provide Nginx reverse-proxy and frontend static config
6. Externalize and secure environment variables
7. Define TLS certificate handling strategy
8. Security review: non-root users, image scan, secret rotation
**목표**: 운영 환경 최소 보안 기준 충족
### 3.3 운영 안정화 작업 (Phase 3 진행)
9. Add healthcheck and readiness endpoint in backend
10. Add logging and log rotation guidance
11. Document backup and restore procedures
12. Add smoke tests and post-deploy checks
**목표**: 배포 후 문제 식별 및 백업/복구 가능 상태
### 3.4 장기 운영 자동화 (Phase 4-5는 선택)
13. Prepare CI/CD build & push scripts
14. Create deploy directory with compose.prod and nginx configs
15. Write rollout and rollback procedures
**목표**: 반복 배포 자동화, 절차 표준화
---
## 4. 작업 환경 및 검증 기준
### 4.1 개발/테스트 환경
- 로컬 Linux VM 또는 WSL에서 Phase 1-2 테스트
- Docker Desktop or Docker Engine 필수
- 외부 테스트 MySQL 또는 mock DB
### 4.2 스테이징 환경
- 실제 Linux 서버에서 전체 배포 절차 테스트
- 운영 환경과 동일 아키텍처
### 4.3 운영 배포
- 위 모든 Phase 완료 후 진행
- 백업 확인 후 배포
- 배포 후 smoke test 자동 실행
---
## 5. 진행 추적
진행 상황은 아래 상태로 추적한다.
- `not-started`: 아직 시작 안 함
- `in-progress`: 현재 진행 중
- `completed`: 완료됨
현재 상태는 모두 `not-started`이며, Phase 1 우선 순위 항목부터 순차적으로 진행한다.
---
## 6. 예상 일정
- Phase 1 (핵심 배포 파일): 1-2일
- Phase 2 (네트워킹 및 보안): 1-2일
- Phase 3 (모니터링 및 운영 준비): 1일
- Phase 4-5 (자동화 및 절차): 1-2일
**전체 예상 소요 시간**: 4-7일
---
## 7. 추가 고려사항
1. 현재 `doc_readme3.md`는 가이드 문서이고, 이 로드맵은 구현 작업 목록이다.
2. Phase 1-2 완료 후 실제 코드 커밋은 `Dockerizing` 브랜치에만 한다.
3. 각 Phase 완료 후 관련 문서도 함께 업데이트한다.
4. 운영 전환 전에 최소 1회 스테이징 환경에서 전체 배포 절차 테스트를 수행한다.

110
scripts/backup.sh Normal file
View File

@@ -0,0 +1,110 @@
#!/usr/bin/env sh
set -eu
COMMAND="${1:-help}"
ENV_FILE="${ENV_FILE:-.env}"
BACKUP_ROOT="${BACKUP_ROOT:-backups}"
RETENTION_DAYS="${RETENTION_DAYS:-14}"
TIMESTAMP="${BACKUP_TIMESTAMP:-$(date +%Y%m%d_%H%M%S)}"
log() {
printf '[backup] %s\n' "$*"
}
fail() {
printf '[backup] %s\n' "$*" >&2
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "Required command not found: $1"
}
load_env() {
[ -f "$ENV_FILE" ] || fail "Env file not found: $ENV_FILE"
set -a
# shellcheck disable=SC1090
. "$ENV_FILE"
set +a
: "${DB_HOST:?DB_HOST is required in $ENV_FILE}"
: "${DB_PORT:=3306}"
: "${DB_USER:?DB_USER is required in $ENV_FILE}"
: "${DB_PASS:?DB_PASS is required in $ENV_FILE}"
: "${DB_NAME:?DB_NAME is required in $ENV_FILE}"
}
db_dump() {
require_command mysqldump
require_command gzip
load_env
mkdir -p "$BACKUP_ROOT/db"
output_path="$BACKUP_ROOT/db/${DB_NAME}_${TIMESTAMP}.sql.gz"
log "Creating DB dump: $output_path"
MYSQL_PWD="$DB_PASS" mysqldump \
--host="$DB_HOST" \
--port="$DB_PORT" \
--user="$DB_USER" \
--single-transaction \
--quick \
--routines \
--triggers \
"$DB_NAME" | gzip > "$output_path"
log "DB dump completed: $output_path"
}
files_backup() {
require_command tar
mkdir -p "$BACKUP_ROOT/files"
archive_path="$BACKUP_ROOT/files/runtime_${TIMESTAMP}.tar.gz"
set --
[ -f "$ENV_FILE" ] && set -- "$@" "$ENV_FILE"
[ -d "uploads" ] && set -- "$@" "uploads"
[ -f "map_config.json" ] && set -- "$@" "map_config.json"
[ "$#" -gt 0 ] || fail "No runtime files found to archive"
log "Creating runtime archive: $archive_path"
tar -czf "$archive_path" "$@"
log "Runtime archive completed: $archive_path"
}
cleanup_backups() {
require_command find
[ -d "$BACKUP_ROOT" ] || {
log "Backup root does not exist, skipping cleanup: $BACKUP_ROOT"
return 0
}
log "Deleting backup files older than ${RETENTION_DAYS} days from $BACKUP_ROOT"
find "$BACKUP_ROOT" -type f -mtime "+$RETENTION_DAYS" -print -delete
}
case "$COMMAND" in
db)
db_dump
;;
files)
files_backup
;;
full)
db_dump
files_backup
;;
cleanup)
cleanup_backups
;;
help|--help|-h)
log "Commands: db | files | full | cleanup"
;;
*)
fail "Unknown command: $COMMAND"
;;
esac

View File

@@ -742,6 +742,31 @@ app.post('/api/upload', (req, res) => {
} }
}); });
// Health check endpoint for container orchestration
app.get('/health', async (req, res) => {
try {
const connection = await pool.getConnection();
const result = await connection.query('SELECT 1');
connection.release();
res.status(200).json({ status: 'ok', db: 'connected' });
} catch (err) {
// Return degraded status if DB unreachable, but still report service as alive
res.status(200).json({ status: 'degraded', db: 'unreachable', error: err.message });
}
});
// Readiness check endpoint (only returns 200 if fully ready)
app.get('/ready', async (req, res) => {
try {
const connection = await pool.getConnection();
const result = await connection.query('SELECT 1');
connection.release();
res.status(200).json({ status: 'ready' });
} catch (err) {
res.status(503).json({ status: 'not_ready', error: err.message });
}
});
app.listen(3000, '0.0.0.0', () => { app.listen(3000, '0.0.0.0', () => {
console.log('📡 ITAM BACKEND SERVER RUNNING ON PORT 3000 (V3 Normalized)'); console.log('📡 ITAM BACKEND SERVER RUNNING ON PORT 3000 (V3 Normalized)');
}); });

View File

@@ -1,10 +1,10 @@
@echo off @echo off
chcp 65001 >nul chcp 65001 >nul
cd /d "%~dp0" cd /d "%~dp0"
powershell -ExecutionPolicy Bypass -File "%~dp0start_docker_wsl.ps1" powershell -ExecutionPolicy Bypass -File "%~dp0start_docker_wsl.ps1"
if errorlevel 1 ( if errorlevel 1 (
echo. echo.
echo [ERROR] start_docker_wsl.ps1 failed. echo [ERROR] start_docker_wsl.ps1 failed.
pause pause
exit /b %errorlevel% exit /b %errorlevel%
) )

View File

@@ -1,107 +1,107 @@
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$projectWindowsPath = $PSScriptRoot $projectWindowsPath = $PSScriptRoot
$wslProjectPath = (wsl wslpath $projectWindowsPath).Trim() $wslProjectPath = (wsl wslpath $projectWindowsPath).Trim()
$envFilePath = Join-Path $PSScriptRoot '.env' $envFilePath = Join-Path $PSScriptRoot '.env'
function Get-EnvValue { function Get-EnvValue {
param( param(
[string]$FilePath, [string]$FilePath,
[string]$Key [string]$Key
) )
if (-not (Test-Path $FilePath)) { if (-not (Test-Path $FilePath)) {
return $null return $null
} }
$line = Get-Content $FilePath | Where-Object { $_ -match "^$Key=" } | Select-Object -First 1 $line = Get-Content $FilePath | Where-Object { $_ -match "^$Key=" } | Select-Object -First 1
if (-not $line) { if (-not $line) {
return $null return $null
} }
return ($line -split '=', 2)[1].Trim() return ($line -split '=', 2)[1].Trim()
} }
function Test-TcpPortFast { function Test-TcpPortFast {
param( param(
[string]$HostName, [string]$HostName,
[int]$Port, [int]$Port,
[int]$TimeoutMs = 3000 [int]$TimeoutMs = 3000
) )
$client = New-Object System.Net.Sockets.TcpClient $client = New-Object System.Net.Sockets.TcpClient
try { try {
$asyncResult = $client.BeginConnect($HostName, $Port, $null, $null) $asyncResult = $client.BeginConnect($HostName, $Port, $null, $null)
if (-not $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false)) { if (-not $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false)) {
$client.Close() $client.Close()
return $false return $false
} }
$client.EndConnect($asyncResult) $client.EndConnect($asyncResult)
$client.Close() $client.Close()
return $true return $true
} }
catch { catch {
$client.Close() $client.Close()
return $false return $false
} }
} }
Write-Host "============================================" -ForegroundColor Cyan Write-Host "============================================" -ForegroundColor Cyan
Write-Host " HM ITAM WSL Docker Start" -ForegroundColor Cyan Write-Host " HM ITAM WSL Docker Start" -ForegroundColor Cyan
Write-Host "============================================" -ForegroundColor Cyan Write-Host "============================================" -ForegroundColor Cyan
Write-Host "" Write-Host ""
Write-Host "[INFO] Checking WSL..." Write-Host "[INFO] Checking WSL..."
wsl -l -v wsl -l -v
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Host "[ERROR] WSL is not available." -ForegroundColor Red Write-Host "[ERROR] WSL is not available." -ForegroundColor Red
exit 1 exit 1
} }
Write-Host "[INFO] Checking Docker in WSL..." Write-Host "[INFO] Checking Docker in WSL..."
wsl sh -lc "docker --version" wsl sh -lc "docker --version"
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Host "[ERROR] Docker is not available inside WSL." -ForegroundColor Red Write-Host "[ERROR] Docker is not available inside WSL." -ForegroundColor Red
exit 1 exit 1
} }
$dbHost = Get-EnvValue -FilePath $envFilePath -Key 'DB_HOST' $dbHost = Get-EnvValue -FilePath $envFilePath -Key 'DB_HOST'
$dbPort = Get-EnvValue -FilePath $envFilePath -Key 'DB_PORT' $dbPort = Get-EnvValue -FilePath $envFilePath -Key 'DB_PORT'
if (-not $dbPort) { if (-not $dbPort) {
$dbPort = '3306' $dbPort = '3306'
} }
if (-not $dbHost) { if (-not $dbHost) {
Write-Host "[WARN] .env is missing DB_HOST. Containers will still start, but backend DB calls will fail until DB settings are fixed." -ForegroundColor Yellow Write-Host "[WARN] .env is missing DB_HOST. Containers will still start, but backend DB calls will fail until DB settings are fixed." -ForegroundColor Yellow
} }
if ($dbHost) { if ($dbHost) {
Write-Host "[INFO] Checking external DB reachability..." Write-Host "[INFO] Checking external DB reachability..."
$dbReachable = Test-TcpPortFast -HostName $dbHost -Port ([int]$dbPort) $dbReachable = Test-TcpPortFast -HostName $dbHost -Port ([int]$dbPort)
if (-not $dbReachable) { if (-not $dbReachable) {
Write-Host "[WARN] External DB is unreachable: $dbHost`:$dbPort" -ForegroundColor Yellow Write-Host "[WARN] External DB is unreachable: $dbHost`:$dbPort" -ForegroundColor Yellow
Write-Host "[HINT] Containers will still start. Check VPN/private network connection, firewall rules, DB host/port in .env, or whether the DB server is running." -ForegroundColor Yellow Write-Host "[HINT] Containers will still start. Check VPN/private network connection, firewall rules, DB host/port in .env, or whether the DB server is running." -ForegroundColor Yellow
} }
} }
Write-Host "[INFO] Starting ITAM containers in WSL..." Write-Host "[INFO] Starting ITAM containers in WSL..."
wsl sh -lc "cd '$wslProjectPath' && docker compose up --build -d --remove-orphans" wsl sh -lc "cd '$wslProjectPath' && docker compose up --build -d --remove-orphans"
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Host "[WARN] Build-based startup failed. Retrying with cached images/containers..." -ForegroundColor Yellow Write-Host "[WARN] Build-based startup failed. Retrying with cached images/containers..." -ForegroundColor Yellow
wsl sh -lc "cd '$wslProjectPath' && docker compose up -d --remove-orphans" wsl sh -lc "cd '$wslProjectPath' && docker compose up -d --remove-orphans"
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Host "[ERROR] Failed to start containers." -ForegroundColor Red Write-Host "[ERROR] Failed to start containers." -ForegroundColor Red
exit 1 exit 1
} }
} }
Write-Host "" Write-Host ""
Write-Host "============================================" -ForegroundColor Green Write-Host "============================================" -ForegroundColor Green
Write-Host " [OK] WSL Docker stack started." -ForegroundColor Green Write-Host " [OK] WSL Docker stack started." -ForegroundColor Green
Write-Host " [INFO] Frontend: http://localhost:8080" Write-Host " [INFO] Frontend: http://localhost:8080"
Write-Host " [INFO] Backend : http://localhost:3000/api/assets/master" Write-Host " [INFO] Backend : http://localhost:3000/api/assets/master"
Write-Host "============================================" -ForegroundColor Green Write-Host "============================================" -ForegroundColor Green
Start-Process "http://localhost:8080" Start-Process "http://localhost:8080"

View File

@@ -1,4 +1,4 @@
@echo off @echo off
chcp 65001 >nul chcp 65001 >nul
cd /d "%~dp0" cd /d "%~dp0"
powershell -ExecutionPolicy Bypass -File "%~dp0stop_docker_wsl.ps1" powershell -ExecutionPolicy Bypass -File "%~dp0stop_docker_wsl.ps1"

View File

@@ -1,13 +1,13 @@
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$projectWindowsPath = $PSScriptRoot $projectWindowsPath = $PSScriptRoot
$wslProjectPath = (wsl wslpath $projectWindowsPath).Trim() $wslProjectPath = (wsl wslpath $projectWindowsPath).Trim()
Write-Host "[INFO] Stopping ITAM WSL Docker stack..." Write-Host "[INFO] Stopping ITAM WSL Docker stack..."
wsl sh -lc "cd '$wslProjectPath' && docker compose down --remove-orphans" wsl sh -lc "cd '$wslProjectPath' && docker compose down --remove-orphans"
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Host "[ERROR] Failed to stop containers." -ForegroundColor Red Write-Host "[ERROR] Failed to stop containers." -ForegroundColor Red
exit 1 exit 1
} }
Write-Host "[OK] WSL Docker stack stopped." -ForegroundColor Green Write-Host "[OK] WSL Docker stack stopped." -ForegroundColor Green

View File

@@ -0,0 +1,242 @@
# ITAM 구축 방식 변경 비교 보고서
## 1. 문서 목적
이 문서는 ITAM 시스템의 Docker 기반 구축 방식이 초기 개발/테스트 중심 구조에서 현재 운영 배포 중심 구조로 어떻게 변경되었는지 설명하기 위한 보고용 문서다.
이번 비교의 기준은 아래 두 단계다.
1. 초기 구축 방식: Windows + WSL 환경에서 개발 및 테스트 재현을 목적으로 한 Docker 구조
2. 현재 구축 방식: 정식 운영 배포를 고려하여 production 기준으로 정리한 Docker 구조
즉, 이 문서는 “예전 계획안과 지금 계획안의 차이”가 아니라, “처음 구축했던 개발/테스트용 구조와 현재 운영 배포용 구조의 차이”를 설명하는 문서다.
---
## 2. 한눈에 보는 핵심 변화
초기 구축은 아래 목적에 맞춰져 있었다.
1. Windows PC에서 WSL2를 이용해 빠르게 실행할 수 있어야 한다.
2. 현재 개발 구조를 그대로 Docker 안에서 재현해야 한다.
3. 프런트와 백엔드가 개발용 방식으로 동작하면 된다.
반면 현재 구축은 아래 목적에 맞춰 변경되었다.
1. 실제 운영 배포에 사용할 수 있어야 한다.
2. 프런트는 개발 서버가 아니라 정적 빌드 산출물로 서비스되어야 한다.
3. reverse proxy, health check, 운영용 Dockerfile, 운영용 compose 기준이 정리되어야 한다.
4. 운영 과정에서 경로, 정적 자산, 로그, 환경변수 사용 방식이 명확해야 한다.
즉, 초기에는 “개발 재현”이 목적이었고, 현재는 “운영 배포 가능 상태로 정리”하는 것이 목적이다.
---
## 3. 구조 비교도
### 3.1 초기 구축 구조
```mermaid
flowchart LR
U["User Browser"] --> FE["Frontend Container Vite Dev 8080"]
FE -->|api proxy| BE["Backend Container Express 3000"]
BE --> DB["External MySQL 3306"]
BE --> UP["./uploads"]
BE --> CFG["./map_config.json"]
ENV["./.env"] --> BE
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
### 3.2 현재 구축 구조
```mermaid
flowchart LR
U["User Browser"] --> RP["Nginx Reverse Proxy 80"]
RP --> FE["Frontend Container Static Nginx 80"]
RP -->|api| BE["Backend Container Node 3000"]
RP -->|uploads| BE
BE --> DB["External MySQL 3306"]
BE --> UP["./uploads"]
BE --> CFG["./map_config.json"]
ENV["./.env"] --> BE
LOGS["./logs/nginx"] --> RP
linkStyle default stroke:#d32f2f,stroke-width:2px;
```
---
## 4. 비교 요약표
| 구분 | 초기 구축 방식 | 현재 구축 방식 |
|---|---|---|
| 구축 목적 | Windows/WSL 개발 테스트 재현 | 정식 운영 배포 기준 정리 |
| 프런트 실행 방식 | Vite dev server | 빌드된 정적 파일 + Nginx |
| 프런트 포트 | 8080 | 80 |
| 백엔드 | Express API 컨테이너 | Express API 컨테이너 |
| 프록시 구조 | frontend 내부 dev proxy | 외부 nginx reverse proxy |
| 운영용 compose | 없음 또는 개발형 중심 | `docker-compose.prod.yaml` 별도 구성 |
| 테스트용 compose | `docker-compose.yaml` 중심 | `docker-compose.test.yaml` 별도 구성 |
| 정적 자산 처리 | 개발 서버가 직접 참조 | 운영 이미지에 정적 자산 포함 필요 |
| health check | 사실상 없음 | `/health`, `/ready` 기반 포함 |
| 운영 관점 기능 | 낮음 | 높음 |
---
## 5. 초기 구축 방식 설명
초기 구축은 Windows + WSL 환경에서 “지금 돌아가는 개발 구조를 Docker로 그대로 재현”하는 것이 핵심이었다.
주요 특징은 아래와 같다.
1. 프런트는 `Dockerfile.frontend`를 통해 Vite 개발 서버를 컨테이너에서 실행했다.
2. 백엔드는 `Dockerfile.backend`를 통해 Express API 서버를 컨테이너로 실행했다.
3. 프런트는 `VITE_DEV_PROXY_TARGET`을 이용해 `/api` 요청을 `backend:3000`으로 전달했다.
4. 접속 포트는 `http://localhost:8080`이었다.
5. 목적은 운영 배포가 아니라 개발/시연/테스트 재현이었다.
즉, 이 단계에서 중요한 것은 “실제 앱이 개발 환경과 최대한 비슷하게 동작하는가”였다.
---
## 6. 현재 구축 방식 설명
현재 구축은 정식 운영 배포에 맞게 구조를 보강한 상태다.
주요 특징은 아래와 같다.
1. 프런트는 `Dockerfile.frontend.prod`에서 multi-stage build를 통해 정적 파일로 빌드된다.
2. 빌드 산출물은 frontend 컨테이너 내부 Nginx가 서빙한다.
3. 별도의 nginx reverse proxy가 외부 80 포트를 받아 frontend와 backend로 요청을 분기한다.
4. backend는 `Dockerfile.backend.prod`를 사용하며, 비루트 사용자와 health check를 포함한다.
5. 운영용 `docker-compose.prod.yaml`과 검증용 `docker-compose.test.yaml`이 분리되어 있다.
6. 정적 이미지 자산과 로고 파일도 운영 이미지에 포함되도록 보완되었다.
즉, 현재는 단순 개발 재현이 아니라 “운영에 투입 가능한 형태로 구조를 정리”한 상태다.
---
## 7. 주요 변경 포인트 상세
### 7.1 프런트 실행 방식 변경
초기 구축:
1. Vite 개발 서버를 그대로 띄웠다.
2. 코드 수정과 빠른 재확인에 유리했다.
3. 운영 배포용 웹 서버 구조는 아니었다.
현재 구축:
1. `npm run build` 결과물을 사용한다.
2. frontend 컨테이너 내부 Nginx가 정적 파일을 서빙한다.
3. 운영 환경에서 더 안정적인 구조다.
이 차이는 개발 중심 구조에서 운영 중심 구조로 넘어갔다는 것을 가장 잘 보여준다.
### 7.2 프록시 구조 변경
초기 구축:
1. 프런트 컨테이너 내부에서 dev proxy가 `/api`를 backend로 우회했다.
2. 구조가 단순했지만 운영형 reverse proxy는 아니었다.
현재 구축:
1. 외부 nginx가 진입점 역할을 한다.
2. `/`는 frontend, `/api``/uploads`는 backend로 분기한다.
3. 운영형 라우팅 구조를 갖추게 되었다.
### 7.3 compose 구성 분리
초기 구축:
1. 사실상 개발용 compose 중심이었다.
2. 운영 목적과 검증 목적이 명확히 분리되지 않았다.
현재 구축:
1. `docker-compose.test.yaml`은 운영형 구조 검증용
2. `docker-compose.prod.yaml`은 운영 모드 기동용
3. 목적에 따라 compose 파일이 구분된다.
### 7.4 백엔드 운영성 강화
초기 구축:
1. 백엔드는 단순 실행 중심이었다.
2. 운영 상태 확인이나 readiness 기준이 부족했다.
현재 구축:
1. `/health`, `/ready` 엔드포인트 추가
2. `dumb-init` 적용
3. 비루트 계정 실행
4. health check 포함
즉, 장애 확인과 운영 점검이 가능한 상태로 바뀌었다.
### 7.5 정적 자산 보강
초기 구축:
1. 개발 환경에서는 파일이 로컬에 그대로 있어 지도 이미지와 로고가 직접 보였다.
2. 운영 이미지 기준으로는 정적 자산 누락 가능성이 드러나지 않았다.
현재 구축:
1. `img/location_photo/...` 경로의 지도 이미지 포함
2. `/image 92.png` 로고 파일 포함
3. 운영 이미지에서도 실제 화면이 깨지지 않도록 보완 완료
즉, 현재 구축은 운영 화면 품질까지 고려한 상태다.
---
## 8. 왜 이렇게 바뀌었는가
초기 단계에서는 다음이 중요했다.
1. 빠르게 실행되는가
2. 개발 PC에서 재현 가능한가
3. 기존 코드 구조를 크게 안 건드리고 Docker 안에서 띄울 수 있는가
하지만 운영 배포 기준으로는 다음이 추가로 필요했다.
1. 개발 서버가 아니라 정적 배포 구조여야 함
2. reverse proxy가 있어야 함
3. health check와 운영 점검 기준이 있어야 함
4. 정적 자산 누락 없이 화면이 완전하게 떠야 함
따라서 초기 구축은 목적에 충실한 개발/테스트형 구조였고, 현재 구축은 그 위에 운영 요소를 보강한 형태라고 볼 수 있다.
---
## 9. 기대 효과
현재 구축 방식으로 변경되면서 기대되는 효과는 아래와 같다.
1. 운영 배포 적합성 향상
2. 요청 라우팅 구조 명확화
3. 장애 점검 및 상태 확인 가능
4. test와 prod 분리로 검증 절차 명확화
5. 화면 자산 누락 문제 해소
즉, 지금 구조는 단순히 실행되는 수준을 넘어 운영 준비도가 높아진 구조다.
---
## 10. 최종 요약
이번 변경의 본질은 아래와 같이 정리할 수 있다.
“처음에는 Windows/WSL에서 개발과 테스트를 빠르게 재현하는 Docker 구조였다면, 현재는 실제 운영 배포를 염두에 둔 production 기준 구조로 발전시켰다.”
요약하면 아래와 같다.
1. 초기 구축은 개발 재현과 테스트 목적이었다.
2. 현재 구축은 정식 운영 배포 목적이다.
3. 프런트는 dev server에서 정적 배포 구조로 바뀌었다.
4. nginx reverse proxy와 health check가 추가되었다.
5. 운영 이미지 기준 정적 자산까지 보완되었다.
현재 상태는 “개발 테스트용 Docker 재현” 단계를 넘어, “운영 배포가 가능한 Docker 구조”로 정리된 상태라고 설명할 수 있다.