[{"data":1,"prerenderedAt":773},["ShallowReactive",2],{"post-document:\u002Fdevops\u002Fci-cd\u002F2026\u002F08\u002F17\u002Fci-cd-01-basics\u002F":3},{"id":4,"title":5,"body":6,"categories":752,"date":755,"description":756,"extension":757,"image":758,"key_concepts":758,"last_modified_at":758,"legacyPath":759,"meta":760,"navigation":148,"part":762,"path":763,"published":148,"robots":758,"seo":764,"series":765,"stem":766,"strengths":758,"summary":758,"tags":767,"tradeoffs":758,"__hash__":772},"posts\u002Fposts\u002FCI-CD-Docker\u002FCI-CD\u002F2026-08-17-ci-cd-01-basics.md","CI\u002FCD 기초 1편: 개념과 GitHub Actions",{"type":7,"value":8,"toc":741},"minimark",[9,14,18,29,34,42,45,50,89,93,112,115,165,168,172,175,191,194,198,204,208,214,218,221,227,233,526,529,533,625,629,635,642,645,672,676,694,698,702,705,711,714,718,721,725,728,734,737],[10,11,13],"h2",{"id":12},"cicd가-뭔가요","CI\u002FCD가 뭔가요?",[15,16,17],"p",{},"쉽게 말하면 코드를 GitHub에 올린 다음, 사람이 반복하던 검사와 배포를 자동화하는 것이다.",[19,20,26],"pre",{"className":21,"code":23,"language":24,"meta":25},[22],"language-text","코드 작성\n  ↓\nGitHub에 Push\n  ↓\n자동 테스트와 빌드                 ← CI\n  ↓\n통과한 프로그램을 서버에 배포     ← CD\n  ↓\n실제 서비스 동작 확인\n","text","",[27,28,23],"code",{"__ignoreMap":25},[30,31,33],"h3",{"id":32},"_1-cicontinuous-integration","1. CI(Continuous Integration)",[15,35,36,37,41],{},"한국어로는 ",[38,39,40],"strong",{},"지속적 통합","이다. 여러 사람이 작성한 코드나 새로 작성한 코드가 기존 프로젝트와 잘 합쳐지는지 자동으로 검사한다.",[15,43,44],{},"현재 프로젝트인 Pi-monitor를 예로 들면 다음 작업이 CI에 해당한다.",[46,47,49],"h4",{"id":48},"frontend","Frontend",[19,51,55],{"className":52,"code":53,"language":54,"meta":25,"style":25},"language-bash shiki shiki-themes github-light github-dark","npm ci\nnpm test\nnpm run build\n","bash",[27,56,57,70,78],{"__ignoreMap":25},[58,59,62,66],"span",{"class":60,"line":61},"line",1,[58,63,65],{"class":64},"sScJk","npm",[58,67,69],{"class":68},"sZZnC"," ci\n",[58,71,73,75],{"class":60,"line":72},2,[58,74,65],{"class":64},[58,76,77],{"class":68}," test\n",[58,79,81,83,86],{"class":60,"line":80},3,[58,82,65],{"class":64},[58,84,85],{"class":68}," run",[58,87,88],{"class":68}," build\n",[46,90,92],{"id":91},"backend","Backend",[19,94,96],{"className":52,"code":95,"language":54,"meta":25,"style":25},".\u002Fmvnw test\n.\u002Fmvnw package\n",[27,97,98,105],{"__ignoreMap":25},[58,99,100,103],{"class":60,"line":61},[58,101,102],{"class":64},".\u002Fmvnw",[58,104,77],{"class":68},[58,106,107,109],{"class":60,"line":72},[58,108,102],{"class":64},[58,110,111],{"class":68}," package\n",[15,113,114],{},"잘못된 코드를 GitHub에 올렸을 때 프론트엔드 테스트, Vue 빌드, Java 컴파일 또는 백엔드 테스트가 실패할 수 있다. CI가 없으면 사람이 매번 직접 명령을 실행해야 한다.",[19,116,118],{"className":52,"code":117,"language":54,"meta":25,"style":25},"cd frontend\nnpm test\nnpm run build\n\ncd ..\u002Fbackend\n.\u002Fmvnw test\n",[27,119,120,129,135,143,150,158],{"__ignoreMap":25},[58,121,122,126],{"class":60,"line":61},[58,123,125],{"class":124},"sj4cs","cd",[58,127,128],{"class":68}," frontend\n",[58,130,131,133],{"class":60,"line":72},[58,132,65],{"class":64},[58,134,77],{"class":68},[58,136,137,139,141],{"class":60,"line":80},[58,138,65],{"class":64},[58,140,85],{"class":68},[58,142,88],{"class":68},[58,144,146],{"class":60,"line":145},4,[58,147,149],{"emptyLinePlaceholder":148},true,"\n",[58,151,153,155],{"class":60,"line":152},5,[58,154,125],{"class":124},[58,156,157],{"class":68}," ..\u002Fbackend\n",[58,159,161,163],{"class":60,"line":160},6,[58,162,102],{"class":64},[58,164,77],{"class":68},[15,166,167],{},"CI를 구성하면 GitHub가 별도의 실행 환경에서 이 명령들을 자동으로 실행한다.",[30,169,171],{"id":170},"_2-cdcontinuous-delivery-또는-deployment","2. CD(Continuous Delivery 또는 Deployment)",[15,173,174],{},"CD에는 두 가지 의미가 있다.",[176,177,178,185],"ul",{},[179,180,181,184],"li",{},[38,182,183],{},"Continuous Delivery",": 배포 가능한 상태까지 자동으로 준비하고 실제 배포는 사람이 승인한다.",[179,186,187,190],{},[38,188,189],{},"Continuous Deployment",": 테스트가 통과하면 실제 서버 배포까지 자동으로 진행한다.",[15,192,193],{},"배포 시스템을 처음 구성한다면 사람이 마지막으로 승인하는 Continuous Delivery 방식이 안전하다.",[46,195,197],{"id":196},"continuous-delivery-예시","Continuous Delivery 예시",[19,199,202],{"className":200,"code":201,"language":24,"meta":25},[22],"main에 Push\n→ 테스트 및 빌드\n→ 배포 준비 완료\n→ 사람이 Deploy 버튼 클릭\n",[27,203,201],{"__ignoreMap":25},[46,205,207],{"id":206},"continuous-deployment-예시","Continuous Deployment 예시",[19,209,212],{"className":210,"code":211,"language":24,"meta":25},[22],"main에 Push\n→ 테스트 및 빌드 통과\n→ 라즈베리파이 자동 업데이트\n→ 컨테이너 재시작\n",[27,213,211],{"__ignoreMap":25},[30,215,217],{"id":216},"_3-github-actions","3. GitHub Actions",[15,219,220],{},"GitHub Actions는 GitHub에서 CI\u002FCD 작업을 실행해 주는 기능이다. 저장소에 다음과 같은 Workflow 파일을 만든다.",[19,222,225],{"className":223,"code":224,"language":24,"meta":25},[22],".github\u002F\n└── workflows\u002F\n    └── ci.yml\n",[27,226,224],{"__ignoreMap":25},[15,228,229,232],{},[27,230,231],{},"ci.yml","에는 언제, 어떤 운영체제에서, 어떤 명령을 실행할지 정의한다.",[19,234,238],{"className":235,"code":236,"language":237,"meta":25,"style":25},"language-yaml shiki shiki-themes github-light github-dark","name: CI\n\non:\n  push:\n  pull_request:\n\njobs:\n  frontend:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: 소스 코드 가져오기\n        uses: actions\u002Fcheckout@v4\n\n      - name: Node.js 설치\n        uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: 22\n          cache: npm\n          cache-dependency-path: frontend\u002Fpackage-lock.json\n\n      - name: 패키지 설치\n        working-directory: frontend\n        run: npm ci\n\n      - name: 테스트\n        working-directory: frontend\n        run: npm test\n\n      - name: 빌드\n        working-directory: frontend\n        run: npm run build\n","yaml",[27,239,240,253,257,265,272,279,283,291,299,310,315,323,336,347,352,364,374,382,393,404,415,420,432,443,454,459,471,480,490,495,507,516],{"__ignoreMap":25},[58,241,242,246,250],{"class":60,"line":61},[58,243,245],{"class":244},"s9eBZ","name",[58,247,249],{"class":248},"sVt8B",": ",[58,251,252],{"class":68},"CI\n",[58,254,255],{"class":60,"line":72},[58,256,149],{"emptyLinePlaceholder":148},[58,258,259,262],{"class":60,"line":80},[58,260,261],{"class":124},"on",[58,263,264],{"class":248},":\n",[58,266,267,270],{"class":60,"line":145},[58,268,269],{"class":244},"  push",[58,271,264],{"class":248},[58,273,274,277],{"class":60,"line":152},[58,275,276],{"class":244},"  pull_request",[58,278,264],{"class":248},[58,280,281],{"class":60,"line":160},[58,282,149],{"emptyLinePlaceholder":148},[58,284,286,289],{"class":60,"line":285},7,[58,287,288],{"class":244},"jobs",[58,290,264],{"class":248},[58,292,294,297],{"class":60,"line":293},8,[58,295,296],{"class":244},"  frontend",[58,298,264],{"class":248},[58,300,302,305,307],{"class":60,"line":301},9,[58,303,304],{"class":244},"    runs-on",[58,306,249],{"class":248},[58,308,309],{"class":68},"ubuntu-latest\n",[58,311,313],{"class":60,"line":312},10,[58,314,149],{"emptyLinePlaceholder":148},[58,316,318,321],{"class":60,"line":317},11,[58,319,320],{"class":244},"    steps",[58,322,264],{"class":248},[58,324,326,329,331,333],{"class":60,"line":325},12,[58,327,328],{"class":248},"      - ",[58,330,245],{"class":244},[58,332,249],{"class":248},[58,334,335],{"class":68},"소스 코드 가져오기\n",[58,337,339,342,344],{"class":60,"line":338},13,[58,340,341],{"class":244},"        uses",[58,343,249],{"class":248},[58,345,346],{"class":68},"actions\u002Fcheckout@v4\n",[58,348,350],{"class":60,"line":349},14,[58,351,149],{"emptyLinePlaceholder":148},[58,353,355,357,359,361],{"class":60,"line":354},15,[58,356,328],{"class":248},[58,358,245],{"class":244},[58,360,249],{"class":248},[58,362,363],{"class":68},"Node.js 설치\n",[58,365,367,369,371],{"class":60,"line":366},16,[58,368,341],{"class":244},[58,370,249],{"class":248},[58,372,373],{"class":68},"actions\u002Fsetup-node@v4\n",[58,375,377,380],{"class":60,"line":376},17,[58,378,379],{"class":244},"        with",[58,381,264],{"class":248},[58,383,385,388,390],{"class":60,"line":384},18,[58,386,387],{"class":244},"          node-version",[58,389,249],{"class":248},[58,391,392],{"class":124},"22\n",[58,394,396,399,401],{"class":60,"line":395},19,[58,397,398],{"class":244},"          cache",[58,400,249],{"class":248},[58,402,403],{"class":68},"npm\n",[58,405,407,410,412],{"class":60,"line":406},20,[58,408,409],{"class":244},"          cache-dependency-path",[58,411,249],{"class":248},[58,413,414],{"class":68},"frontend\u002Fpackage-lock.json\n",[58,416,418],{"class":60,"line":417},21,[58,419,149],{"emptyLinePlaceholder":148},[58,421,423,425,427,429],{"class":60,"line":422},22,[58,424,328],{"class":248},[58,426,245],{"class":244},[58,428,249],{"class":248},[58,430,431],{"class":68},"패키지 설치\n",[58,433,435,438,440],{"class":60,"line":434},23,[58,436,437],{"class":244},"        working-directory",[58,439,249],{"class":248},[58,441,442],{"class":68},"frontend\n",[58,444,446,449,451],{"class":60,"line":445},24,[58,447,448],{"class":244},"        run",[58,450,249],{"class":248},[58,452,453],{"class":68},"npm ci\n",[58,455,457],{"class":60,"line":456},25,[58,458,149],{"emptyLinePlaceholder":148},[58,460,462,464,466,468],{"class":60,"line":461},26,[58,463,328],{"class":248},[58,465,245],{"class":244},[58,467,249],{"class":248},[58,469,470],{"class":68},"테스트\n",[58,472,474,476,478],{"class":60,"line":473},27,[58,475,437],{"class":244},[58,477,249],{"class":248},[58,479,442],{"class":68},[58,481,483,485,487],{"class":60,"line":482},28,[58,484,448],{"class":244},[58,486,249],{"class":248},[58,488,489],{"class":68},"npm test\n",[58,491,493],{"class":60,"line":492},29,[58,494,149],{"emptyLinePlaceholder":148},[58,496,498,500,502,504],{"class":60,"line":497},30,[58,499,328],{"class":248},[58,501,245],{"class":244},[58,503,249],{"class":248},[58,505,506],{"class":68},"빌드\n",[58,508,510,512,514],{"class":60,"line":509},31,[58,511,437],{"class":244},[58,513,249],{"class":248},[58,515,442],{"class":68},[58,517,519,521,523],{"class":60,"line":518},32,[58,520,448],{"class":244},[58,522,249],{"class":248},[58,524,525],{"class":68},"npm run build\n",[15,527,528],{},"이 파일을 Push하면 GitHub가 Ubuntu 가상 환경을 만들고 위 명령을 실행한다.",[30,530,532],{"id":531},"_4-알아두면-좋은-용어","4. 알아두면 좋은 용어",[534,535,536,549],"table",{},[537,538,539],"thead",{},[540,541,542,546],"tr",{},[543,544,545],"th",{},"용어",[543,547,548],{},"의미",[550,551,552,561,569,577,585,593,601,609,617],"tbody",{},[540,553,554,558],{},[555,556,557],"td",{},"Workflow",[555,559,560],{},"CI\u002FCD 전체 작업을 정의한 파일",[540,562,563,566],{},[555,564,565],{},"Trigger",[555,567,568],{},"작업을 시작시키는 사건. 예: Push, Pull Request",[540,570,571,574],{},[555,572,573],{},"Job",[555,575,576],{},"테스트, 빌드처럼 하나로 묶인 작업",[540,578,579,582],{},[555,580,581],{},"Step",[555,583,584],{},"Job 안에서 실행하는 개별 명령",[540,586,587,590],{},[555,588,589],{},"Runner",[555,591,592],{},"실제 명령을 실행하는 컴퓨터",[540,594,595,598],{},[555,596,597],{},"Artifact",[555,599,600],{},"빌드해서 만들어진 결과물",[540,602,603,606],{},[555,604,605],{},"Secret",[555,607,608],{},"비밀번호, 토큰 같은 비공개 값",[540,610,611,614],{},[555,612,613],{},"Deploy",[555,615,616],{},"완성된 프로그램을 실제 서버에 반영하는 작업",[540,618,619,622],{},[555,620,621],{},"Rollback",[555,623,624],{},"문제가 생겼을 때 이전 버전으로 복구하는 작업",[30,626,628],{"id":627},"_5-내-프로젝트로-보는-cicd","5. 내 프로젝트로 보는 CI\u002FCD",[19,630,633],{"className":631,"code":632,"language":24,"meta":25},[22],"GitHub Push\n  │\n  ├─ Frontend CI\n  │    ├─ npm ci\n  │    ├─ npm test\n  │    └─ npm run build\n  │\n  ├─ Backend CI\n  │    ├─ Maven test\n  │    └─ Spring Boot package\n  │\n  └─ 모든 검사 통과\n       │\n       ├─ Vercel이 프론트엔드 자동 배포\n       └─ Raspberry Pi에 백엔드 배포\n",[27,634,632],{"__ignoreMap":25},[15,636,637,638,641],{},"프론트엔드는 Vercel과 GitHub 저장소가 연결되어 있어 기본적인 CD를 사용하고 있다. ",[27,639,640],{},"main","에 Push하면 Vercel이 자동으로 빌드하고 배포한다.",[15,643,644],{},"반면 라즈베리파이 백엔드는 다음 명령으로 수동 배포 중이므로 이 부분을 자동화해야 한다.",[19,646,648],{"className":52,"code":647,"language":54,"meta":25,"style":25},"git pull\n.\u002Fscripts\u002Fpi-compose.sh up -d --build\n",[27,649,650,658],{"__ignoreMap":25},[58,651,652,655],{"class":60,"line":61},[58,653,654],{"class":64},"git",[58,656,657],{"class":68}," pull\n",[58,659,660,663,666,669],{"class":60,"line":72},[58,661,662],{"class":64},".\u002Fscripts\u002Fpi-compose.sh",[58,664,665],{"class":68}," up",[58,667,668],{"class":124}," -d",[58,670,671],{"class":124}," --build\n",[30,673,675],{"id":674},"_6-추천-학습-순서","6. 추천 학습 순서",[677,678,679,682,685,688,691],"ol",{},[179,680,681],{},"GitHub Actions로 프론트엔드 테스트와 빌드 자동화",[179,683,684],{},"백엔드 테스트와 빌드 추가",[179,686,687],{},"Pull Request에서 CI가 통과해야 Merge하도록 설정",[179,689,690],{},"Vercel 자동 배포 과정 이해",[179,692,693],{},"라즈베리파이 자동 배포 테스트",[30,695,697],{"id":696},"_7-라즈베리파이-cd-방법","7. 라즈베리파이 CD 방법",[46,699,701],{"id":700},"ssh로-배포","SSH로 배포",[15,703,704],{},"GitHub Actions가 라즈베리파이에 SSH로 접속해 배포 명령을 실행한다.",[19,706,709],{"className":707,"code":708,"language":24,"meta":25},[22],"GitHub Actions\n→ SSH 접속\n→ git pull\n→ Docker 이미지 빌드\n→ 컨테이너 교체\n→ 상태 확인\n",[27,710,708],{"__ignoreMap":25},[15,712,713],{},"다만 라즈베리파이를 외부에서 SSH로 접근할 수 있어야 한다.",[46,715,717],{"id":716},"self-hosted-runner-사용","Self-hosted Runner 사용",[15,719,720],{},"라즈베리파이에 GitHub Actions Runner를 설치하면 GitHub가 보낸 작업을 라즈베리파이가 직접 실행한다. 외부 SSH 포트를 열 필요는 없지만, 공개 저장소나 신뢰할 수 없는 PR의 코드가 실행되지 않도록 주의해야 한다.",[46,722,724],{"id":723},"이미지-기반-배포","이미지 기반 배포",[15,726,727],{},"GitHub Actions가 Docker 이미지를 빌드해 Container Registry에 올리고, 라즈베리파이는 완성된 이미지만 내려받는다.",[19,729,732],{"className":730,"code":731,"language":24,"meta":25},[22],"GitHub Actions\n→ Docker 이미지 빌드\n→ GitHub Container Registry에 Push\n→ Raspberry Pi가 이미지 Pull\n→ 컨테이너 교체\n",[27,733,731],{"__ignoreMap":25},[15,735,736],{},"가장 체계적인 방식이며, 다음 글에서 Docker 기반 CI\u002FCD 흐름을 더 자세히 살펴본다.",[738,739,740],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":25,"searchDepth":72,"depth":72,"links":742},[743],{"id":12,"depth":72,"text":13,"children":744},[745,746,747,748,749,750,751],{"id":32,"depth":80,"text":33},{"id":170,"depth":80,"text":171},{"id":216,"depth":80,"text":217},{"id":531,"depth":80,"text":532},{"id":627,"depth":80,"text":628},{"id":674,"depth":80,"text":675},{"id":696,"depth":80,"text":697},[753,754],"DevOps","CI-CD","2026-08-17 13:00:00 +0900","CI와 CD의 차이, GitHub Actions의 기본 구조, 라즈베리파이 배포 방법을 정리한다.","md",null,"\u002Fdevops\u002Fci-cd\u002F2026\u002F08\u002F17\u002Fci-cd-01-basics\u002F",{"layout":761},"post","1","\u002Fposts\u002Fci-cd-docker\u002Fci-cd\u002F2026-08-17-ci-cd-01-basics",{"title":5,"description":756},"Docker 기반 CI\u002FCD","posts\u002FCI-CD-Docker\u002FCI-CD\u002F2026-08-17-ci-cd-01-basics",[768,769,770,771],"CI\u002FCD","GitHub Actions","Docker","Deployment","M_wA5a5yNaYF0A07Brgj_jsOa0zidjvjeIfoG8bxKCk",1788744787345]