Files
ITAM/doc_readme3.md

23 KiB

ITAM Linux 운영 배포 가이드

1. 문서 목적

이 문서는 현재 ITAM 저장소를 기준으로 Linux 환경에서 운영 배포하는 방법을 정리한 가이드다.

핵심 전제는 아래와 같다.

  1. 저장소 구조를 크게 재편하지 않는다.
  2. 현재 워크스페이스 기준 파일 구조를 그대로 활용한다.
  3. docker-compose.test.yamldocker-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, 로그는 현재 저장소 기준 상대 경로를 사용한다.
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;

현재 저장소 기준 파일/컨테이너 관계는 아래와 같다.

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. 운영 파일 구조 기준

현재 운영 배포는 저장소 루트를 기준으로 아래 구조를 전제로 한다.

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 기준 예시는 아래와 같다.

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 계정은 분리한다.

권장 권한 예시는 아래와 같다.

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 접근 가능 여부

예시:

ls -la .env
ls -la uploads
ls -la logs/nginx
ls -la map_config.json

10.2 Compose 검증

docker compose -f docker-compose.prod.yaml config

10.3 운영 기동

docker compose -f docker-compose.prod.yaml up -d --build
docker compose -f docker-compose.prod.yaml ps

10.4 운영 중지

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. 배포 실패 또는 검증 실패 후 재배포
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를 확인한다.

즉 최초 배포는 아래 순서다.

서버 준비 완료
-> 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 통과 여부를 확인한다.
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를 수행한다.
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 연결, 앱 런타임 오류를 수정한다.

즉 재배포 전 판단 기준은 아래와 같다.

CI 실패 -> 로컬 코드 / 설정 수정 후 재커밋
배포 실패 -> 서버 환경 또는 배포 설정 수정 후 수동 재실행
Smoke Check 실패 -> 앱 / 프록시 / DB 상태 수정 후 수동 재실행

운영 관점에서는 아래 순서를 지키는 것이 안전하다.

  1. 실패 지점 확인
  2. 원인 수정
  3. 같은 실패가 다시 나는지 좁은 범위로 재검증
  4. 그 다음에만 수동 배포 재실행

11. 테스트 배포 절차

운영형 구성을 먼저 검증하려면 아래처럼 진행한다.

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 또는 외부 의존성 미준비

확인 예시는 아래와 같다.

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. 로그 및 장애 대응

기본 점검 명령은 아래와 같다.

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. 업로드/설정 파일 권한 문제는 없는지

추가 확인 예시는 아래와 같다.

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 데이터베이스

현재 저장소에는 운영 백업을 직접 실행할 수 있도록 Makefilescripts/backup.sh가 추가되어 있다.

기본 원칙은 아래와 같다.

  1. DB dump와 런타임 파일 백업을 분리해서 실행할 수 있어야 한다.
  2. 기본 백업 산출물은 backups/ 디렉터리에 쌓는다.
  3. DB 접속 정보는 .env를 기준으로 읽는다.
  4. 오래된 백업 파일은 보존 주기에 따라 정리한다.

백업 실행 흐름은 아래와 같다.

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 명령 기준

사용 가능한 기본 명령은 아래와 같다.

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.jsonbackups/files/.tar.gz로 저장
  3. make full-backup: DB dump와 파일 백업을 한 번에 수행
  4. make cleanup-backups: 기본 14일이 지난 백업 파일 정리

15.2 실행 예시

가장 단순한 전체 백업 예시는 아래와 같다.

make full-backup

DB dump만 별도로 실행하려면 아래처럼 사용한다.

make db-dump

운영 파일만 묶으려면 아래처럼 사용한다.

make files-backup

보존 주기를 30일로 바꿔 정리하려면 아래처럼 사용한다.

make cleanup-backups RETENTION_DAYS=30

백업 경로를 별도 디스크나 마운트 경로로 바꾸려면 아래처럼 사용한다.

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 테스트 수행

가장 단순한 예시는 아래와 같다.

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, 보안 점검을 현재 구조 기준으로 계속 보강하는 것이다.