|
2 | 2 |
|
3 | 3 | 자체 N100 호스트에서 Docker Compose 로 띄우는 절차. 사람(호스트)만 본다. |
4 | 4 |
|
| 5 | +## 정책 요약 (Week 3 ~) |
| 6 | + |
| 7 | +- **N100 = prod 단일.** staging 은 N100 에서 안 돌린다. dev 작업은 로컬에서 직접 `pnpm dev`. |
| 8 | +- **자동 배포는 `main` push 일 때만.** `dev` push 는 GHCR 에 `staging-<sha>` 이미지를 빌드/푸시까지만 한다 ("컨테이너 빌드가 깨지지 않는가" 안전망용). N100 에는 떨어지지 않는다. |
| 9 | +- **DuckDNS + Let's Encrypt HTTPS.** `DOMAIN=murun.duckdns.org` 로 Caddy 자동 발급. |
| 10 | + |
5 | 11 | ## 0. CI/CD (자동 배포) 사용 시 (권장) |
6 | 12 |
|
7 | | -GitHub Actions 가 dev push → staging, main push → prod 로 자동 배포한다. |
8 | | -첫 셋업 한 번만 호스트에서 manual init 후, 그 다음부턴 코드만 push 하면 됨. |
| 13 | +GitHub Actions 가 `main` push → prod 배포한다. 첫 셋업 한 번만 호스트에서 manual init 후, 그 다음부턴 main 에 PR 머지하면 자동 반영. |
9 | 14 |
|
10 | 15 | ### 호스트 1회 init |
11 | 16 |
|
12 | 17 | ```bash |
13 | | -# 1) repo clone (한 번만) |
| 18 | +# 1) repo clone (한 번만; deploy workflow 의 git sync 경로와 일치해야 함) |
| 19 | +cd ~ |
14 | 20 | git clone https://github.com/boostcampwm-snu-2026-1/murun-peterabcd.git |
15 | | -cd murun-peterabcd/deploy |
| 21 | +cd murun-peterabcd |
16 | 22 |
|
17 | | -# 2) staging / prod 각각 .env 준비 |
18 | | -cp .env.example .env.staging |
19 | | -cp .env.example .env.prod |
20 | | -# (DOMAIN, AUTH_*, GOOGLE_*, DATABASE_URL, NEXT_PUBLIC_APP_URL 채우기) |
| 23 | +# main 을 default 로 둠 (workflow 가 main ref 기준으로 fetch/checkout 한다) |
| 24 | +git checkout main |
21 | 25 |
|
22 | | -# 3) ghcr.io pull 인증 (한 번만, repo 가 public 이면 skip 가능) |
23 | | -echo "<PAT_with_read:packages>" | docker login ghcr.io -u <github_user> --password-stdin |
| 26 | +cd deploy |
24 | 27 |
|
25 | | -# 4) 첫 가동 (이미지 빌드는 GH Actions 가 대신, 여기선 pull 만) |
26 | | -export MURUN_ENV_FILE=.env.staging |
27 | | -export IMAGE_TAG=staging |
| 28 | +# 2) prod .env 준비 |
| 29 | +cp .env.example .env.prod |
| 30 | +# 안에서 채울 키: |
| 31 | +# DOMAIN=murun.duckdns.org |
| 32 | +# NEXT_PUBLIC_APP_URL=https://murun.duckdns.org |
| 33 | +# AUTH_URL=https://murun.duckdns.org |
| 34 | +# AUTH_SECRET=$(openssl rand -base64 32) |
| 35 | +# AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET (Google Cloud Console) |
| 36 | +# DATABASE_URL=file:/app/data/murun.db |
| 37 | +# UPLOADS_DIR=/app/uploads |
| 38 | +# AUTH_TRUST_HOST=true |
| 39 | +# AUTH_GOOGLE_HD=snu.ac.kr |
| 40 | +# IMAGE_TAG=prod (GH Actions 가 main push 시 prod-<sha> 로 덮어씀) |
| 41 | + |
| 42 | +# 3) (필요 시) ghcr.io pull 인증 |
| 43 | +echo "<PAT_with_read:packages>" | docker login ghcr.io -u <github_user> --password-stdin |
28 | 44 |
|
29 | | -docker compose -p murun-staging --env-file .env.staging pull |
30 | | -docker compose -p murun-staging --env-file .env.staging up -d |
| 45 | +# 4) 첫 가동 |
| 46 | +export MURUN_ENV_FILE=.env.prod |
| 47 | +docker compose -p murun-prod --env-file .env.prod pull |
| 48 | +docker compose -p murun-prod --env-file .env.prod up -d |
31 | 49 | ``` |
32 | 50 |
|
33 | 51 | ### GitHub Actions secrets 등록 (Settings → Secrets and variables → Actions) |
34 | 52 |
|
35 | 53 | | 키 | 값 | |
36 | 54 | |----|------| |
37 | | -| `N100_HOST` | N100 의 공인 IP 또는 DDNS 도메인 | |
| 55 | +| `N100_HOST` | N100 의 공인 IP 또는 DDNS 도메인 (`murun.duckdns.org`) | |
38 | 56 | | `N100_USER` | SSH 로그인 유저 | |
39 | 57 | | `N100_SSH_KEY` | private SSH key 내용 전체 (BEGIN/END 포함). 공개키는 N100 의 `~/.ssh/authorized_keys` 에 등록 | |
40 | 58 |
|
41 | 59 | SSH key 생성 (호스트에서): |
| 60 | + |
42 | 61 | ```bash |
43 | 62 | ssh-keygen -t ed25519 -C "gh-actions-murun-deploy" -f ~/.ssh/n100_gh_deploy |
44 | 63 | cat ~/.ssh/n100_gh_deploy.pub >> ~/.ssh/authorized_keys |
| 64 | +chmod 700 ~/.ssh |
| 65 | +chmod 600 ~/.ssh/authorized_keys |
45 | 66 | # private key 를 GitHub secret 에 등록: |
46 | 67 | gh secret set N100_SSH_KEY < ~/.ssh/n100_gh_deploy |
47 | 68 | gh secret set N100_HOST --body "<ip-or-domain>" |
48 | 69 | gh secret set N100_USER --body "<user>" |
49 | 70 | ``` |
50 | 71 |
|
51 | | -Secrets 가 아직 없으면 GitHub Actions 는 GHCR 이미지 build/push 까지만 성공 처리하고, SSH deploy job 은 skip 된다. N100 준비 후 위 3개 secret 을 넣으면 다음 dev push 부터 staging deploy 까지 진행된다. |
| 72 | +Secrets 가 등록되어 있지 않으면 main push 도 image build/push 까지만 성공 처리하고, SSH deploy job 은 skip 된다. |
52 | 73 |
|
53 | 74 | ### (옵션) prod environment 보호 |
54 | 75 |
|
55 | | -main 으로의 자동 배포 전에 수동 승인이 필요하면 Settings → Environments → "prod" 생성 후 "Required reviewers" 본인 추가. |
| 76 | +main → prod 자동 배포 전에 수동 승인이 필요하면 Settings → Environments → `prod` 생성 후 "Required reviewers" 본인 추가. |
| 77 | + |
| 78 | +### 수동 트리거 |
56 | 79 |
|
57 | | -## 1. 수동 배포 (CI/CD 우회) — 첫 배포 또는 디버깅 |
| 80 | +특정 ref 를 prod 로 즉시 배포: |
58 | 81 |
|
59 | 82 | ```bash |
60 | | -# 호스트에 git clone |
61 | | -git clone https://github.com/boostcampwm-snu-2026-1/murun-peterabcd.git |
62 | | -cd murun-peterabcd/deploy |
| 83 | +gh workflow run deploy.yml --ref main -f target=prod |
| 84 | +``` |
63 | 85 |
|
64 | | -# compose용 env |
65 | | -cp .env.example .env |
66 | | -# (도메인 붙기 전엔 DOMAIN=:80 그대로 두기) |
| 86 | +## 1. 수동 배포 (CI/CD 우회) — 디버깅 / 첫 배포 |
67 | 87 |
|
68 | | -# compose 변수와 app 환경변수를 같은 파일에서 읽음 |
69 | | -# 기본 수동 실행은 .env, staging/prod 분리는 MURUN_ENV_FILE 로 지정 |
| 88 | +```bash |
| 89 | +cd ~/murun-peterabcd/deploy |
70 | 90 |
|
| 91 | +# 위의 .env.prod 가 이미 준비됐다고 가정 |
| 92 | +export MURUN_ENV_FILE=.env.prod |
71 | 93 |
|
72 | | -# 빌드 + 기동 |
73 | | -docker compose up -d --build |
| 94 | +# 이미지가 없거나 강제로 rebuild 하고 싶을 때 (오래 걸림) |
| 95 | +docker compose -p murun-prod --env-file .env.prod up -d --build |
74 | 96 |
|
75 | | -# 헬스체크 |
76 | | -curl -fsS http://localhost/ && echo OK |
| 97 | +# 이미지를 GHCR 에서 pull 만 (보통은 이쪽) |
| 98 | +docker compose -p murun-prod --env-file .env.prod pull |
| 99 | +docker compose -p murun-prod --env-file .env.prod up -d |
77 | 100 | ``` |
78 | 101 |
|
79 | | -## 2. 업데이트 배포 |
| 102 | +헬스체크: |
80 | 103 |
|
81 | 104 | ```bash |
82 | | -cd murun-peterabcd |
83 | | -git pull |
84 | | -cd deploy |
85 | | -MURUN_ENV_FILE=.env docker compose --env-file .env up -d --build |
86 | | -docker image prune -f |
| 105 | +docker compose -p murun-prod --env-file .env.prod ps |
| 106 | +curl -fsS http://localhost/api/health && echo OK |
87 | 107 | ``` |
88 | 108 |
|
89 | | -## 3. staging + prod 동시 운영 |
90 | | - |
91 | | -같은 디렉터리에 `.env.staging`, `.env.prod` 를 별도로 두고 project name 으로 분리. |
| 109 | +## 2. 업데이트 배포 (수동) |
92 | 110 |
|
93 | 111 | ```bash |
94 | | -# staging (포트 8080; .env.staging 안에 CADDY_HTTP_PORT=8080 권장) |
95 | | -MURUN_ENV_FILE=.env.staging docker compose -p murun-staging --env-file .env.staging up -d --build |
96 | | - |
97 | | -# prod (포트 80) |
98 | | -MURUN_ENV_FILE=.env.prod docker compose -p murun-prod --env-file .env.prod up -d --build |
| 112 | +cd ~/murun-peterabcd |
| 113 | +git pull |
| 114 | +cd deploy |
| 115 | +MURUN_ENV_FILE=.env.prod docker compose -p murun-prod --env-file .env.prod pull |
| 116 | +MURUN_ENV_FILE=.env.prod docker compose -p murun-prod --env-file .env.prod up -d |
| 117 | +docker image prune -f |
99 | 118 | ``` |
100 | 119 |
|
101 | | -볼륨은 project name prefix 가 자동으로 붙어 분리됨 (`murun-staging_data` vs `murun-prod_data`). |
102 | | - |
103 | | -## 4. 데이터 위치 (백업 대상) |
| 120 | +## 3. 데이터 위치 (백업 대상) |
104 | 121 |
|
105 | 122 | | docker volume | 호스트 경로 (Linux 기본) | 용도 | |
106 | 123 | |---------------|--------------------------|------| |
107 | | -| `murun_data` | `/var/lib/docker/volumes/murun_data/_data` | SQLite 파일 | |
108 | | -| `murun_uploads` | `/var/lib/docker/volumes/murun_uploads/_data` | 단체사진 원본 + next/image 캐시 | |
109 | | -| `murun_caddy_data` | `/var/lib/docker/volumes/murun_caddy_data/_data` | Let's Encrypt 인증서 | |
| 124 | +| `murun-prod_data` | `/var/lib/docker/volumes/murun-prod_data/_data` | SQLite 파일 | |
| 125 | +| `murun-prod_uploads` | `/var/lib/docker/volumes/murun-prod_uploads/_data` | 단체사진 원본 + next/image 캐시 | |
| 126 | +| `murun-prod_caddy_data` | `/var/lib/docker/volumes/murun-prod_caddy_data/_data` | Let's Encrypt 인증서 | |
110 | 127 |
|
111 | | -백업 cron 은 후속 PR 에서 (`scripts/backup.sh`). |
| 128 | +백업 cron 은 후속 작업 (`scripts/backup.sh`). |
112 | 129 |
|
113 | | -## 5. 롤백 |
| 130 | +## 4. 롤백 |
114 | 131 |
|
115 | 132 | ```bash |
116 | 133 | # 직전 이미지 태그 확인 |
117 | 134 | docker images ghcr.io/boostcampwm-snu-2026-1/murun-peterabcd --format '{{.Tag}}\t{{.CreatedAt}}' |
118 | 135 |
|
119 | | -# 특정 태그로 되돌리기 |
120 | | -IMAGE_TAG=<tag> MURUN_ENV_FILE=.env.staging docker compose --env-file .env.staging up -d |
| 136 | +# 특정 태그로 되돌리기 (예: 직전 prod 빌드) |
| 137 | +IMAGE_TAG=prod-<sha> MURUN_ENV_FILE=.env.prod docker compose -p murun-prod --env-file .env.prod up -d |
121 | 138 | ``` |
122 | 139 |
|
123 | | -## 6. 트러블슈팅 |
| 140 | +## 5. 트러블슈팅 |
124 | 141 |
|
125 | 142 | ```bash |
126 | 143 | # 컨테이너 상태 |
127 | | -docker compose ps |
| 144 | +docker compose -p murun-prod --env-file .env.prod ps |
128 | 145 |
|
129 | 146 | # app 로그 |
130 | | -docker compose logs -f app |
| 147 | +docker compose -p murun-prod --env-file .env.prod logs -f app |
131 | 148 |
|
132 | | -# caddy 로그 |
133 | | -docker compose logs -f caddy |
| 149 | +# caddy 로그 (인증서 발급 / TLS 문제) |
| 150 | +docker compose -p murun-prod --env-file .env.prod logs -f caddy |
134 | 151 |
|
135 | 152 | # 강제 재기동 |
136 | | -docker compose restart app |
| 153 | +docker compose -p murun-prod --env-file .env.prod restart app |
137 | 154 | ``` |
138 | 155 |
|
139 | | -## 7. 도메인 붙인 후 (예: duckdns) |
| 156 | +## 6. DuckDNS HTTPS 활성 절차 |
| 157 | + |
| 158 | +1. DuckDNS 에서 `murun.duckdns.org` 발급 + N100 공인 IP 등록 (DDNS 클라이언트로 자동 갱신 권장) |
| 159 | +2. 공유기에서 외부 80/443 → N100 내부 IP 의 80/443 포트포워딩 |
| 160 | +3. `deploy/.env.prod` 의 값 확인: |
| 161 | + ```env |
| 162 | + DOMAIN=murun.duckdns.org |
| 163 | + NEXT_PUBLIC_APP_URL=https://murun.duckdns.org |
| 164 | + AUTH_URL=https://murun.duckdns.org |
| 165 | + ``` |
| 166 | +4. `docker compose -p murun-prod --env-file .env.prod up -d` (Caddy 재기동 → Let's Encrypt 자동 발급) |
| 167 | +5. `docker compose ... logs caddy` 에서 "certificate obtained for murun.duckdns.org" 확인 |
| 168 | + |
| 169 | +## 7. Staging 환경 (옵션, N100 외 어딘가에서 돌릴 때) |
| 170 | + |
| 171 | +기본 정책상 N100 에서는 staging 을 돌리지 않는다. 필요하면 다른 호스트/포트로: |
| 172 | + |
| 173 | +```bash |
| 174 | +MURUN_ENV_FILE=.env.staging \ |
| 175 | + CADDY_HTTP_PORT=8080 CADDY_HTTPS_PORT=8443 \ |
| 176 | + docker compose -p murun-staging --env-file .env.staging up -d --build |
| 177 | +``` |
140 | 178 |
|
141 | | -1. duckdns 에서 `murun.duckdns.org` 발급 + 현재 공인 IP 등록 |
142 | | -2. 공유기 80/443 포트포워딩 → N100 내부 IP |
143 | | -3. `deploy/.env` 에 `DOMAIN=murun.duckdns.org` 로 변경 |
144 | | -4. `docker compose up -d` (Caddy 재기동되며 Let's Encrypt 자동 발급) |
145 | | -5. 첫 인증서 발급 후 `docker compose logs caddy` 에서 "certificate obtained" 확인 |
| 179 | +이 경우에도 GH Actions 자동 배포 대상은 아님. 항상 수동. |
0 commit comments