서론[주제 소개]: 오늘은 "내 소중한 소스 코드는 감추면서, GitHub의 무료 CI/CD 파이프라인 혜택은 100% 누리는" 꿈의 아키텍처에 대해 이야기해 보려 합니다. 많은 개인 개발자들이 포트폴리오 관리를 위해 코드를 공개하고 싶어 하지만, 핵심 비즈니스 로직이나 민감한 정보 때문에 망설이는 경우가 많습니다.
[왜 작성하였는가?]: 이 글을 통해 여러분은 **Private Repo(비공개)**에서 안전하게 개발하면서, 빌드된 결과물만 **Public Repo(공개)**로 보내 무료로 도커 이미지를 굽고 배포하는 실전 노하우를 얻어가실 수 있습니다. 서버 비용 절감과 보안이라는 두 마리 토끼를 동시에 잡는 방법을 공개합니다.2-Track 배포 전략: 핵심 개념 파헤치기Next.js Standalone Mode (독립 모드): Next.js 애플리케이션을 배포할 때, 거대한 node_modules 전체가 아니라 실행에 필요한 최소한의 파일만 추려서 경량화된 폴더를 만들어주는 기능입니다.
왜 중요한가요?: 이 기능을 사용하면 소스 코드 원본 없이도 서버를 실행할 수 있게 되어, 배포 파일의 용량을 획기적으로 줄이고 보안성을 높일 수 있습니다. (마치 껍질 깐 알맹이와 같습니다.)
GitHub Actions (Public vs Private): GitHub의 자동화 도구인 Actions는 Private 저장소에서는 월 2,000분까지만 무료지만, Public 저장소에서는 무제한 무료입니다.
놓치기 쉬운 점: 많은 개발자가 비공개 프로젝트를 할 때 Actions 사용량을 걱정합니다. 하지만 "빌드 결과물"만 공개 저장소로 보낸다면, 무제한 무료 혜택을 합법적으로(?) 누릴 수 있습니다.
GHCR (GitHub Container Registry): 도커 이미지를 저장하는 GitHub의 창고입니다. 이것 역시 Public 패키지로 설정하면 저장 용량과 트래픽이 무료입니다.
실무 적용 시 고려사항: 도커 허브(Docker Hub)보다 보안 설정(Token 권한 관리)이 조금 더 까다롭지만, GitHub 생태계 안에서 모든 것을 해결할 수 있다는 강력한 장점이 있습니다.GitHub 정책: 공식 가이드라인 & 권장 사항[공식 소스]: GitHub Actions Billing Policy, Next.js Output File Tracing
[주요 권장 사항]:
민감 정보 분리: 환경 변수(.env)나 키 파일은 절대로, 어떤 형태로든 Public 저장소에 올라가서는 안 됩니다. 반드시 실행 시점(Runtime)에 주입해야 합니다.
이미지 최적화: 도커 빌드 시간을 줄이고 저장소 용량을 효율적으로 쓰기 위해 .dockerignore와 멀티 스테이지 빌드를 적극 활용해야 합니다.
권한 최소화 (Least Privilege): 이미지를 다운로드(Pull)하는 토큰은 오직 read:packages 권한만 부여하여, 만약 토큰이 유출되더라도 코드가 수정되거나 삭제되는 일을 막아야 합니다.시크릿 CI/CD: 실무 적용 마스터 플랜[개발은 비공개로, 배포는 공개로]: 제가 직접 구축한 **"소스 코드 세탁(?) 및 배포 파이프라인"**의 3단계 시나리오입니다.[첫 번째 단계: Next.js 경량화 설정]
무엇을 하는가?: Next.js가 빌드할 때 도커 친화적인 결과물을 뱉어내도록 설정합니다.
어떻게 하는가?:
next.config.ts
(또는 js) 파일에 output: 'standalone' 옵션을 추가합니다.
typescript
// next.config.ts
const nextConfig: NextConfig = {
output: 'standalone', // 핵심: 이 한 줄이 매직을 부립니다.
};
export default nextConfig;
성공 점검: npm run build 실행 후 .next/standalone 폴더가 생성되었는지 확인합니다.
실수 방지 팁: 이 설정 없이는 node_modules 전체를 복사해야 해서 이미지가 1GB를 훌쩍 넘기기 쉽습니다. 꼭 켜세요!
[두 번째 단계: '배포용 껍데기' 생성 자동화]
무엇을 하는가?: 원본 소스는 놔두고, 빌드된 알맹이만 추출해서 공개 저장소로 보낼 준비를 하는 스크립트(deploy-public.sh)를 짭니다.
어떻게 하는가?: 빌드 후 생성된 파일들을 deploy-public/ 같은 임시 폴더에 모으고, 그 안에 Dockerfile과 GitHub workflow 파일을 동적으로 생성해서 넣습니다.
bash
# (스크립트 로직 요약)
# 1. 빌드
npm run build
# 2. 필수 파일 복사 (여기가 제일 중요!!!)
# standalone만 복사하면 안 되고, static 폴더와 필수 manifest 파일들도 챙겨야 함
cp -r .next/standalone/* ./deploy-public/
cp -r .next/static ./deploy-public/.next/
cp .next/BUILD_ID ./deploy-public/.next/ # ← 이거 없으면 에러 남
cp -r .next/server ./deploy-public/.next/ # ← 이것도 필수
# 3. Dockerfile 및 Action 파일 생성 (echo 커맨드로 파일 생성)
# ...
# 4. 공개 레포로 강제 푸시 (Force Push)
git push -f origin main
성공 점검: 스크립트 실행 후 공개 저장소(Public Repo)에 가봤을 때, 소스 코드는 없고, 난독화된 파일들과 Dockerfile만 올라와 있으면 성공입니다.
실수 방지 팁: .next 폴더 구조를 복사할 때 경로가 꼬이기 쉽습니다. 특히 BUILD_ID나 server 폴더가 누락되면, 나중에 도커 실행 시 "파일을 찾을 수 없다"며 뻗어버립니다.
[세 번째 단계: 서버에서 이미지 풀(Pull) & 실행]
무엇을 하는가?: 내 운영 서버(VPS 등)에서 공개 저장소에 올라온 이미지를 다운받아 실행합니다.
어떻게 하는가?: GHCR에 로그인한 뒤 이미지를 받고, 환경변수를 주입하며 실행합니다.
bash
# 1. GHCR 로그인 (토큰 필요)
docker login ghcr.io -u [Github아이디] -p [토큰]
# 2. 실행 (환경변수는 여기서 주입!)
docker run -d \
-e NEXT_PUBLIC_SECRET_KEY="내_진짜_키" \
ghcr.io/[아이디]/[이미지명]:latest
성공 점검: docker logs 확인 시 에러 없이 Ready in ... 메시지가 뜨고 사이트 접속이 되면 완료!생생한 성공 & 실패 사례 분석[성공 사례: 무자본 1인 개발자의 배포 혁명]
배경: 개인 프로젝트라 서버 비용을 아껴야 하는데, 포트폴리오는 보여주고 싶고 소스는 공개하기 싫은 딜레마.
적용 전략: Private Repo에서 개발하고, Public Repo를 '이미지 빌드 셔틀'로만 사용하는 전략 채택.
핵심 결과: GitHub Actions 비용 0원, GHCR 비용 0원 달성. GitHub 프로필에는 활발한 커밋 기록(초록잔디)이 남으면서도, 실제 코드는 완벽하게 보호됨.
성공 요인 분석: 소스 코드(Source)와 배포 아티팩트(Artifact)를 철저히 분리한 구조적 설계가 주효함.
[실패 사례: "매니페스트가 왜 없어?" 대소동]
배경: standalone 폴더만 믿고 덜컥 복사해서 배포함.
실패 요인: Next.js Standalone 모드는 실행 파일(server.js)만 줄 뿐, 라우팅 정보가 담긴 routes-manifest.json이나 pages-manifest.json 같은 메타데이터 파일들은 .next 폴더 원본에 남아있었음. 이를 누락하고 이미지로 구워버림.
얻은 교훈: "Standalone은 만능이 아니다." 배포 스크립트를 짤 때 .next 폴더의 하위 구조(특히 server, BUILD_ID)를 꼼꼼하게 챙겨서 올바른 위치에 복사해 넣는 디테일이 생명임을 깨달음.자주 묻는 질문(FAQ)Q1. 공개 레포에 올라간 코드를 누가 훔쳐보면 어떡하나요? A1. 올라가는 코드는 Webpack으로 번들링(난독화/압축)된 기계 친화적 코드입니다. 사람이 읽고 유지보수하는 것은 불가능에 가까우며, 중요한 환경 변수(Env)만 포함하지 않았다면 보안상 안전합니다.
Q2. Docker Hub 대신 왜 GHCR을 쓰나요? A2. GitHub Actions와의 연동성이 압도적으로 좋고(별도 로그인 불필요), GitHub UI에서 패키지를 바로 관리할 수 있어 편리하기 때문입니다. 물론 Docker Hub를 써도 무방합니다.
Q3. 이 방식이 유료인가요? A3. 아니요! Public Repository를 활용하는 이 방식은 GitHub 정책상 100% 무료입니다. (단, 개인 계정 기준)
Q4. 매번 스크립트 실행이 귀찮지 않나요? A4. 그래서 deploy-public.sh 같은 셸 스크립트로 "원클릭 배포"를 만들어두는 것이 중요합니다. 개발자는 코딩 끝나고 명령어 한 줄만 치면 됩니다.
Q5. 데이터베이스 정보는 어디에 넣나요? A5. 절대 코드나 도커 이미지에 넣지 마세요. 서버에서 docker run 할 때 -e 옵션이나 .env 파일을 통해 주입해야 안전합니다.
Q6. 서버 업데이트는 어떻게 하나요? A6. 서버에서 docker pull로 새 이미지를 받고, 기존 컨테이너를 내린 후(stop/rm) 다시 run 하면 됩니다. (Docker Compose나 Swarm을 쓰면 무중단 배포도 가능합니다.)시크릿 CI/CD: 실전 운영 팁 & 주의사항[팁 1: 토큰 관리는 생명]: 서버에서 이미지를 받을 때 사용하는 GitHub Token(PAT)은 반드시 주기적으로 갱신하고, 권한은 read:packages로 최소화하세요.
[팁 2: 구버전 이미지 청소]: 배포가 잦으면 GHCR에 이미지가 쌓입니다. GitHub Actions를 통해 오래된 이미지를 주기적으로 삭제하는 워크플로우를 추가하면 더 깔끔합니다.
[팁 3: Force Push 활용]: 공개 레포의 커밋 히스토리가 길어질 필요가 없습니다. 매 배포마다 git push --force를 사용하여 히스토리를 덮어쓰면, "최신 상태의 스냅샷"만 깔끔하게 유지할 수 있습니다.결론 및 다음 단계[핵심 요약]: 우리는 지금 Private Repo(개발) → Public Repo(빌드/배포) 라는 이원화 전략을 통해 보안과 비용 효율성을 극대화했습니다. Next.js의 standalone 기능과 도커의 유연함을 결합하여 "소스 없는 배포"를 실현했습니다.
[다음 단계 제안]: 이제 이 시스템 위에 Docker Swarm이나 **K3s(Kubernetes)**를 얹어 무중단 배포 시스템을 완성해 보세요. "죽지 않는 웹 서비스"를 운영하는 진정한 데브옵스 엔지니어로 거듭날 수 있습니다.