Skip to content

[feat] Dockerfile, docker-compose, Kubernetes 매니페스트 추가 (nginx 정적 호스팅)#114

Open
dasomel wants to merge 3 commits into
eGovFramework:mainfrom
dasomel-eGovFramework:feat/docker-and-k8s-5.0.x
Open

[feat] Dockerfile, docker-compose, Kubernetes 매니페스트 추가 (nginx 정적 호스팅)#114
dasomel wants to merge 3 commits into
eGovFramework:mainfrom
dasomel-eGovFramework:feat/docker-and-k8s-5.0.x

Conversation

@dasomel

@dasomel dasomel commented May 20, 2026

Copy link
Copy Markdown
Contributor

변경 사유

컨테이너 이미지/k8s 매니페스트가 없어 vite build 후 수동 배포만 가능합니다. nginxinc/nginx-unprivileged:1.27-alpine 기반의 운영 가능한 최소 산출물을 추가합니다.

변경 내용

  • Dockerfile — multi-stage node:22-alpinenginxinc/nginx-unprivileged:1.27-alpine
    • 빌드 단계: npm ci + npm run build
    • 런타임: nginx 설정을 SPA용으로 교체
      • 포트 8080 (unprivileged 이미지 기본)
      • try_files로 React Router HTML5 history mode 지원
      • /assets/*는 1년 immutable 캐시
      • /index.html은 no-cache
  • .dockerignorenode_modules/, dist/, coverage/, IDE/.env 등 제외
  • docker-compose.yml — 단일 서비스, ${APP_VERSION:-5.0.0} 변수화
  • k8s/deployment.yaml — 2 replica RollingUpdate, runAsNonRoot(uid 101 nginx-unprivileged 기본), readOnlyRootFilesystem, drop ALL, 정적 서빙에 맞춘 리소스(50m–300m CPU / 32Mi–128Mi), liveness/readiness probe, /var/cache/nginx//var/run//tmp emptyDir로 read-only root 호환
  • k8s/service.yaml — ClusterIP 8080

영향 범위

  • 애플리케이션 코드 변경 없음
  • package.json 변경 없음
  • image: 태그는 예시 — 운영 환경 레지스트리로 교체 필요
  • API 엔드포인트가 동일 origin이 아닌 경우 nginx 설정에 reverse proxy 블록을 별도로 추가 권장

체크리스트

  • 단일 주제(프론트엔드 컨테이너화 + k8s 매니페스트)
  • 5.0.x 브랜치 대상
  • 기존 코드 미변경

The repo has no container image definition or k8s manifests — the only
deployment path is 'vite dev'/'vite build' + manually copying dist to a
web server. Add a production-shaped set that serves the Vite bundle
from nginx-unprivileged.

- Dockerfile: multi-stage Node 22 -> nginx-unprivileged 1.27 Alpine.
  Build stage uses 'npm ci' for reproducibility and 'npm run build'.
  Runtime stage replaces the default nginx config with one that:
    - listens on 8080 (matches the unprivileged image's user)
    - serves the SPA with try_files fallback for HTML5 router
    - caches /assets/* aggressively (Vite emits hashed filenames)
    - bypasses cache for /index.html
- .dockerignore: keep build context small (node_modules/, dist/,
  build/, coverage/, IDE/.env files).
- docker-compose.yml: single-service compose for demo runs, image tag
  parameterised via ${APP_VERSION:-5.0.0}.
- k8s/deployment.yaml: replicas=2 RollingUpdate, runAsNonRoot (uid 101
  matches nginx-unprivileged), readOnlyRootFilesystem, drop ALL,
  resources sized for static serving (50m-300m CPU, 32Mi-128Mi),
  separate liveness/readiness probes, ephemeral
  /var/cache/nginx, /var/run, /tmp emptyDirs so the read-only root
  filesystem profile works.
- k8s/service.yaml: ClusterIP exposing port 8080.
@dasomel dasomel changed the base branch from 5.0.x to main May 26, 2026 15:53
@dasomel dasomel changed the title [feat][5.0.x] Dockerfile, docker-compose, Kubernetes 매니페스트 추가 (nginx 정적 호스팅) [feat] Dockerfile, docker-compose, Kubernetes 매니페스트 추가 (nginx 정적 호스팅) May 26, 2026
@eGovFrameSupport

eGovFrameSupport commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

표준프레임워크에 대한 지속적인 참여에 대단히 감사드립니다. Dockerfile·docker-compose·k8s 매니페스트를 로컬에서 검증했습니다.
멀티스테이지 빌드(npm ci)와 단독 컨테이너 서빙(루트 200, SPA fallback 200, HEALTHCHECK healthy), k8s 배포(replicas 2 정상 기동)까지 확인했습니다.

  1. 배포 상태에서 백엔드 연동(로그인/CRUD)이 정상 동작할 것

본 프로젝트의 병합 기준은 "정상 동작"입니다. 기존 워크플로(백엔드·프론트엔드를 각각 로컬 구동 → 브라우저에서
로그인/CRUD가 백엔드 API를 타고 정상 동작)가 그 기준선입니다. 본 PR은 이 둘을 k8s에 올리는 배포 방식을 새로
제안하므로, 그 배포 산출물에서도 브라우저 로그인/CRUD가 동일하게 정상 동작해야 합니다.

현재는 그렇지 못합니다. 프론트 번들이 API base URL을 빌드 타임에 http://localhost:8080 으로 하드코딩하고(src/config.js), nginx에 백엔드로의 프록시가 없습니다.
그 결과 배포된 SPA는 정적 화면만 뜰 뿐, 클러스터 환경에서 localhost가 클라이언트 자신을 가리켜 API 호출이 전부 깨집니다.

이 end-to-end 동작의 최종 검증은 백엔드의 k8s 배포가 함께 떠야 가능합니다. 가능하면 백엔드까지 함께 올려
로그인이 도는 구성(예: compose 또는 k8s 매니페스트 묶음)을 제시해 주시면 한 번에 확인할 수 있습니다.

  1. 배포 문서(README) 보강

이번에 추가된 Docker/k8s 배포 경로에 대한 안내가 없습니다.

  • 이미지 빌드 명령(docker build -t egovframe-template-simple-react:5.0.0 .)과 BuildKit 필요(Dockerfile heredoc) 여부
  • k8s 배포 절차 및 Service가 ClusterIP이므로 접속은 kubectl port-forward 라는 점(예: kubectl port-forward svc/egov-simple-react 3000:8080 → http://localhost:3000/)
  • 1번과 연계하여 백엔드를 어떻게 띄우고 연결해야 로그인/기본 동작이 되는지(전제 backend, 연결 방식)

이하 사항 반영 확인 후 추가 검증을 진행하도록 하겠습니다.

정적 번들이 API base 를 빌드 시점에 http://localhost:8080 으로 고정해
클러스터/컨테이너에서 localhost 가 클라이언트 자신을 가리켜 로그인·CRUD 가
동작하지 않던 문제를 해결한다.

- src/config.js: SERVER_URL 을 동일 출처 상대경로(/api)로 변경.
  필요 시 VITE_APP_API_BASE_URL 로 절대 URL 지정 가능.
- Dockerfile: nginx 에 /api/ 리버스 프록시 location 추가. prefix 를 제거해
  백엔드(context-path=/)로 전달하고, 백엔드 주소는 BACKEND_URL 환경변수를
  기동 시 envsubst 로 치환(nginx 템플릿 사용).
- vite.config.js: 개발 서버에도 동일한 /api 프록시를 추가해 npm run dev 호환.
- docker-compose.yml: fullstack 프로파일로 백엔드+MySQL 동반 기동 구성 추가.
  브라우저 → nginx(/api) → 백엔드 → DB 경로로 로그인·CRUD 통합 동작.
- k8s/deployment.yaml: BACKEND_URL 환경변수 및 conf.d 쓰기 볼륨 추가
  (readOnlyRootFilesystem 환경에서 envsubst 출력 기록).
- README: 이미지 빌드(BuildKit), docker compose 단독/통합 실행,
  k8s 배포 및 port-forward 접속, 백엔드 연동 방법 문서화.
@dasomel

dasomel commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

두 가지 모두 반영했습니다.

1. 배포 시 백엔드 연동
프론트가 API base를 http://localhost:8080으로 고정 빌드해 컨테이너/클러스터에서 깨지던 문제를, API base를 동일 출처 상대경로(/api)로 바꾸고 nginx에 /api/ 리버스 프록시를 추가해 해결했습니다. 백엔드 주소는 빌드가 아닌 런타임 BACKEND_URL 환경변수로 주입됩니다(nginx envsubst). 개발 서버(npm run dev)에도 동일한 /api 프록시를 넣어 동작을 맞췄습니다. docker-compose.yml에는 fullstack 프로파일로 백엔드+MySQL을 함께 올려 로그인/CRUD까지 한 번에 도는 구성을 추가했습니다(백엔드 이미지·DB 시드 전제는 README에 명시).

2. 문서 보강
README에 이미지 빌드(Dockerfile heredoc → BuildKit 필요), docker compose 단독/통합 실행, k8s 배포 및 kubectl port-forward svc/egov-simple-react 3000:8080(ClusterIP) 접속, 백엔드 연동 방법을 정리했습니다.

검증: npm run build·단위 테스트 통과, 번들에서 localhost:8080 제거 확인, 실제 이미지 빌드→컨테이너 기동→BACKEND_URL 치환 및 / 200 응답까지 확인했습니다. 다만 실제 백엔드+DB와의 E2E 로그인/CRUD, 그리고 k8s 클러스터 실배포는 이 환경에서 직접 검증하지 못해 매니페스트/구성 문법 검증 수준임을 밝힙니다.

@eGovFrameSupport

Copy link
Copy Markdown
Contributor

백엔드 k8s PR(egovframe-template-simple-backend#121)과 함께 로컬 Kubernetes에 올려
풀스택(브라우저 → nginx /api 프록시 → 백엔드 → MySQL/PVC)으로 로그인·게시판·첨부까지 동작하는 것을 확인했습니다.
/api 동일 출처 프록시로 CORS를 제거한 구성, readOnlyRootFilesystem 하에서 쓰기 볼륨 분리 처리한 것도 확인하였습니다.

다만 두 PR을 그대로 함께 배포했을 때 기본값으로는 연동되지 않는 지점이 있어 정렬을 요청드립니다.

  1. k8s 백엔드 Service 이름 불일치 — 본 PR의 BACKEND_URL 기본값은 http://egov-simple-backend:8080이나,
    백엔드 PR #121이 생성하는 k8s Service 이름은 egovframe-template-simple-backend입니다. 두 매니페스트를 그대로
    kubectl apply 하면 nginx /api 프록시 대상이 존재하지 않아 502가 발생합니다(검증 시 BACKEND_URL 오버라이드로 동작
    확인). docker-compose 레이어는 양쪽이 egov-simple-backend로 일치하나 k8s 레이어만 어긋납니다. 짝이 되는 두 템플릿이
    기본값만으로 함께 배포되도록, k8s BACKEND_URL 기본값(및 README 안내)을 백엔드의 실제 Service 이름과 일치하도록 수정 부탁드립니다.

  2. README 환경변수 안내 불일치 - README의 백엔드 연동 단계는 VITE_APP_EGOV_CONTEXT_URL을 안내하나, 변경된
    src/config.js/api 프록시 기반(VITE_APP_API_BASE_URL)을 사용합니다.
    동작에는 지장 없으나 안내 변수와 실제 사용 변수가 달라 혼선 소지가 있습니다.

프론트엔드 k8s deployment 의 BACKEND_URL 기본값이 egov-simple-backend 로
설정되어 있어, 백엔드 템플릿이 생성하는 k8s Service(egovframe-template-simple-backend)와
달라 두 매니페스트를 그대로 apply 하면 nginx /api 프록시 대상이 없어 502 가 발생했다.
BACKEND_URL 기본값을 백엔드 실제 Service 이름으로 맞춰 기본값만으로 연동되도록 한다.

README 의 개발 서버용 VITE_APP_EGOV_CONTEXT_URL 안내와 컨테이너 배포의 /api 프록시
구성을 구분하는 설명도 함께 정리했다.
@dasomel

dasomel commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

재검증과 상세한 피드백 감사합니다. 두 지점 모두 반영했습니다.

  1. k8s BACKEND_URL 기본값 정렬k8s/deployment.yamlBACKEND_URL 기본값을 백엔드 템플릿이 생성하는 실제 k8s Service 이름(egovframe-template-simple-backend)과 일치하도록 http://egovframe-template-simple-backend:8080 으로 수정했습니다. 이제 두 매니페스트를 그대로 kubectl apply 해도 기본값만으로 nginx /api 프록시가 백엔드를 찾아갑니다. (docker-compose 레이어는 지적하신 대로 양쪽이 egov-simple-backend 로 이미 일치하므로 변경하지 않았습니다.)

  2. README 환경변수 안내 정리 — 개발 서버(npm run dev) 흐름에서 쓰는 VITE_APP_EGOV_CONTEXT_URL 과 컨테이너/k8s 배포에서 쓰는 /api 프록시(BACKEND_URL 런타임 주입) 구성을 구분하는 설명을 추가하고, 컨테이너 배포 절의 Service 이름 안내도 위 변경에 맞춰 갱신했습니다.

참고로 BACKEND_URL 은 정적 매니페스트 값 변경이라 별도 빌드/번들 영향은 없습니다. 실 클러스터 풀스택 재검증은 저희 환경에서 불가하여 매니페스트 정합성·Service 이름 일치 기준으로만 확인했고, 동작 검증은 메인테이너님 환경에 의존하는 점 양해 부탁드립니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants