❄️ snow 사용 가이드
한 줄 명령으로 Astro 사이트를 생성 → 빌드 → 배포 → 도메인 연동까지 자동화하는 셀프호스팅 PaaS — 하얀눈.
평소엔
snow 명령 하나만 씁니다. CMS와 연동하면 글 발행만 해도 사이트가 자동 재배포됩니다(섹션 C).
관리 화면은 하얀눈 DASHBOARD.
deploy-astro 는 별칭으로 계속 동작합니다.A. 처음 1번만 — 다른 PC 준비
아래 4가지를 한 번 갖추면 그 PC는 영구히 준비 완료. 다시 안 합니다.
코드 받기 — drinkcode/platform repo (진실원)
platform 코드는 이제 GitHub private repo로 관리됩니다. rsync 복사 대신 clone:
git clone https://github.com/drinkcode/platform.git ~/astro-platform
# private repo 라 GitHub 로그인(또는 PAT) 필요
이후 업데이트는 git pull 한 줄. (구 방식이던 k3s/platform 폴더 rsync 는 폐기)
kubeconfig 받기 (클러스터 접속 “차 키”)
이 PC의 kubectl이 어느 클러스터에 배포할지 알려주는 설정 파일. 서버에서 1번 생성 후 복사합니다.
# 서버에서:
~/Documents/k3s/platform/cli/make-remote-kubeconfig.sh # → ~/k3s-remote.yaml
# 개발 PC로 복사: (Windows는 C:\Users\<나>\.kube\config 위치)
scp <user>@192.168.0.4:~/k3s-remote.yaml ~/.kube/config
kubectl get ns # goddapp 등이 보이면 성공
설치 (Node + 명령어 등록)
| OS | 방법 |
|---|---|
| Linux / Mac | cd ~/astro-platform/cli && ./setup-devpc.shnode·kubectl·엔진 확인 + npm install + PATH 등록까지 자동 |
| Windows | cd platform\cli → npm install → npm linknpm link 가 전역 snow(+별칭 deploy-astro) 명령을 만들어 줍니다 |
확인: snow --help 가 도움말을 출력하면 성공.
컨테이너 엔진 + insecure 레지스트리
이미지를 빌드/푸시하려면 docker(또는 podman)가 필요하고, 평문 HTTP 레지스트리를 허용해야 합니다.
| 환경 | 설정 |
|---|---|
| Linux + docker | /etc/docker/daemon.json 에 {"insecure-registries":["192.168.0.4:30000"]} 후 sudo systemctl restart docker |
| Linux + podman | 보통 불필요(CLI가 --tls-verify=false 자동). 막히면 registries.conf.d 에 insecure 등록 |
| Mac / Windows (Docker Desktop) | Settings → Docker Engine JSON에 "insecure-registries":["192.168.0.4:30000"] 추가 → Apply & Restart |
B. 매번 사용 — 이것만 반복
cd ~/my-astro-project # 만든 Astro 프로젝트 폴더
snow new myblog # 이 한 줄 끝 → https://myblog.godd.app
전체 명령
| 명령 | 설명 |
|---|---|
snow new <이름> | SSG/SSR 자동 감지 → 빌드 → 배포 (처음 올릴 때) |
snow redeploy <이름> | 코드 수정 후 재빌드 + 롤아웃 |
snow list | 도메인별 사이트 목록 |
snow logs <이름> | 로그 스트리밍 |
snow destroy <이름> | 워크로드+ingress 삭제 (DNS는 자동 정리) |
snow deploy <이름> --dry-run | 빌드 없이 생성될 YAML 미리보기 |
옵션
| 옵션 | 의미 |
|---|---|
--domain <존> | 대상 도메인 (생략 시 기본 godd.app) |
--subdomain <sub> | 서브도메인 (생략 시 이름과 동일) |
--source <git URL> | 소스 repo 기록 — 클러스터가 스스로 재빌드(자동 재배포)할 때 clone 할 주소. CMS 자동화에 필수 |
--force | 내용이 같아도 강제로 새 태그/롤아웃 (CMS 글만 바뀐 경우) |
--ssr | SSR로 강제 (자동 감지 무시) |
--proxied / --direct | TLS 방식 강제 (기본은 도메인 설정 따름) |
C. CMS 자동 재배포 — 글만 쓰면 사이트가 갱신
SSG는 콘텐츠가 빌드 시점에 이미지에 박히므로, CMS(cms.godd.app)에 글을 발행하면 클러스터 안에서 자동 재빌드가 돌아야 합니다. 이 파이프라인은 이미 구축돼 있고, 사이트마다 아래 온보딩만 하면 편입됩니다.
사이트 온보딩 4단계 (사이트당 1회)
CMS_URL/CMS_TOKEN 환경변수 사용(예: lang_boost, youtip 구조).
공유 PAT 의 Repository access 에 repo 추가.
kubectl -n goddapp create secret generic cms-<이름>-godd-app \
--from-literal=CMS_URL=https://cms.godd.app \
--from-literal=CMS_TOKEN=<read토큰>
kubectl -n goddapp annotate deploy/<slug> astro.platform/source=<git URL> --overwriten8n 워크플로 (한 번만 import — 모든 사이트 공용)
파일 (platform repo n8n/) | 역할 |
|---|---|
redeploy-site-webhook.json | CMS 발행 webhook → 해당 사이트만 재배포. payload {"tenant":"lang"} → slug 자동 계산 |
redeploy-all-scheduled.json | 매일 06:00 KST 전체 사이트 순차 재배포 (안전망) |
n8n.godd.app → Workflows → Import from File → Active 토글. 사이트가 늘어도 워크플로는 안 늘어납니다(테넌트명 = 사이트명 컨벤션).
git-creds(GitHub PAT, Contents:Read-only)로 처리 — 토큰은 URL/로그에 노출되지 않음.⚙️ snow new 가 내부에서 하는 순서
②③은 docker, ⑤는 kubeconfig가 필요 → 그래서 “처음 1번 준비”의 3·4단계가 있는 것. 클러스터 안 자동 재빌드(섹션 C)는 같은 일을 Kaniko 빌더 Job 이 수행합니다.
⚠️ 자주 하는 실수
snow ✅ (구 deploy-astro 도 별칭으로 동작, astro-destro ❌)npm link를 쓰세요.401 Invalid token. 토큰은 클러스터 CMS에서 발급.📊 하얀눈 DASHBOARD
주소 : https://dash.godd.app | 계정 : admin (비밀번호는 별도 전달)
도메인별 사이트 목록 / 상태(🟢🔴 + 진단 배지) / 빌드 배지(빌드 중·✓·✗ — 클릭하면 빌드 로그) / logs / redeploy / SEO·글 목록 / ➕ New Astro Site.
redeploy 를 누르면 빌드 로그가 자동으로 열리고 완료/실패가 표시됩니다. 자동화용 API:
GET /sites · POST /redeploy/<slug> · GET /job/<name> · GET /builds/<slug>
🧰 트러블슈팅 — WSL 실전 셋업 기록
Windows + WSL2 환경에서 실제로 처음 셋업할 때 순서대로 만난 문제들과 해결.
node: Permission denied
증상
/mnt/c/Program Files/nodejs/snow: exec: node: Permission denied
원인 — WSL(리눅스)에서 Windows용 node(/mnt/c/...)를 실행하려다 막힘.
해결 — WSL 안에 리눅스 node 설치:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install --lts
which node # /home/<나>/.nvm/... 로 바뀌면 OK (더이상 /mnt/c 아님)
# 그 뒤 cli 폴더에서: rm -rf node_modules && npm install && npm link
빌드는 됐는데 apply 단계에서 “✗ 오류: … 실패: undefined”
원인 — kubectl이 WSL에 설치돼 있지 않음(명령을 못 찾아 에러가 undefined로 표시됨).
해결 — kubectl 설치 후 PATH로 이동(받기만 하면 안 됨):
curl -LO "https://dl.k8s.io/release/v1.30.3/bin/linux/amd64/kubectl"
sudo install -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client
⚠️ /mnt/c에서 직접 실행하면 권한 문제 → 반드시 /usr/local/bin(리눅스 쪽)으로 옮길 것.
kubeconfig는 서버에서 만들어 가져온다
핵심 — make-remote-kubeconfig.sh는 서버(192.168.0.4)에서 실행. 노트북엔 k3s 설정
(/etc/rancher/k3s/k3s.yaml)이 없어서 sudo여도 “읽을 수 없음”이 납니다.
# 서버에서 1번:
~/Documents/k3s/platform/cli/make-remote-kubeconfig.sh # → ~/k3s-remote.yaml
# WSL에서 가져오기:
mkdir -p ~/.kube
scp drinkcode@192.168.0.4:~/k3s-remote.yaml ~/.kube/config
kubectl get ns
kubectl get ns → no route to host (192.168.0.4:6443)
증상
Unable to connect to the server: dial tcp 192.168.0.4:6443: connect: no route to host
원인 — 서버 firewalld가 6443 포트를 외부에서 막음(22=ssh는 열려 있어 scp는 됨). 해결 — 서버에서 6443 열기(root 필요):
ssh drinkcode@192.168.0.4
sudo firewall-cmd --permanent --add-port=6443/tcp
sudo firewall-cmd --reload
exit
# 다시 WSL: kubectl get ns → 목록 나오면 완성
--add-rich-rule='rule family="ipv4" source address="192.168.0.0/24" port port="6443" protocol="tcp" accept'.
API는 kubeconfig 인증서가 있어야 동작하므로 신뢰 LAN 개방은 안전한 편.증상별 빠른 표
| 증상 | 원인 | 해결 |
|---|---|---|
exec: node: Permission denied | WSL에서 Windows node 사용 | nvm으로 리눅스 node 설치 |
snow: not recognized | npm link 안 함 / PATH | cd cli && npm link 재실행 |
apply 단계 실패: undefined | kubectl 미설치 | kubectl 설치 + /usr/local/bin |
k3s.yaml 읽을 수 없음 | 노트북에서 서버용 스크립트 실행 | 스크립트는 서버에서 실행 |
6443: no route to host | firewalld가 6443 차단 | 서버에서 firewall-cmd --add-port=6443/tcp |
| 이미지 push 실패(HTTP) | insecure registry 미등록 | docker daemon.json / podman --tls-verify=false |
CMS fetch 401 Invalid token | 로컬 CMS에서 발급한 토큰 | 클러스터 CMS에서 테넌트/토큰 재발급 → cms-<slug> Secret 교체 |
| 자동 재배포가 빈 사이트 빌드 | source 주석이 local | --source 로 배포하거나 annotate 로 git URL 보정 |
클러스터 빌드만 npm ci 에서Class extends value undefined | 구버전 빌더(node 가 /usr/local)와 사이트 베이스의 kaniko rootfs 충돌 — 2026-06-13 빌더 격리로 해결됨 | 빌더 이미지를 최신으로 재빌드/push. 이후 사이트는 임의의 node 버전(20/22/…) 사용 가능 |
한 번 모두 통과하면, 이후엔 snow new <이름> 한 줄 — CMS 사이트는 글 발행만 — 반복하면 됩니다.