Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 0 additions & 73 deletions .github/workflows/build-image.yml

This file was deleted.

26 changes: 20 additions & 6 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: Deploy

# 홈서버 자동 배포. Build Image가 main에서 성공하면 셀프호스트 러너(맥미니)가
# 홈서버 자동 배포. main push의 CI(check·image)가 전부 성공하면 셀프호스트 러너(맥미니)가
# .env(600) 생성 → compose pull → migrate → worker → api → ready 대기 → Discord 순서로 교체한다.
# 롤백 = workflow_dispatch에 이전 sha를 넣어 같은 잡을 돌린다. main 밖의 실행은 environment 규칙과 if로 막는다.
on:
workflow_run:
workflows: [Build Image]
workflows: [CI]
types: [completed]
branches: [main]
workflow_dispatch:
Expand All @@ -18,19 +18,20 @@ on:
permissions:
contents: read
packages: read
actions: read

concurrency:
group: deploy-production
cancel-in-progress: false

jobs:
deploy:
# workflow_run은 PR 빌드(포크의 main 브랜치 포함)도 보낸다. Build Image가 GHCR에 푸시하는 유일한 경로는
# CI 성공(workflow_run) 트리거이고 그 빌드는 이 레포 main push의 CI에서만 시작되므로, 여기서는 그 체인 이벤트만 받는다
# workflow_run은 PR의 CI(포크의 main 브랜치 포함)도 보낸다. 이미지를 GHCR에 푸시하는 건 이 레포 main push의 CI뿐이므로
# 그 이벤트만 받는다. 워크플로 결론이 success면 check(테스트)와 image(빌드·푸시)가 모두 성공한 것이다
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'workflow_run' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.head_repository.full_name == github.repository)
runs-on: [self-hosted, macmini]
Expand Down Expand Up @@ -65,10 +66,23 @@ jobs:
else
tag=$RUN_SHA
fi
# build-image는 전체 sha(40 hex)만 푸시한다 — 짧은 sha는 존재하지 않는 태그라 pull에서 실패한다
# CI image job은 전체 sha(40 hex)만 푸시한다 — 짧은 sha는 존재하지 않는 태그라 pull에서 실패한다
[[ "$tag" =~ ^[0-9a-f]{40}$ ]] || { echo "image_tag은 전체 커밋 sha(40 hex)만: $tag" >&2; exit 1; }
echo "tag=$tag" >> "$GITHUB_OUTPUT"

# 이미지는 CI의 image job이 check와 나란히 푸시해 테스트에 실패한 커밋에도 있다. 자동 배포는 CI 전체 성공으로 걸러지지만
# 수동 실행(재배포·롤백)은 CI를 거치지 않으므로, 그 sha의 main push CI가 성공했는지 확인한다(마이그레이션은 되돌릴 수 없다)
- name: Require CI success (수동 실행)
if: github.event_name == 'workflow_dispatch'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
set -euo pipefail
n=$(gh api "repos/$REPO/actions/workflows/pr-check.yml/runs?head_sha=$TAG&event=push&branch=main&status=success" --jq .total_count)
[ "$n" -gt 0 ] || { echo "$TAG: main push CI 성공 기록이 없다 — 배포하지 않는다" >&2; exit 1; }

# 빌드 완료 순서는 커밋 순서와 다를 수 있다 — 지금 main 끝이 아닌 커밋의 빌드는 배포하지 않는다(수동 실행은 그대로)
- name: Skip if not the current main head
if: github.event_name == 'workflow_run'
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,52 @@ jobs:
yarn build
test -f dist/main.js

# arm64 앱 이미지(홈서버 맥미니). check와 나란히 돌려 배포까지 걸리는 시간을 줄인다 — Deploy는 이 워크플로(CI) 전체가
# 성공해야 시작하므로, check가 실패한 커밋은 이미지가 올라가도 배포되지 않는다.
# PR은 빌드만(Dockerfile 게이트), main push만 GHCR에 :<sha>로 푸시한다. 가변 태그(:main)는 늦게 끝난 옛 빌드가 덮어쓸 수 있어
# 두지 않는다 — 배포·롤백은 항상 sha. 공개 레포라 셀프호스트가 아닌 GitHub 호스트 arm64 러너(QEMU 없이 네이티브)를 쓴다.
image:
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04-arm
timeout-minutes: 30
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1

