데이터를 불러오는 중입니다...
데이터를 불러오는 중입니다...
사내 Jenkins 기반 배포 환경을 GitHub Actions 중심의 표준화된 CI/CD 및 개발 하네스로 전환한 v2 프로젝트입니다. v1은 혼자 테스트하며 하네스 구조와 인프라 선택지를 검증한 성격이 강했다면, v2는 실제 사내 프로젝트에 적용하고 팀 피드백을 받는 운영 도구로 설계했습니다. 초기 목표는 Jenkins 서버를 줄여 비용을 낮추는 것이었지만, 작업을 진행하면서 단순히 배포 도구만 바꾸는 것으로는 충분하지 않다고 판단했습니다.
신규 프로젝트가 생길 때마다 CI/CD 설정, 배포 방식, 시크릿 연결, 브랜치와 환경 매핑, AI 에이전트 작업 규칙을 다시 정해야 한다면 Jenkins를 제거해도 운영 복잡도는 다른 형태로 반복됩니다. 그래서 GitHub Actions workflow, Infisical, 프로젝트 초기화 CLI, 하네스 업데이트 정책, AI 작업 guardrail을 하나로 묶은 사내 DX 하네스를 설계했습니다.
저는 문제 정의, GitHub Actions 파이프라인 설계, Infisical wiring, 배포 스크립트, harness 전용 CLI, shared/project-owned 업데이트 정책, GSD/GStack/Superpowers 기반 에이전트 작업 규칙까지 전반을 주도했습니다. 현재는 실제 사용 과정에서 들어오는 피드백을 바탕으로 doctor 검증, 업데이트 정책, 사용 편의성을 계속 보완하고 있습니다.
기존 Jenkins 서버는 2대로 운영되었고, 서버당 월 10만 원씩 총 월 20만 원의 고정 비용이 발생했습니다. 하지만 실제 사용 범위는 Jenkins의 복잡한 기능을 적극 활용하기보다 단순 배포 자동화에 가까웠습니다.
별도 Jenkins 관리 담당자가 없었기 때문에 최초 설정 이후 플러그인과 버전 관리가 거의 이루어지지 않았고, 배포 실패가 발생했을 때 원인이 배포 스크립트인지 Jenkins 서버 장애인지 분리해 추적하는 데에도 비용이 들었습니다. 실제로 Jenkins 서버 자체가 죽어 배포에 차질이 생기는 경우도 있었습니다.
또한 여러 프로젝트가 하나의 Jenkins 서버에 묶여 있어, 여러 프로젝트를 동시에 업데이트해도 하나의 프로젝트 배포가 끝난 뒤 다음 프로젝트가 배포되는 순차 처리 병목이 있었습니다. 서버 스펙상 slave 확장도 쉽지 않았습니다.
환경변수와 외부 서비스 토큰을 Jenkins UI에 직접 넣어 관리하다 보니, 어떤 값이 어떤 프로젝트와 환경에 정확히 반영되어 있는지 확인하기 어려웠습니다. 값 누락이나 오입력 같은 휴먼 오류 가능성도 있었고, 팀원이 함께 검토하고 공유하기에도 불편했습니다.
처음에는 Jenkins를 GitHub Actions로 옮기는 것이 목표였지만, 프로젝트마다 workflow 파일을 복사해 넣는 방식은 장기적인 DX 개선으로 보기 어려웠습니다. 신규 프로젝트마다 CI/CD, PM2/Docker/Vercel 배포 방식, 시크릿 연결, 브랜치와 환경 매핑을 다시 결정해야 하기 때문입니다.
v1 하네스에서도 비슷한 문제가 있었습니다. GStack과 skill을 각 프로젝트 모노레포 안에 포함했기 때문에 프로젝트마다 버전이 달라졌고, 공통 변경이 생길 때마다 각 레포에 변경분을 복사하고 커밋해야 했습니다. 문제의 핵심은 레포 용량보다 공통 도구의 버전 드리프트와 업데이트 전파 비용이었습니다.
GitHub Actions의 pipeline.yml을 CI/CD의 단일 진입점으로 설계했습니다. 단순히 push 시 모든 배포를 실행하는 구조가 아니라, 변경 파일을 감지해 backend_changed, frontend_changed, dependency_changed를 계산하고 필요한 배포만 실행하도록 했습니다.
프론트엔드만 변경된 경우 백엔드 배포가 실행되지 않게 하고, 애플리케이션 변경이 없으면 불필요한 배포를 줄였습니다. 배포 방식은 PM2, Docker, Vercel을 reusable workflow로 분리해 하나의 진입점에서 프로젝트에 맞는 방식을 선택할 수 있게 했습니다.
배포 전에는 Node 검증과 dependency security 검사를 공통 gate로 두었습니다. 이 때문에 CI/CD 시간이 단순히 줄어든 것은 아니지만, 패키지 상태와 보안 문제를 배포 전에 확인하고 Slack 알림으로 빠르게 대응할 수 있게 했습니다.
GitHub Secrets에는 INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET처럼 Infisical 접근을 위한 bootstrap secret만 두었습니다. 실제 런타임 env, 배포 변수, Slack/Vercel 같은 외부 서비스 토큰은 Infisical에서 관리하도록 했습니다.
Infisical 경로는 프로젝트와 목적별로 분리했습니다.
| 경로 | 책임 |
|---|---|
/backend | 백엔드 런타임 환경변수 |
/frontend | 프론트엔드 런타임 환경변수 |
/backend/github-actions | 백엔드 배포 workflow 전용 변수 |
/frontend/github-actions | 프론트엔드 배포 workflow 전용 변수 |
Shared-Secrets /slack, /vercel | 공용 외부 서비스 토큰 |
Infisical을 셀프호스팅한 이유는 당시 팀에서 환경변수를 안전하게 보관하고 공유할 중앙 저장소가 마땅히 없었기 때문입니다. 도메인을 연결해 팀원이 같은 기준으로 secret을 조회하고 관리할 수 있게 했습니다.
./harness CLI로 초기화와 검증 흐름 제품화하네스는 단순한 workflow 모음이 아니라 ./harness CLI 중심의 내부 도구로 만들었습니다. 신규 프로젝트와 기존 프로젝트 모두에 적용할 수 있도록 아래 흐름을 커버했습니다.
split-front-back, next-fullstack, frontend-only, backend-only profile 선택apps/front, apps/back로 통합.infisical.json과 workflow의 _PROJECT_ID_ placeholder 치환dev/main 브랜치와 dev/prod 환경 매핑./harness doctor로 누락 파일, skill tree, hook 상태 확인이 구조의 목적은 신규 프로젝트 세팅을 사람이 기억해서 맞추는 작업에서, CLI로 초기화하고 doctor로 검증하는 작업으로 바꾸는 것이었습니다.
v2 하네스에서는 공통 하네스의 최신화를 자동화하되, 다운스트림 프로젝트의 소유 파일을 덮어쓰지 않도록 파일 소유권을 나눴습니다.
공통 정책, 하네스 스크립트, shared skill은 upstream에서 업데이트되는 shared 영역으로 두었습니다. 반대로 apps/ **, .github/** , README.md, 패키지 파일, lockfile, .planning/ **, .harness/config/project-profile.yaml, .harness/skills-local/** 처럼 프로젝트마다 달라지는 파일은 project-owned로 분류해 업데이트 대상에서 제외했습니다.
shared 파일도 로컬 변경이 있으면 자동으로 덮어쓰지 않고 적용을 막았습니다. 누락된 shared 파일은 manifest 기준으로 복구하고, upstream에서 제거된 stale shared 파일은 shared root 안에서만 정리하도록 했습니다. 이 방식으로 공통 하네스는 계속 최신화하면서도 프로젝트별 앱 코드와 작업 상태는 보존할 수 있었습니다.
하네스에는 GSD, GStack, Superpowers의 역할을 결합했습니다.
.planning/ 기반 milestone, phase state, 검증 기록 관리개발자마다 Codex나 Claude Code에 주는 프롬프트와 작업 스타일은 다를 수 있지만, 최소한의 phase와 검증 기준은 같아야 한다고 판단했습니다. AI가 만든 정크 코드가 프로젝트에 섞이면 이후 에이전트 작업 품질도 낮아질 수 있기 때문입니다.
또한 rm -rf, git reset --hard, production deploy/rollback, gh pr merge처럼 사람의 판단이 필요한 작업은 guardrail 대상으로 분류했습니다. project-profile.yaml로 apps/front와 apps/back의 경계를 관리하고, Next.js/NestJS/React/Express별 패키지 매니저 정책, GitHub Secrets 최소화, shared/local skill 소유권 분리도 함께 적용했습니다.
| 구분 | 전환 전 | 전환 후 |
|---|---|---|
| 인프라 비용 | Jenkins 서버 2대, 월 20만 원 | 제거 또는 제거 예정으로 월 20만 원 절감 |
| 배포 병목 | 작은 Jenkins 서버에서 여러 프로젝트가 순차 배포됨 | GitHub Actions workflow 기반 프로젝트별 실행으로 병목 완화 |
| 배포 방식 | Jenkins 중심 | PM2, Docker, Vercel reusable workflow 지원 |
| 시크릿 관리 | Jenkins UI에 env 직접 등록 | Infisical에서 프로젝트/환경/경로별 관리 |
| 보안/패키지 문제 | 별도 탐지 체계 부족 | dependency security와 Slack 알림으로 빠른 인지 |
| 신규 프로젝트 세팅 | 사람이 체크리스트로 반복 처리 | ./harness CLI와 doctor로 초기화/검증 |
| 적용 범위 | Jenkins 기반 프로젝트별 수동 세팅 | GitHub Actions 전환 및 적용 프로젝트 7개 |
| 사용 인원 | 개인 테스트 중심 | 팀원 3명 사용 및 피드백 기반 개선 |
실제 사용 과정에서 받은 피드백도 하네스 구조에 반영했습니다.
apps/back 파일을 침범하는 문제가 있어, 작업 mode를 명시하고 접근 경계를 확인하도록 보완했습니다..harness/skills-local을 별도 소유 영역으로 분리했습니다.AGENTS.md, CLAUDE.md에 phase와 검증 기준을 명시했습니다.CI/CD 시간이 단순히 줄어든 것은 아닙니다. 오히려 패키지 상태와 보안 검사를 추가하면서 일부 검증 시간은 늘었습니다. 하지만 이 프로젝트의 목표는 단순 속도 개선이 아니라 비용 절감, 배포 병목 완화, 시크릿 관리 명확화, 보안 문제 조기 감지, 프로젝트 초기화 표준화였습니다.
이번 프로젝트에서 가장 크게 배운 점은 DX 개선이 도구 교체만으로 완성되지 않는다는 것입니다. Jenkins를 GitHub Actions로 바꾸는 것은 시작점일 뿐이었습니다. 실제로 팀의 개발 경험을 바꾸려면 프로젝트가 시작되는 방식, 시크릿이 관리되는 방식, 배포가 검증되는 방식, AI 에이전트가 작업하는 방식까지 하나의 운영 모델로 묶어야 했습니다.
또한 재사용성은 "파일을 복사하기 쉽게 만드는 것"이 아니라 "변경이 생겼을 때 안전하게 전파되는 구조를 만드는 것"이라는 점을 체감했습니다. v1에서 공통 도구를 프로젝트마다 복사했던 방식은 초기 적용은 쉬웠지만, 시간이 지나면서 버전 드리프트와 업데이트 비용을 만들었습니다. v2에서 shared/project-owned 경계를 둔 이유도 바로 이 문제를 해결하기 위해서였습니다.
이 하네스는 배포 자동화 도구이면서 동시에 팀의 개발 규칙을 코드로 고정한 내부 플랫폼입니다. 빠르게 일하는 AI 에이전트와 개발자를 막는 것이 아니라, 빠르게 일하더라도 넘지 말아야 할 경계와 반드시 거쳐야 할 검증 기준을 제공하는 것이 핵심이었습니다.