❄️ snow 사용 가이드

한 줄 명령으로 Astro 사이트를 생성 → 빌드 → 배포 → 도메인 연동까지 자동화하는 셀프호스팅 PaaS — 하얀눈.

핵심 한 줄 요약
평소엔 snow 명령 하나만 씁니다. CMS와 연동하면 글 발행만 해도 사이트가 자동 재배포됩니다(섹션 C). 관리 화면은 하얀눈 DASHBOARD.
구 명령어 deploy-astro 는 별칭으로 계속 동작합니다.

A. 처음 1번만 — 다른 PC 준비

아래 4가지를 한 번 갖추면 그 PC는 영구히 준비 완료. 다시 안 합니다.

1

코드 받기 — 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 는 폐기)

2

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 등이 보이면 성공
3

설치 (Node + 명령어 등록)

OS방법
Linux / Mac cd ~/astro-platform/cli && ./setup-devpc.sh
node·kubectl·엔진 확인 + npm install + PATH 등록까지 자동
Windows cd platform\clinpm installnpm link
npm link 가 전역 snow(+별칭 deploy-astro) 명령을 만들어 줍니다

확인: snow --help 가 도움말을 출력하면 성공.

4

컨테이너 엔진 + 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 글만 바뀐 경우)
--ssrSSR로 강제 (자동 감지 무시)
--proxied / --directTLS 방식 강제 (기본은 도메인 설정 따름)

C. CMS 자동 재배포 — 글만 쓰면 사이트가 갱신

SSG는 콘텐츠가 빌드 시점에 이미지에 박히므로, CMS(cms.godd.app)에 글을 발행하면 클러스터 안에서 자동 재빌드가 돌아야 합니다. 이 파이프라인은 이미 구축돼 있고, 사이트마다 아래 온보딩만 하면 편입됩니다.

CMS 글 발행 → n8n webhook → 하얀눈 DASHBOARD /redeploy/<slug> → 빌더 Job: git clone(private OK) → CMS fetch → 이미지 빌드/푸시 → 롤아웃 → 1~2분 뒤 사이트에 새 글 반영 (빌드 상태는 대시보드 배지로 확인)

사이트 온보딩 4단계 (사이트당 1회)

1
repo — Astro 프로젝트를 GitHub private repo 로 push. 빌드 시 CMS 를 읽도록 CMS_URL/CMS_TOKEN 환경변수 사용(예: lang_boost, youtip 구조). 공유 PAT 의 Repository access 에 repo 추가.
2
CMS 테넌트 — cms 의 테넌트 생성 스크립트로 테넌트 + read 토큰 발급 (⚠️ 반드시 클러스터 CMS 기준 — 로컬 CMS 토큰은 DB가 달라 401).
3
Secret — 빌드 시 주입될 CMS 접속값:
kubectl -n goddapp create secret generic cms-<이름>-godd-app \
  --from-literal=CMS_URL=https://cms.godd.app \
  --from-literal=CMS_TOKEN=<read토큰>
4
배포대시보드의 ➕ New Astro Site 폼 (이름 + 소스 git URL) 제출. 이후 글 발행은 전부 자동.
CLI 로 올린 기존 사이트는 source 주석만 1회 보정: kubectl -n goddapp annotate deploy/<slug> astro.platform/source=<git URL> --overwrite

n8n 워크플로 (한 번만 import — 모든 사이트 공용)

파일 (platform repo n8n/)역할
redeploy-site-webhook.jsonCMS 발행 webhook → 해당 사이트만 재배포. payload {"tenant":"lang"} → slug 자동 계산
redeploy-all-scheduled.json매일 06:00 KST 전체 사이트 순차 재배포 (안전망)

n8n.godd.app → Workflows → Import from File → Active 토글. 사이트가 늘어도 워크플로는 안 늘어납니다(테넌트명 = 사이트명 컨벤션).

private repo clone 은 공유 Secret git-creds(GitHub PAT, Contents:Read-only)로 처리 — 토큰은 URL/로그에 노출되지 않음.

⚙️ snow new 가 내부에서 하는 순서

snow new myblog │ ├─ 감지 : 이 프로젝트가 SSG(정적)냐 SSR(서버)냐 자동 판단 ├─ 빌드 : docker/podman 으로 이미지 빌드 (CMS_* env 는 --build-arg 로 주입) ├─ 푸시 : 192.168.0.4:30000 레지스트리로 업로드 ├─ 생성 : Deployment / Service / Ingress YAML 자동 작성 ├─ 배포 : kubectl 로 클러스터에 apply (← kubeconfig 사용) └─ 도메인 : external-dns 가 myblog.godd.app DNS 생성 + TLS 자동 발급 ↓ https://myblog.godd.app 완성 + 대시보드 목록에 등장

②③은 docker, ⑤는 kubeconfig가 필요 → 그래서 “처음 1번 준비”의 3·4단계가 있는 것. 클러스터 안 자동 재빌드(섹션 C)는 같은 일을 Kaniko 빌더 Job 이 수행합니다.

⚠️ 자주 하는 실수

① 명령어 이름snow ✅ (구 deploy-astro 도 별칭으로 동작, astro-destro ❌)
② cli 폴더만 복사 — “템플릿 없음” 에러. repo 전체를 clone 하세요.
③ Windows에서 setup-devpc.sh 실행 — bash 스크립트라 PowerShell에선 안 됨. npm link를 쓰세요.
④ 로컬 CMS 토큰을 클러스터에 사용 — 클러스터 CMS는 DB가 달라 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 환경에서 실제로 처음 셋업할 때 순서대로 만난 문제들과 해결.

1

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
2

빌드는 됐는데 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(리눅스 쪽)으로 옮길 것.

3

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
4

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  → 목록 나오면 완성
🔒 더 좁히려면 LAN만 허용: --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 deniedWSL에서 Windows node 사용nvm으로 리눅스 node 설치
snow: not recognizednpm link 안 함 / PATHcd cli && npm link 재실행
apply 단계 실패: undefinedkubectl 미설치kubectl 설치 + /usr/local/bin
k3s.yaml 읽을 수 없음노트북에서 서버용 스크립트 실행스크립트는 서버에서 실행
6443: no route to hostfirewalld가 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 사이트는 글 발행만 — 반복하면 됩니다.