- name: Login to GHCR (main만)
if: github.event_name == 'push'
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Image metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ghcr.io/caquick/caquick-be
tags: |
type=raw,value=${{ github.sha }}

- name: Build (main이면 push)
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
platforms: linux/arm64
push: ${{ github.event_name == 'push' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

coverage-report:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
Expand Down
22 changes: 11 additions & 11 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ caquick-be/
├── terraform/ # GitHub repo/branch protection + AWS (S3, IAM) as code
├── scripts/ # gate and ops scripts (dto:check · docs:check · outbox:requeue) + infra specs
├── docs/guide/ # architecture conventions (source of truth)
└── .github/workflows/ # pr-check · build-image · deploy · codeql · knip · nestjs-doctor · discord-notify
└── .github/workflows/ # pr-check · deploy · codeql · knip · nestjs-doctor · discord-notify
```

## 🚀 Getting Started
Expand Down Expand Up @@ -405,14 +405,14 @@ docker compose --profile edge up -d # cloudflared (TUNNEL_

### Workflows

| Workflow | Trigger | Role |
| -------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pr-check.yml` | PR · push (main/develop) | codegen, tsc, lint, docs/arch gates, dto:check (warning only; the hard gate is the pre-push hook), infrastructure specs, integration tests, coverage, and two consecutive builds (cache regression check) |
| `codeql.yml` | PR · push · weekly | CodeQL static security analysis |
| `knip.yml` · `nestjs-doctor.yml` | PR | comments with unused-code and NestJS health reports (advisory) |
| `build-image.yml` | PR (build only) · main **after CI succeeds** (push) | builds an arm64 image and pushes `ghcr.io/caquick/caquick-be:<sha>` (no mutable tags) |
| `deploy.yml` | `build-image` success (main) · manual (rollback sha) | the self-hosted runner (Mac mini) writes `.env` and `app.env` (mode 600) from secrets, then pull → migrate → worker → api → readiness wait → observability, and notifies Discord |
| `discord-notify.yml` | PR · push · issue | Discord notifications |
| Workflow | Trigger | Role |
| -------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pr-check.yml` | PR · push (main/develop) | codegen, tsc, lint, docs/arch gates, dto:check (warning only; the hard gate is the pre-push hook), infrastructure specs, integration tests, coverage, and two consecutive builds (cache regression check) |
| `codeql.yml` | PR · push · weekly | CodeQL static security analysis |
| `knip.yml` · `nestjs-doctor.yml` | PR | comments with unused-code and NestJS health reports (advisory) |
| `pr-check.yml` `image` job | PR (build only) · main push (push) | builds an arm64 image alongside the tests and pushes `ghcr.io/caquick/caquick-be:<sha>` (no mutable tags) |
| `deploy.yml` | main **CI fully succeeds** · manual (rollback sha, CI-passed only) | the self-hosted runner (Mac mini) writes `.env` and `app.env` (mode 600) from secrets, then pull → migrate → worker → api → readiness wait → observability, and notifies Discord |
| `discord-notify.yml` | PR · push · issue | Discord notifications |

### Flow

Expand All @@ -424,13 +424,13 @@ flowchart LR
Develop[🌿 develop]
Release[🔀 Release PR<br/>develop → main]
Main[🌲 main]
Image[📦 build-image → GHCR :sha]
Image[📦 image job → GHCR :sha]
Deploy[🚀 deploy<br/>self-hosted macmini]

Dev --> PR --> Checks
Checks -->|✅ pass| Develop
Develop --> Release --> Main
Main -->|CI ✅| Image --> Deploy
Main --> Image -->|CI all ✅| Deploy
```

### Branch Protection
Expand Down
Loading
Loading