From 5dd265605f6ccd2480285386eb380f5d88dbdb17 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 12 Aug 2026 17:11:09 +0900 Subject: [PATCH] =?UTF-8?q?dev-pilots=20=ED=91=9C=EC=A4=80=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=B4=ED=94=84=EB=9D=BC=EC=9D=B8=20=EC=A0=81=EC=9A=A9:=20de?= =?UTF-8?q?ploy.yml=20+=20docker-compose=20(=ED=8F=AC=ED=8A=B8=203000)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 공식 워크플로우(.gitea/workflows/deploy.yml) 그대로 사용 (repo secrets 주입) - deploy/docker-compose.yml: Next.js 포트 3000으로 Traefik 라벨 변경 - 커스텀 배포 방식 제거 Co-Authored-By: Claude Fable 5 --- .gitea/workflows/build-and-deploy.yml | 62 --------------------------- .gitea/workflows/deploy.yml | 35 +++++++++++++++ README.md | 16 ++++--- deploy/docker-compose.yml | 22 ++++++++++ 4 files changed, 66 insertions(+), 69 deletions(-) delete mode 100644 .gitea/workflows/build-and-deploy.yml create mode 100644 .gitea/workflows/deploy.yml create mode 100644 deploy/docker-compose.yml diff --git a/.gitea/workflows/build-and-deploy.yml b/.gitea/workflows/build-and-deploy.yml deleted file mode 100644 index 7ec5435..0000000 --- a/.gitea/workflows/build-and-deploy.yml +++ /dev/null @@ -1,62 +0,0 @@ -# Gitea Actions — 빌드 & 배포 -# -# 동작: -# 1. main 브랜치 push 시 자동 실행 -# 2. 러너가 호스트 docker 데몬으로 이미지 빌드 (레지스트리 불필요) -# 3. 같은 호스트에서 컨테이너 교체 → Traefik이 jskim.vexplor.com HTTPS 라우팅 -# -# DATABASE_URL은 서버의 /home/three/jskim/.env (chmod 600)에서 읽는다. -# 저장소에는 어떤 비밀값도 커밋하지 않는다. - -name: build-and-deploy - -on: - push: - branches: - - main - workflow_dispatch: - -env: - IMAGE: harbor.wace.me/dev-pilots/jskim:latest - CONTAINER_NAME: jskim - DOMAIN: jskim.vexplor.com - HOST_ENV_DIR: /home/three/jskim - -jobs: - deploy: - runs-on: ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: docker CLI 준비 - run: command -v docker || (apt-get update -qq && apt-get install -y -qq docker.io) - - - name: 이미지 빌드 - run: docker build -t "$IMAGE" . - - - name: 컨테이너 교체 - run: | - # 호스트의 env 파일을 호스트 docker 데몬 경유로 읽는다 (러너 컨테이너에는 미마운트) - DATABASE_URL=$(docker run --rm -v "$HOST_ENV_DIR":/cfg:ro alpine sh -c 'grep "^DATABASE_URL=" /cfg/.env | cut -d= -f2-') - if [ -z "$DATABASE_URL" ]; then echo "호스트 $HOST_ENV_DIR/.env 에 DATABASE_URL 없음"; exit 1; fi - docker rm -f "$CONTAINER_NAME" 2>/dev/null || true - docker run -d \ - --name "$CONTAINER_NAME" \ - --restart always \ - --network web \ - -e TZ=Asia/Seoul \ - -e DATABASE_URL="$DATABASE_URL" \ - --label "traefik.enable=true" \ - --label "traefik.docker.network=web" \ - --label "traefik.http.routers.jskim.rule=Host(\`$DOMAIN\`)" \ - --label "traefik.http.routers.jskim.entrypoints=websecure" \ - --label "traefik.http.routers.jskim.tls.certresolver=le" \ - --label "traefik.http.services.jskim.loadbalancer.server.port=3000" \ - "$IMAGE" - docker network connect pgnet "$CONTAINER_NAME" - - - name: 헬스체크 - run: | - sleep 10 - docker exec "$CONTAINER_NAME" wget -qO- http://localhost:3000/api/health || (docker logs --tail 50 "$CONTAINER_NAME"; exit 1) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..54b6d53 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,35 @@ +name: build-and-deploy +on: + push: + branches: [main] + workflow_dispatch: +env: + HARBOR: harbor.wace.me + PROJECT: dev-pilots + APP: jskim +jobs: + deploy: + runs-on: ubuntu-24.04 + steps: + - name: tools + run: | + apt-get update -qq + apt-get install -y ca-certificates curl git docker.io docker-compose-v2 >/dev/null 2>&1 || apt-get install -y ca-certificates curl git docker.io >/dev/null + - name: checkout + run: | + rm -rf src && git clone --depth 1 --branch "$GITHUB_REF_NAME" http://oauth2:${{ github.token }}@gitea:3000/${GITHUB_REPOSITORY}.git src + - name: build + run: | + cd src && docker build -t $HARBOR/$PROJECT/$APP:latest . + - name: push + env: + HUSER: ${{ secrets.HARBOR_USER }} + HSECRET: ${{ secrets.HARBOR_SECRET }} + run: | + echo "$HSECRET" | docker login $HARBOR -u "$HUSER" --password-stdin + docker push $HARBOR/$PROJECT/$APP:latest + - name: deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + run: | + cd src && docker compose -f deploy/docker-compose.yml up -d --pull always --force-recreate diff --git a/README.md b/README.md index 0cbd614..4dea076 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,14 @@ npm run dev ## 배포 -`main` 브랜치에 push하면 Gitea Actions(`.gitea/workflows/build-and-deploy.yml`)가 자동으로: +dev-pilots 교육 파이프라인을 그대로 사용한다. `main` 브랜치에 push하면 +Gitea Actions(`.gitea/workflows/deploy.yml`)가 자동으로: -1. 서버(THREEDEV)에서 Docker 이미지 빌드 (레지스트리 불필요) -2. 컨테이너 교체 (`web` + `pgnet` 네트워크, Traefik 라벨) -3. `/api/health` 헬스체크 +1. Docker 이미지 빌드 → Harbor push (`harbor.wace.me/dev-pilots/jskim`) +2. `deploy/docker-compose.yml`로 컨테이너 교체 (`web` + `pgnet` 네트워크, Traefik 라벨, 포트 3000) +3. Traefik이 `jskim.vexplor.com` HTTPS 라우팅 -운영 `DATABASE_URL`은 서버의 `/home/three/jskim/.env`(chmod 600)에서 읽는다. +운영 `DATABASE_URL`과 Harbor 인증(`HARBOR_USER`/`HARBOR_SECRET`)은 저장소 Secrets로 이미 등록되어 있다. ## 구조 @@ -41,6 +42,7 @@ src/ api/health/ # DB 연결 헬스체크 lib/db.ts # pg Pool (DATABASE_URL 사용) scripts/db-tunnel.sh -Dockerfile # standalone 빌드, node:20-alpine -.gitea/workflows/ # CI/CD +Dockerfile # standalone 빌드, node:20-alpine +deploy/docker-compose.yml # 배포 정의 (Traefik 라벨, 앱 포트) +.gitea/workflows/deploy.yml # CI/CD (dev-pilots 표준) ``` diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..af39128 --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,22 @@ +services: + jskim: + image: harbor.wace.me/dev-pilots/jskim:latest + container_name: jskim + restart: always + environment: + TZ: Asia/Seoul + DATABASE_URL: ${DATABASE_URL:?set DATABASE_URL in repo secrets} + networks: [web, pgnet] + labels: + - traefik.enable=true + - traefik.http.routers.jskim.rule=Host(`jskim.vexplor.com`) + - traefik.http.routers.jskim.entrypoints=websecure + - traefik.http.routers.jskim.tls.certresolver=le + # Next.js standalone 서버 포트 (기존 nginx 플레이스홀더는 80이었음) + - traefik.http.services.jskim.loadbalancer.server.port=3000 + - traefik.docker.network=web +networks: + web: + external: true + pgnet: + external: true