From 4d597a16506809a97086f7e65cfeb7fbfc486561 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Tue, 13 Oct 2026 10:02:55 +0900 Subject: [PATCH 1/2] feat: dockerize and deploy Storybook --- .dockerignore | 18 +++++++++ .gitea/workflows/deploy-storybook.yml | 55 +++++++++++++++++++++++++++ Dockerfile | 15 ++++++++ README.md | 29 ++++++++++++++ compose.yaml | 6 +++ 5 files changed, 123 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/deploy-storybook.yml create mode 100644 Dockerfile create mode 100644 compose.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..60e6d64 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +.git +.github +.agents +.codex +node_modules +**/node_modules +dist +**/dist +storybook-static +theme-dist +coverage +test-results +playwright-report +_local +*.log +.env +.env.* +!.env.example diff --git a/.gitea/workflows/deploy-storybook.yml b/.gitea/workflows/deploy-storybook.yml new file mode 100644 index 0000000..de48ad5 --- /dev/null +++ b/.gitea/workflows/deploy-storybook.yml @@ -0,0 +1,55 @@ +name: Deploy Storybook + +on: + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: storybook-deployment + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Prepare SSH credentials + env: + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + DEPLOY_KNOWN_HOSTS: ${{ vars.DEPLOY_KNOWN_HOSTS }} + run: | + install -d -m 700 "$HOME/.ssh" + printf '%s\n' "$DEPLOY_SSH_KEY" > "$HOME/.ssh/deploy_key" + chmod 600 "$HOME/.ssh/deploy_key" + printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > "$HOME/.ssh/deploy_known_hosts" + chmod 600 "$HOME/.ssh/deploy_known_hosts" + + - name: Update checkout and deploy on host + env: + DEPLOY_HOST: ${{ vars.DEPLOY_HOST }} + DEPLOY_USER: ${{ vars.DEPLOY_USER }} + DEPLOY_PATH: ${{ vars.DEPLOY_PATH }} + DEPLOY_PORT: ${{ vars.DEPLOY_PORT }} + run: | + test -n "$DEPLOY_HOST" + test -n "$DEPLOY_USER" + test -n "$DEPLOY_PATH" + test -n "$DEPLOY_PORT" + DEPLOY_PATH_B64="$(printf '%s' "$DEPLOY_PATH" | base64 | tr -d '\n')" + ssh \ + -i "$HOME/.ssh/deploy_key" \ + -p "$DEPLOY_PORT" \ + -o BatchMode=yes \ + -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile="$HOME/.ssh/deploy_known_hosts" \ + "$DEPLOY_USER@$DEPLOY_HOST" \ + "DEPLOY_PATH_B64=$DEPLOY_PATH_B64 bash -se" <<'REMOTE' + set -euo pipefail + DEPLOY_PATH="$(printf '%s' "$DEPLOY_PATH_B64" | base64 -d)" + cd "$DEPLOY_PATH" + git fetch origin main + git checkout main + git pull --ff-only origin main + docker compose down && STORYBOOK_PORT=6024 docker compose up -d --build + REMOTE diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ca0be75 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:24-alpine AS build + +WORKDIR /app + +COPY package.json package-lock.json ./ +COPY packages ./packages +RUN npm ci + +COPY . . +RUN npm run build +RUN npm run build-storybook + +FROM nginx:1.29-alpine AS service +COPY --from=build /app/storybook-static /usr/share/nginx/html +EXPOSE 80 diff --git a/README.md b/README.md index fe6475d..058479e 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,35 @@ Windows에서는 Clone한 루트의 `.\start-storybook.bat`를 실행하거나 정적 Storybook 빌드는 `npm run build-storybook`, 앱 빌드는 `npm run build`, 타입 확인은 `npm run typecheck`입니다. 명령은 루트 package.json의 현재 scripts를 기준으로 합니다. +## Docker로 Storybook 실행 + +Docker Engine과 Docker Compose plugin만 있으면 호스트에 Node.js/npm이나 프로젝트 의존성을 설치하지 않고 Storybook을 실행할 수 있습니다. 저장소 루트에서 실행하면 Docker 이미지 안에서 `npm ci`, 앱 빌드, 정적 Storybook 빌드를 수행한 뒤 nginx가 결과를 제공합니다. + +```sh +docker compose up -d --build +``` + +기본 주소는 **http://localhost:6024/** 입니다. 6024가 사용 중이면 로컬 실행 포트를 변경할 수 있습니다. + +```sh +STORYBOOK_PORT=6025 docker compose up -d --build +``` + +PowerShell에서는 다음과 같이 실행합니다. + +```powershell +$env:STORYBOOK_PORT = "6025" +docker compose up -d --build +``` + +상태와 로그는 `docker compose ps`, `docker compose logs -f storybook`으로 확인하고, 중지할 때는 `docker compose down`을 실행합니다. 소스 변경 후 `docker compose up -d --build`를 실행하면 이미지를 다시 빌드하고 서비스를 갱신합니다. + +### Gitea Actions 배포 + +`.gitea/workflows/deploy-storybook.yml`은 `main`에 push되거나 수동 실행될 때 SSH로 배포 서버의 기존 저장소를 갱신하고, 그 서버에서 `docker compose down && docker compose up -d --build`를 실행합니다. 빌드와 호스팅은 배포 서버에서 이뤄지며 Storybook 산출물을 별도로 전송하지 않습니다. 서비스는 배포 서버의 **6024 포트**를 사용합니다. + +저장소 Actions Variables에 `DEPLOY_HOST` (`10.13.10.4`), `DEPLOY_USER` (`user`), `DEPLOY_PATH` (`/home/user/services/HMDesign`), `DEPLOY_PORT` (`22`), `DEPLOY_KNOWN_HOSTS` (배포 서버의 검증된 SSH host key)를 설정하고, Actions Secret에 `DEPLOY_SSH_KEY`를 등록합니다. 배포 서버 계정은 해당 경로의 저장소에서 `origin/main`을 가져올 수 있어야 하며 Docker Engine/Compose plugin 실행 권한이 필요합니다. Actions Runner는 SSH로 배포 서버에 접속할 수 있어야 합니다. + 문서 내부 링크 검사는 프로젝트 루트에서 `node scripts/check-doc-links.mjs`로 실행합니다. `.git`이 있으면 기존 Git 추적·비추적(ignored 제외) Markdown 수집을 유지하고, `.git` 없는 압축 해제본에서는 파일 시스템과 루트·하위 `.gitignore`의 현재 경로·와일드카드·제외/예외 규칙으로 수집합니다. `.git` 메타데이터와 ignored 폴더는 순회하지 않습니다. Node만 있으면 독립 실행할 수 있으며 결과에 `collectionMode`, 실제 MD/링크 수와 문제 목록을 출력합니다. 종료 코드 0은 통과, 1은 링크 오류, 2는 수집 실행 오류입니다. 회귀 검사는 `node --test scripts/check-doc-links.test.mjs`입니다(두 수집 방식 비교에는 Git 필요). ## 기존 Component 기반 새 React 화면 diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..cb5ee3f --- /dev/null +++ b/compose.yaml @@ -0,0 +1,6 @@ +services: + storybook: + build: . + ports: + - "${STORYBOOK_PORT:-6024}:80" + restart: unless-stopped From 9118c2c602791f9b90417af14f029ba9069ac265 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Tue, 13 Oct 2026 10:06:06 +0900 Subject: [PATCH 2/2] fix: deploy Storybook from immutable source archive --- .gitea/workflows/deploy-storybook.yml | 47 ++++++++++++++++++++------- .gitignore | 1 + README.md | 4 +-- compose.yaml | 2 ++ 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/.gitea/workflows/deploy-storybook.yml b/.gitea/workflows/deploy-storybook.yml index de48ad5..6e713a3 100644 --- a/.gitea/workflows/deploy-storybook.yml +++ b/.gitea/workflows/deploy-storybook.yml @@ -14,18 +14,26 @@ jobs: deploy: runs-on: ubuntu-latest steps: + - name: Check out workflow commit + uses: actions/checkout@v4 + - name: Prepare SSH credentials env: DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} DEPLOY_KNOWN_HOSTS: ${{ vars.DEPLOY_KNOWN_HOSTS }} run: | + test -n "$DEPLOY_SSH_KEY" + test -n "$DEPLOY_KNOWN_HOSTS" install -d -m 700 "$HOME/.ssh" printf '%s\n' "$DEPLOY_SSH_KEY" > "$HOME/.ssh/deploy_key" chmod 600 "$HOME/.ssh/deploy_key" printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > "$HOME/.ssh/deploy_known_hosts" chmod 600 "$HOME/.ssh/deploy_known_hosts" - - name: Update checkout and deploy on host + - name: Package source checkout + run: git archive --format=tar.gz --output="$RUNNER_TEMP/hmdesign-storybook-$GITHUB_SHA.tar.gz" "$GITHUB_SHA" + + - name: Transfer source and deploy on host env: DEPLOY_HOST: ${{ vars.DEPLOY_HOST }} DEPLOY_USER: ${{ vars.DEPLOY_USER }} @@ -37,19 +45,34 @@ jobs: test -n "$DEPLOY_PATH" test -n "$DEPLOY_PORT" DEPLOY_PATH_B64="$(printf '%s' "$DEPLOY_PATH" | base64 | tr -d '\n')" + ARCHIVE="$RUNNER_TEMP/hmdesign-storybook-$GITHUB_SHA.tar.gz" + REMOTE_ARCHIVE="/tmp/hmdesign-storybook-$GITHUB_SHA.tar.gz" + ssh_options=( + -i "$HOME/.ssh/deploy_key" + -p "$DEPLOY_PORT" + -o BatchMode=yes + -o StrictHostKeyChecking=yes + -o UserKnownHostsFile="$HOME/.ssh/deploy_known_hosts" + ) + scp_options=( + -i "$HOME/.ssh/deploy_key" + -P "$DEPLOY_PORT" + -o BatchMode=yes + -o StrictHostKeyChecking=yes + -o UserKnownHostsFile="$HOME/.ssh/deploy_known_hosts" + ) + scp "${scp_options[@]}" "$ARCHIVE" "$DEPLOY_USER@$DEPLOY_HOST:$REMOTE_ARCHIVE" ssh \ - -i "$HOME/.ssh/deploy_key" \ - -p "$DEPLOY_PORT" \ - -o BatchMode=yes \ - -o StrictHostKeyChecking=yes \ - -o UserKnownHostsFile="$HOME/.ssh/deploy_known_hosts" \ + "${ssh_options[@]}" \ "$DEPLOY_USER@$DEPLOY_HOST" \ - "DEPLOY_PATH_B64=$DEPLOY_PATH_B64 bash -se" <<'REMOTE' + "DEPLOY_PATH_B64=$DEPLOY_PATH_B64 RELEASE_SHA=$GITHUB_SHA bash -se" <<'REMOTE' set -euo pipefail DEPLOY_PATH="$(printf '%s' "$DEPLOY_PATH_B64" | base64 -d)" - cd "$DEPLOY_PATH" - git fetch origin main - git checkout main - git pull --ff-only origin main - docker compose down && STORYBOOK_PORT=6024 docker compose up -d --build + RELEASE_DIR="$DEPLOY_PATH/.storybook-deploy/releases/$RELEASE_SHA" + mkdir -p "$RELEASE_DIR" + tar -xzf "/tmp/hmdesign-storybook-$RELEASE_SHA.tar.gz" -C "$RELEASE_DIR" + rm -f "/tmp/hmdesign-storybook-$RELEASE_SHA.tar.gz" + cd "$RELEASE_DIR" + STORYBOOK_PORT=6024 docker compose build + STORYBOOK_PORT=6024 docker compose up -d --no-build --remove-orphans REMOTE diff --git a/.gitignore b/.gitignore index fdaa671..e9ac0ba 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ theme-dist/ !.env.example _local/ +.storybook-deploy/ .claude/settings.local.json # Generated recruitment exports and local test output. diff --git a/README.md b/README.md index 058479e..51969a1 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,9 @@ docker compose up -d --build ### Gitea Actions 배포 -`.gitea/workflows/deploy-storybook.yml`은 `main`에 push되거나 수동 실행될 때 SSH로 배포 서버의 기존 저장소를 갱신하고, 그 서버에서 `docker compose down && docker compose up -d --build`를 실행합니다. 빌드와 호스팅은 배포 서버에서 이뤄지며 Storybook 산출물을 별도로 전송하지 않습니다. 서비스는 배포 서버의 **6024 포트**를 사용합니다. +`.gitea/workflows/deploy-storybook.yml`은 `main`에 push되거나 수동 실행될 때 해당 실행 커밋의 소스 archive를 SSH로 배포 서버에 보내고, 서버에서 Docker build와 Compose 갱신을 수행합니다. 기존 Git 작업 트리는 건드리지 않고 빌드는 서버에서 이뤄지며, 빌드 산출물은 Actions Runner에서 전송하지 않습니다. 실행 커밋별 소스는 `.storybook-deploy/releases/`에 보관하고 서비스는 배포 서버의 **6024 포트**를 사용합니다. 새 이미지 빌드가 끝난 뒤 Compose가 컨테이너를 교체하므로 빌드 중에는 현재 서비스를 유지합니다. -저장소 Actions Variables에 `DEPLOY_HOST` (`10.13.10.4`), `DEPLOY_USER` (`user`), `DEPLOY_PATH` (`/home/user/services/HMDesign`), `DEPLOY_PORT` (`22`), `DEPLOY_KNOWN_HOSTS` (배포 서버의 검증된 SSH host key)를 설정하고, Actions Secret에 `DEPLOY_SSH_KEY`를 등록합니다. 배포 서버 계정은 해당 경로의 저장소에서 `origin/main`을 가져올 수 있어야 하며 Docker Engine/Compose plugin 실행 권한이 필요합니다. Actions Runner는 SSH로 배포 서버에 접속할 수 있어야 합니다. +저장소 Actions Variables에 `DEPLOY_HOST` (`10.13.10.4`), `DEPLOY_USER` (`user`), `DEPLOY_PATH` (`/home/user/services/HMDesign`), `DEPLOY_PORT` (`22`), `DEPLOY_KNOWN_HOSTS` (배포 서버의 검증된 SSH host key)를 설정하고, Actions Secret에 `DEPLOY_SSH_KEY`를 등록합니다. 배포 서버 계정은 지정한 경로에 `.storybook-deploy/releases/`를 만들고 Docker Engine/Compose plugin을 실행할 권한이 필요합니다. Actions Runner는 SSH와 SCP로 배포 서버에 접속할 수 있어야 합니다. 문서 내부 링크 검사는 프로젝트 루트에서 `node scripts/check-doc-links.mjs`로 실행합니다. `.git`이 있으면 기존 Git 추적·비추적(ignored 제외) Markdown 수집을 유지하고, `.git` 없는 압축 해제본에서는 파일 시스템과 루트·하위 `.gitignore`의 현재 경로·와일드카드·제외/예외 규칙으로 수집합니다. `.git` 메타데이터와 ignored 폴더는 순회하지 않습니다. Node만 있으면 독립 실행할 수 있으며 결과에 `collectionMode`, 실제 MD/링크 수와 문제 목록을 출력합니다. 종료 코드 0은 통과, 1은 링크 오류, 2는 수집 실행 오류입니다. 회귀 검사는 `node --test scripts/check-doc-links.test.mjs`입니다(두 수집 방식 비교에는 Git 필요). diff --git a/compose.yaml b/compose.yaml index cb5ee3f..f7d5891 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,3 +1,5 @@ +name: hmdesign + services: storybook: build: .