[{"data":1,"prerenderedAt":682},["ShallowReactive",2],{"post-document:\u002Fdevops\u002Fci-cd\u002F2026\u002F08\u002F17\u002Fci-cd-02-docker-pipeline\u002F":3},{"id":4,"title":5,"body":6,"categories":661,"date":664,"description":665,"extension":666,"image":667,"key_concepts":667,"last_modified_at":667,"legacyPath":668,"meta":669,"navigation":225,"part":671,"path":672,"published":225,"robots":667,"seo":673,"series":13,"stem":674,"strengths":667,"summary":667,"tags":675,"tradeoffs":667,"__hash__":681},"posts\u002Fposts\u002FCI-CD-Docker\u002FCI-CD\u002F2026-08-17-ci-cd-02-docker-pipeline.md","CI\u002FCD 기초 2편: Docker 이미지와 배포 파이프라인",{"type":7,"value":8,"toc":646},"minimark",[9,14,18,29,32,38,43,48,51,57,60,107,111,118,124,130,136,139,143,146,152,155,158,164,167,171,177,187,193,196,202,206,209,265,335,338,342,345,351,358,364,367,371,374,393,399,402,406,409,415,418,433,436,442,445,468,471,477,481,488,494,497,503,507,510,548,554,557,561,564,579,582,596,599,605,609,615,621,624,630,636,639,642],[10,11,13],"h2",{"id":12},"docker-기반-cicd","Docker 기반 CI\u002FCD",[15,16,17],"p",{},"먼저 Docker 기반 배포의 전체 흐름을 살펴보자.",[19,20,26],"pre",{"className":21,"code":23,"language":24,"meta":25},[22],"language-text","Developer\n   │ git push\n   ▼\nGitHub Repository\n   │\n   ▼\nCI Pipeline\n   ├── Source Checkout\n   ├── Dependency Install\n   ├── Test\n   ├── Build\n   └── Docker Image Build\n            │\n            ▼\n      Container Registry\n      (Docker Hub, GHCR, ECR)\n            │\n            ▼\n       Production Server\n            │ docker pull\n            ▼\n      Docker Container\n            │\n            ▼\n        Application\n            │\n            ▼\n          Users\n","text","",[27,28,23],"code",{"__ignoreMap":25},[15,30,31],{},"핵심 흐름은 다음과 같다.",[19,33,36],{"className":34,"code":35,"language":24,"meta":25},[22],"Source Code\n→ Executable Artifact\n→ Docker Image\n→ Registry\n→ Container\n→ Running Service\n",[27,37,35],{"__ignoreMap":25},[39,40,42],"h3",{"id":41},"_1-ci와-cd-다시-구분하기","1. CI와 CD 다시 구분하기",[44,45,47],"h4",{"id":46},"cicontinuous-integration","CI(Continuous Integration)",[15,49,50],{},"개발자가 작성한 코드를 지속적으로 통합하고 검증하는 과정이다.",[52,53,54],"blockquote",{},[15,55,56],{},"이 코드를 배포 가능한 상태로 신뢰해도 되는가?",[15,58,59],{},"Spring Boot 프로젝트라면 다음과 같은 작업을 수행할 수 있다.",[19,61,65],{"className":62,"code":63,"language":64,"meta":25,"style":25},"language-bash shiki shiki-themes github-light github-dark",".\u002Fgradlew test\n.\u002Fgradlew bootJar\ndocker build -t myapp:1.0 .\n","bash",[27,66,67,80,88],{"__ignoreMap":25},[68,69,72,76],"span",{"class":70,"line":71},"line",1,[68,73,75],{"class":74},"sScJk",".\u002Fgradlew",[68,77,79],{"class":78},"sZZnC"," test\n",[68,81,83,85],{"class":70,"line":82},2,[68,84,75],{"class":74},[68,86,87],{"class":78}," bootJar\n",[68,89,91,94,97,101,104],{"class":70,"line":90},3,[68,92,93],{"class":74},"docker",[68,95,96],{"class":78}," build",[68,98,100],{"class":99},"sj4cs"," -t",[68,102,103],{"class":78}," myapp:1.0",[68,105,106],{"class":78}," .\n",[44,108,110],{"id":109},"cdcontinuous-delivery-또는-deployment","CD(Continuous Delivery 또는 Deployment)",[15,112,113,117],{},[114,115,116],"strong",{},"Continuous Delivery","는 배포 가능한 상태까지 자동화하고 실제 배포는 사람이 승인하는 방식이다.",[19,119,122],{"className":120,"code":121,"language":24,"meta":25},[22],"Code\n→ Test\n→ Build\n→ Image\n→ Registry\n→ 배포 준비 완료\n",[27,123,121],{"__ignoreMap":25},[15,125,126,129],{},[114,127,128],{},"Continuous Deployment","는 Production 배포까지 완전히 자동화하는 방식이다.",[19,131,134],{"className":132,"code":133,"language":24,"meta":25},[22],"git push\n→ CI\n→ Docker Image\n→ Production\n→ Health Check\n→ 서비스\n",[27,135,133],{"__ignoreMap":25},[15,137,138],{},"간단히 정리하면 CI는 배포 가능한 결과물을 만들고, CD는 그 결과물을 실제 환경에 배포한다.",[39,140,142],{"id":141},"_2-docker에서-cicd가-중요한-이유","2. Docker에서 CI\u002FCD가 중요한 이유",[15,144,145],{},"서버에 직접 접속해서 코드를 받고 환경을 구성하면 개발 환경과 운영 환경의 차이로 문제가 생길 수 있다.",[19,147,150],{"className":148,"code":149,"language":24,"meta":25},[22],"개발자 PC\n├── Java 21\n├── PostgreSQL 16\n└── Library A 2.1\n\nProduction\n├── Java 17\n├── PostgreSQL 14\n└── Library A 1.9\n",[27,151,149],{"__ignoreMap":25},[15,153,154],{},"바로 유명한 “내 컴퓨터에서는 되는데?” 문제다.",[15,156,157],{},"Docker는 애플리케이션과 실행에 필요한 환경을 하나의 이미지로 묶어 이 차이를 줄인다.",[19,159,162],{"className":160,"code":161,"language":24,"meta":25},[22],"Application\n+ Runtime\n+ Library\n+ Dependency\n+ Environment\n= Docker Image\n",[27,163,161],{"__ignoreMap":25},[15,165,166],{},"개발 및 검증 환경에서 확인한 이미지를 Production에서도 동일하게 실행할 수 있다.",[39,168,170],{"id":169},"_3-docker-image와-container","3. Docker Image와 Container",[19,172,175],{"className":173,"code":174,"language":24,"meta":25},[22],"Dockerfile\n→ docker build\n→ Docker Image\n→ docker run\n→ Container\n",[27,176,174],{"__ignoreMap":25},[15,178,179,182,183,186],{},[114,180,181],{},"Image","는 실행에 필요한 파일을 담은 불변 배포 패키지다. ",[114,184,185],{},"Container","는 이미지를 실제로 실행한 인스턴스다.",[19,188,191],{"className":189,"code":190,"language":24,"meta":25},[22],"Image: myapp:1.0\n       │\n       ├─ Container A\n       ├─ Container B\n       └─ Container C\n",[27,192,190],{"__ignoreMap":25},[15,194,195],{},"하나의 이미지에서 여러 컨테이너를 만들 수 있다. 객체 지향에 비유하면 Class와 Instance의 관계와 비슷하다.",[19,197,200],{"className":198,"code":199,"language":24,"meta":25},[22],"Class     → Docker Image\nInstance  → Docker Container\n",[27,201,199],{"__ignoreMap":25},[39,203,205],{"id":204},"_4-dockerfile","4. Dockerfile",[15,207,208],{},"Dockerfile은 Docker 이미지를 어떻게 만들지 선언하는 파일이다. Spring Boot라면 다음과 같이 작성할 수 있다.",[19,210,214],{"className":211,"code":212,"language":213,"meta":25,"style":25},"language-dockerfile shiki shiki-themes github-light github-dark","FROM eclipse-temurin:21-jre\n\nWORKDIR \u002Fapp\n\nCOPY build\u002Flibs\u002Fapp.jar app.jar\n\nEXPOSE 8080\n\nENTRYPOINT [\"java\", \"-jar\", \"app.jar\"]\n","dockerfile",[27,215,216,221,227,232,237,243,248,254,259],{"__ignoreMap":25},[68,217,218],{"class":70,"line":71},[68,219,220],{},"FROM eclipse-temurin:21-jre\n",[68,222,223],{"class":70,"line":82},[68,224,226],{"emptyLinePlaceholder":225},true,"\n",[68,228,229],{"class":70,"line":90},[68,230,231],{},"WORKDIR \u002Fapp\n",[68,233,235],{"class":70,"line":234},4,[68,236,226],{"emptyLinePlaceholder":225},[68,238,240],{"class":70,"line":239},5,[68,241,242],{},"COPY build\u002Flibs\u002Fapp.jar app.jar\n",[68,244,246],{"class":70,"line":245},6,[68,247,226],{"emptyLinePlaceholder":225},[68,249,251],{"class":70,"line":250},7,[68,252,253],{},"EXPOSE 8080\n",[68,255,257],{"class":70,"line":256},8,[68,258,226],{"emptyLinePlaceholder":225},[68,260,262],{"class":70,"line":261},9,[68,263,264],{},"ENTRYPOINT [\"java\", \"-jar\", \"app.jar\"]\n",[266,267,268,281],"table",{},[269,270,271],"thead",{},[272,273,274,278],"tr",{},[275,276,277],"th",{},"명령",[275,279,280],{},"의미",[282,283,284,295,305,315,325],"tbody",{},[272,285,286,292],{},[287,288,289],"td",{},[27,290,291],{},"FROM",[287,293,294],{},"사용할 기본 실행 환경",[272,296,297,302],{},[287,298,299],{},[27,300,301],{},"WORKDIR",[287,303,304],{},"컨테이너 내부 작업 디렉터리",[272,306,307,312],{},[287,308,309],{},[27,310,311],{},"COPY",[287,313,314],{},"애플리케이션 파일 복사",[272,316,317,322],{},[287,318,319],{},[27,320,321],{},"EXPOSE",[287,323,324],{},"애플리케이션이 사용하는 포트 명시",[272,326,327,332],{},[287,328,329],{},[27,330,331],{},"ENTRYPOINT",[287,333,334],{},"컨테이너가 시작될 때 실행할 명령",[15,336,337],{},"즉, Dockerfile을 보면 애플리케이션을 실행할 서버 환경이 어떻게 구성되는지 알 수 있다.",[39,339,341],{"id":340},"_5-build-artifact","5. Build Artifact",[15,343,344],{},"Build Artifact는 소스 코드를 컴파일·테스트·패키징하는 과정에서 만들어지는 배포 가능한 결과물이다.",[19,346,349],{"className":347,"code":348,"language":24,"meta":25},[22],"Source Code\n→ Compile\n→ Test\n→ Build\n→ app.jar\n",[27,350,348],{"__ignoreMap":25},[15,352,353,354,357],{},"여기서 ",[27,355,356],{},"app.jar","가 Build Artifact다. Docker를 사용하면 한 단계 더 나아간다.",[19,359,362],{"className":360,"code":361,"language":24,"meta":25},[22],"Source Code\n→ JAR\n→ Docker Image\n",[27,363,361],{"__ignoreMap":25},[15,365,366],{},"Docker 기반 시스템에서는 Docker 이미지를 최종 Release Artifact로 사용할 수 있다.",[39,368,370],{"id":369},"_6-container-registry","6. Container Registry",[15,372,373],{},"Release Artifact인 Docker 이미지는 어딘가에 저장해야 한다. 이때 사용하는 저장소가 Container Registry다.",[375,376,377,381,384,387,390],"ul",{},[378,379,380],"li",{},"Docker Hub",[378,382,383],{},"GitHub Container Registry(GHCR)",[378,385,386],{},"AWS ECR",[378,388,389],{},"Google Artifact Registry",[378,391,392],{},"Azure Container Registry",[19,394,397],{"className":395,"code":396,"language":24,"meta":25},[22],"CI Server\n→ docker push\n→ Registry\n→ docker pull\n→ Production Server\n",[27,398,396],{"__ignoreMap":25},[15,400,401],{},"GitHub가 소스 코드 저장소라면 Registry는 Docker 이미지 저장소다.",[39,403,405],{"id":404},"_7-실제-데이터-흐름","7. 실제 데이터 흐름",[15,407,408],{},"개발자가 Spring Boot 코드를 수정하고 Push하면 GitHub Actions가 CI를 실행한다.",[19,410,413],{"className":411,"code":412,"language":24,"meta":25},[22],"GitHub Actions Runner\n→ Checkout\n→ JDK 설치\n→ .\u002Fgradlew test\n→ .\u002Fgradlew bootJar\n→ docker build\n",[27,414,412],{"__ignoreMap":25},[15,416,417],{},"만들어진 이미지를 Registry에 Push한다.",[19,419,421],{"className":62,"code":420,"language":64,"meta":25,"style":25},"docker push registry.example.com\u002Fmyapp:1.4.2\n",[27,422,423],{"__ignoreMap":25},[68,424,425,427,430],{"class":70,"line":71},[68,426,93],{"class":74},[68,428,429],{"class":78}," push",[68,431,432],{"class":78}," registry.example.com\u002Fmyapp:1.4.2\n",[15,434,435],{},"Registry에는 여러 버전의 이미지가 존재할 수 있다.",[19,437,440],{"className":438,"code":439,"language":24,"meta":25},[22],"myapp\n├── 1.4.0\n├── 1.4.1\n├── 1.4.2\n└── latest\n",[27,441,439],{"__ignoreMap":25},[15,443,444],{},"Production Server는 지정한 버전의 이미지를 받아 실행한다.",[19,446,448],{"className":62,"code":447,"language":64,"meta":25,"style":25},"docker pull registry.example.com\u002Fmyapp:1.4.2\ndocker run registry.example.com\u002Fmyapp:1.4.2\n",[27,449,450,459],{"__ignoreMap":25},[68,451,452,454,457],{"class":70,"line":71},[68,453,93],{"class":74},[68,455,456],{"class":78}," pull",[68,458,432],{"class":78},[68,460,461,463,466],{"class":70,"line":82},[68,462,93],{"class":74},[68,464,465],{"class":78}," run",[68,467,432],{"class":78},[15,469,470],{},"최종 요청 흐름은 다음과 같다.",[19,472,475],{"className":473,"code":474,"language":24,"meta":25},[22],"User\n→ Internet\n→ Load Balancer \u002F Nginx\n→ Container\n→ Spring Boot\n→ Database\n",[27,476,474],{"__ignoreMap":25},[39,478,480],{"id":479},"_8-build-once-deploy-many","8. Build Once, Deploy Many",[15,482,483,484,487],{},"CI\u002FCD에서 중요한 원칙은 ",[114,485,486],{},"한 번 빌드한 결과물을 여러 환경에 배포하는 것","이다.",[19,489,492],{"className":490,"code":491,"language":24,"meta":25},[22],"Dev용 Image Build\nStage용 Image Build\nProd용 Image Build\n",[27,493,491],{"__ignoreMap":25},[15,495,496],{},"환경마다 이미지를 다시 빌드하면 Staging에서 검증한 이미지와 Production에 올린 이미지가 달라질 수 있다. 따라서 한 번 만든 동일한 이미지를 여러 환경에서 사용한다.",[19,498,501],{"className":499,"code":500,"language":24,"meta":25},[22],"Source\n  ↓\nBuild\n  ↓\nImage: abc123\n  ├── Dev\n  ├── Staging\n  └── Production\n",[27,502,500],{"__ignoreMap":25},[39,504,506],{"id":505},"_9-환경-설정-분리","9. 환경 설정 분리",[15,508,509],{},"개발, Staging, Production의 DB 주소와 설정은 서로 다르다. 이미지는 동일하게 유지하고 환경별 값은 컨테이너 실행 시 외부에서 주입한다.",[19,511,513],{"className":62,"code":512,"language":64,"meta":25,"style":25},"docker run \\\n  -e SPRING_PROFILES_ACTIVE=prod \\\n  -e DB_HOST=prod-db \\\n  myapp:1.4.2\n",[27,514,515,524,534,543],{"__ignoreMap":25},[68,516,517,519,521],{"class":70,"line":71},[68,518,93],{"class":74},[68,520,465],{"class":78},[68,522,523],{"class":99}," \\\n",[68,525,526,529,532],{"class":70,"line":82},[68,527,528],{"class":99},"  -e",[68,530,531],{"class":78}," SPRING_PROFILES_ACTIVE=prod",[68,533,523],{"class":99},[68,535,536,538,541],{"class":70,"line":90},[68,537,528],{"class":99},[68,539,540],{"class":78}," DB_HOST=prod-db",[68,542,523],{"class":99},[68,544,545],{"class":70,"line":234},[68,546,547],{"class":78},"  myapp:1.4.2\n",[19,549,552],{"className":550,"code":551,"language":24,"meta":25},[22],"              Docker Image\n              myapp:1.4.2\n                   │\n       ┌───────────┼───────────┐\n       ↓           ↓           ↓\n DEV Container  STAGE Container  PROD Container\n DB=dev         DB=stage         DB=prod\n",[27,553,551],{"__ignoreMap":25},[15,555,556],{},"이미지와 환경 설정을 분리해서 생각해야 한다.",[39,558,560],{"id":559},"_10-secret은-이미지에-넣지-않기","10. Secret은 이미지에 넣지 않기",[15,562,563],{},"다음처럼 비밀번호나 API Key를 이미지에 넣으면 이미지를 받은 사람이 값을 확인할 수 있다.",[19,565,567],{"className":211,"code":566,"language":213,"meta":25,"style":25},"ENV DB_PASSWORD=123456\nENV AWS_SECRET_KEY=example\n",[27,568,569,574],{"__ignoreMap":25},[68,570,571],{"class":70,"line":71},[68,572,573],{},"ENV DB_PASSWORD=123456\n",[68,575,576],{"class":70,"line":82},[68,577,578],{},"ENV AWS_SECRET_KEY=example\n",[15,580,581],{},"다음 값은 이미지 외부의 Secret 관리 시스템에서 관리하고 배포 시점에 주입해야 한다.",[375,583,584,587,590,593],{},[378,585,586],{},"DB Password",[378,588,589],{},"API Key",[378,591,592],{},"AWS Secret Key",[378,594,595],{},"JWT Secret",[15,597,598],{},"대표적인 관리 방법으로 GitHub Actions Secrets, AWS Secrets Manager, Kubernetes Secrets, Vault 등이 있다.",[19,600,603],{"className":601,"code":602,"language":24,"meta":25},[22],"Secret Storage\n→ Deployment\n→ Container Environment\n",[27,604,602],{"__ignoreMap":25},[39,606,608],{"id":607},"_11-image-tagging","11. Image Tagging",[15,610,611,614],{},[27,612,613],{},"myapp:latest","만 사용하면 현재 실행 중인 이미지가 정확히 어떤 버전인지 추적하기 어렵다.",[19,616,619],{"className":617,"code":618,"language":24,"meta":25},[22],"오늘 latest  → v1.5\n내일 latest  → v1.6\n",[27,620,618],{"__ignoreMap":25},[15,622,623],{},"따라서 명시적인 버전이나 Git commit SHA를 태그로 사용한다.",[19,625,628],{"className":626,"code":627,"language":24,"meta":25},[22],"myapp:1.4.2\nmyapp:a8df329\n",[27,629,627],{"__ignoreMap":25},[19,631,634],{"className":632,"code":633,"language":24,"meta":25},[22],"Production Error\n→ 실행 중인 Image 확인\n→ myapp:a8df329\n→ Git commit a8df329 확인\n→ 정확한 Source Code 추적\n",[27,635,633],{"__ignoreMap":25},[15,637,638],{},"이처럼 배포 결과물과 소스 코드의 관계를 추적할 수 있는 성질을 **Traceability(추적 가능성)**라고 한다.",[15,640,641],{},"다음 글에서는 배포 이후의 Health Check, Rollback과 무중단 배포 전략을 살펴본다.",[643,644,645],"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 pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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);}",{"title":25,"searchDepth":82,"depth":82,"links":647},[648],{"id":12,"depth":82,"text":13,"children":649},[650,651,652,653,654,655,656,657,658,659,660],{"id":41,"depth":90,"text":42},{"id":141,"depth":90,"text":142},{"id":169,"depth":90,"text":170},{"id":204,"depth":90,"text":205},{"id":340,"depth":90,"text":341},{"id":369,"depth":90,"text":370},{"id":404,"depth":90,"text":405},{"id":479,"depth":90,"text":480},{"id":505,"depth":90,"text":506},{"id":559,"depth":90,"text":560},{"id":607,"depth":90,"text":608},[662,663],"DevOps","CI-CD","2026-08-17 14:00:00 +0900","Docker 기반 CI\u002FCD의 흐름과 이미지, 컨테이너, Registry, 환경 설정 및 태그 관리 원칙을 정리한다.","md",null,"\u002Fdevops\u002Fci-cd\u002F2026\u002F08\u002F17\u002Fci-cd-02-docker-pipeline\u002F",{"layout":670},"post","2","\u002Fposts\u002Fci-cd-docker\u002Fci-cd\u002F2026-08-17-ci-cd-02-docker-pipeline",{"title":5,"description":665},"posts\u002FCI-CD-Docker\u002FCI-CD\u002F2026-08-17-ci-cd-02-docker-pipeline",[676,677,678,679,680],"CI\u002FCD","Docker","GitHub Actions","Container Registry","Deployment","AFicDgKI8_YGgC0Ri62tSWsnWpMCn0wI-BLmvKG7-20",1788744787400]