[{"data":1,"prerenderedAt":1294},["ShallowReactive",2],{"post-document:\u002Fdevops\u002Fdocker\u002F2026\u002F08\u002F17\u002Fdocker-06-image-build-and-layers\u002F":3},{"id":4,"title":5,"body":6,"categories":1272,"date":1275,"description":1276,"extension":1277,"image":1278,"key_concepts":1278,"last_modified_at":1278,"legacyPath":1279,"meta":1280,"navigation":86,"part":1282,"path":1283,"published":86,"robots":1278,"seo":1284,"series":1285,"stem":1286,"strengths":1278,"summary":1278,"tags":1287,"tradeoffs":1278,"__hash__":1293},"posts\u002Fposts\u002FCI-CD-Docker\u002FDocker\u002F2026-08-17-docker-06-image-build-and-layers.md","Docker 기초 6편: 이미지 Layer와 빌드 최적화",{"type":7,"value":8,"toc":1255},"minimark",[9,14,18,21,241,305,310,313,418,434,438,441,444,451,490,493,501,504,510,517,520,526,529,543,546,581,598,601,610,613,616,652,660,664,667,830,836,850,854,857,861,864,867,870,886,892,895,909,913,1048,1055,1060,1084,1087,1101,1109,1114,1118,1121,1172,1180,1184,1187,1235,1246,1251],[10,11,13],"h2",{"id":12},"컨테이너-이미지-레이어-구조","컨테이너 이미지 레이어 구조",[15,16,17],"p",{},"컨테이너 이미지는 여러 개의 읽기 전용 레이어로 구성된다.",[15,19,20],{},"컨테이너를 실행하면 이미지 레이어 위에 데이터를 쓰고 수정할 수 있는 R\u002FW 레이어가 추가된다.",[22,23,28],"pre",{"className":24,"code":25,"language":26,"meta":27,"style":27},"language-mermaid shiki shiki-themes github-light github-dark","flowchart LR\n    subgraph U[ubuntu Image]\n        direction BT\n        U1[Layer A\u003Cbr\u002F>ubuntu 기반 파일]\n        U2[Layer B\u003Cbr\u002F>기본 라이브러리]\n        U3[Layer C\u003Cbr\u002F>기본 설정]\n        U1 --> U2 --> U3\n    end\n\n    subgraph N[nginx Image]\n        direction BT\n        N1[Layer A\u003Cbr\u002F>ubuntu 기반 파일]\n        N2[Layer B\u003Cbr\u002F>기본 라이브러리]\n        N3[Layer C\u003Cbr\u002F>기본 설정]\n        N4[nginx 설치 Layer]\n        N1 --> N2 --> N3 --> N4\n    end\n\n    subgraph W[web app Image]\n        direction BT\n        W1[Layer A\u003Cbr\u002F>ubuntu 기반 파일]\n        W2[Layer B\u003Cbr\u002F>기본 라이브러리]\n        W3[Layer C\u003Cbr\u002F>기본 설정]\n        W4[nginx 설치 Layer]\n        W5[web app source Layer]\n        W1 --> W2 --> W3 --> W4 --> W5\n    end\n\n    subgraph CT[실행된 Container]\n        direction BT\n        CI[web app Image Layers\u003Cbr\u002F>읽기 전용]\n        RW[R\u002FW Layer\u003Cbr\u002F>쓰기 가능]\n        CI --> RW\n    end\n\n    U --> N --> W -->|docker run| CT\n","mermaid","",[29,30,31,39,45,51,57,63,69,75,81,88,94,99,105,111,117,123,129,134,139,145,150,156,162,168,174,180,186,191,196,202,207,213,219,225,230,235],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,38],{},"flowchart LR\n",[32,40,42],{"class":34,"line":41},2,[32,43,44],{},"    subgraph U[ubuntu Image]\n",[32,46,48],{"class":34,"line":47},3,[32,49,50],{},"        direction BT\n",[32,52,54],{"class":34,"line":53},4,[32,55,56],{},"        U1[Layer A\u003Cbr\u002F>ubuntu 기반 파일]\n",[32,58,60],{"class":34,"line":59},5,[32,61,62],{},"        U2[Layer B\u003Cbr\u002F>기본 라이브러리]\n",[32,64,66],{"class":34,"line":65},6,[32,67,68],{},"        U3[Layer C\u003Cbr\u002F>기본 설정]\n",[32,70,72],{"class":34,"line":71},7,[32,73,74],{},"        U1 --> U2 --> U3\n",[32,76,78],{"class":34,"line":77},8,[32,79,80],{},"    end\n",[32,82,84],{"class":34,"line":83},9,[32,85,87],{"emptyLinePlaceholder":86},true,"\n",[32,89,91],{"class":34,"line":90},10,[32,92,93],{},"    subgraph N[nginx Image]\n",[32,95,97],{"class":34,"line":96},11,[32,98,50],{},[32,100,102],{"class":34,"line":101},12,[32,103,104],{},"        N1[Layer A\u003Cbr\u002F>ubuntu 기반 파일]\n",[32,106,108],{"class":34,"line":107},13,[32,109,110],{},"        N2[Layer B\u003Cbr\u002F>기본 라이브러리]\n",[32,112,114],{"class":34,"line":113},14,[32,115,116],{},"        N3[Layer C\u003Cbr\u002F>기본 설정]\n",[32,118,120],{"class":34,"line":119},15,[32,121,122],{},"        N4[nginx 설치 Layer]\n",[32,124,126],{"class":34,"line":125},16,[32,127,128],{},"        N1 --> N2 --> N3 --> N4\n",[32,130,132],{"class":34,"line":131},17,[32,133,80],{},[32,135,137],{"class":34,"line":136},18,[32,138,87],{"emptyLinePlaceholder":86},[32,140,142],{"class":34,"line":141},19,[32,143,144],{},"    subgraph W[web app Image]\n",[32,146,148],{"class":34,"line":147},20,[32,149,50],{},[32,151,153],{"class":34,"line":152},21,[32,154,155],{},"        W1[Layer A\u003Cbr\u002F>ubuntu 기반 파일]\n",[32,157,159],{"class":34,"line":158},22,[32,160,161],{},"        W2[Layer B\u003Cbr\u002F>기본 라이브러리]\n",[32,163,165],{"class":34,"line":164},23,[32,166,167],{},"        W3[Layer C\u003Cbr\u002F>기본 설정]\n",[32,169,171],{"class":34,"line":170},24,[32,172,173],{},"        W4[nginx 설치 Layer]\n",[32,175,177],{"class":34,"line":176},25,[32,178,179],{},"        W5[web app source Layer]\n",[32,181,183],{"class":34,"line":182},26,[32,184,185],{},"        W1 --> W2 --> W3 --> W4 --> W5\n",[32,187,189],{"class":34,"line":188},27,[32,190,80],{},[32,192,194],{"class":34,"line":193},28,[32,195,87],{"emptyLinePlaceholder":86},[32,197,199],{"class":34,"line":198},29,[32,200,201],{},"    subgraph CT[실행된 Container]\n",[32,203,205],{"class":34,"line":204},30,[32,206,50],{},[32,208,210],{"class":34,"line":209},31,[32,211,212],{},"        CI[web app Image Layers\u003Cbr\u002F>읽기 전용]\n",[32,214,216],{"class":34,"line":215},32,[32,217,218],{},"        RW[R\u002FW Layer\u003Cbr\u002F>쓰기 가능]\n",[32,220,222],{"class":34,"line":221},33,[32,223,224],{},"        CI --> RW\n",[32,226,228],{"class":34,"line":227},34,[32,229,80],{},[32,231,233],{"class":34,"line":232},35,[32,234,87],{"emptyLinePlaceholder":86},[32,236,238],{"class":34,"line":237},36,[32,239,240],{},"    U --> N --> W -->|docker run| CT\n",[22,242,246],{"className":243,"code":244,"language":245,"meta":27,"style":27},"language-dockerfile shiki shiki-themes github-light github-dark","# Layer 1: Ubuntu 기반 이미지 사용\nFROM ubuntu:22.04\n\n# Layer 2: nginx 설치\nRUN apt-get update && \\\n    apt-get install -y --no-install-recommends nginx\n\n# Layer 3: 웹 애플리케이션 소스 복사\nCOPY .\u002Fsrc \u002Fvar\u002Fwww\u002Fhtml\n\n# 컨테이너 실행 시 nginx 시작\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n","dockerfile",[29,247,248,253,258,262,267,272,277,281,286,291,295,300],{"__ignoreMap":27},[32,249,250],{"class":34,"line":35},[32,251,252],{},"# Layer 1: Ubuntu 기반 이미지 사용\n",[32,254,255],{"class":34,"line":41},[32,256,257],{},"FROM ubuntu:22.04\n",[32,259,260],{"class":34,"line":47},[32,261,87],{"emptyLinePlaceholder":86},[32,263,264],{"class":34,"line":53},[32,265,266],{},"# Layer 2: nginx 설치\n",[32,268,269],{"class":34,"line":59},[32,270,271],{},"RUN apt-get update && \\\n",[32,273,274],{"class":34,"line":65},[32,275,276],{},"    apt-get install -y --no-install-recommends nginx\n",[32,278,279],{"class":34,"line":71},[32,280,87],{"emptyLinePlaceholder":86},[32,282,283],{"class":34,"line":77},[32,284,285],{},"# Layer 3: 웹 애플리케이션 소스 복사\n",[32,287,288],{"class":34,"line":83},[32,289,290],{},"COPY .\u002Fsrc \u002Fvar\u002Fwww\u002Fhtml\n",[32,292,293],{"class":34,"line":90},[32,294,87],{"emptyLinePlaceholder":86},[32,296,297],{"class":34,"line":96},[32,298,299],{},"# 컨테이너 실행 시 nginx 시작\n",[32,301,302],{"class":34,"line":101},[32,303,304],{},"CMD [\"nginx\", \"-g\", \"daemon off;\"]\n",[306,307,309],"h3",{"id":308},"이미지-레이어-공유","이미지 레이어 공유",[15,311,312],{},"동일한 레이어는 이미지끼리 공유하기 때문에 중복 다운로드와 업로드가 필요하지 않다.\n(docker는 재탕의 신이라고 할 수 있다.)",[22,314,316],{"className":24,"code":315,"language":26,"meta":27,"style":27},"flowchart TB\n    subgraph SHARED[공통으로 저장된 레이어]\n        L1[Layer 1]\n        L2[Layer 2]\n        L3[Layer 3]\n        L1 --> L2 --> L3\n    end\n\n    L3 --> A[Image A]\n    L3 --> B4[Layer 4] --> B[Image B]\n\n    subgraph LOCAL[로컬에 이미 다운로드된 레이어]\n        L5[Layer 5]\n        L6[Layer 6]\n        L7[Layer 7]\n        L5 --> L6 --> L7\n    end\n\n    L7 --> C[Image C]\n    L7 --> D8[Layer 8\u003Cbr\u002F>추가 다운로드 필요] --> D[Image D]\n    L7 --> E8[Layer 8\u003Cbr\u002F>이미 존재] --> E[Image E\u003Cbr\u002F>다운로드 불필요]\n",[29,317,318,323,328,333,338,343,348,352,356,361,366,370,375,380,385,390,395,399,403,408,413],{"__ignoreMap":27},[32,319,320],{"class":34,"line":35},[32,321,322],{},"flowchart TB\n",[32,324,325],{"class":34,"line":41},[32,326,327],{},"    subgraph SHARED[공통으로 저장된 레이어]\n",[32,329,330],{"class":34,"line":47},[32,331,332],{},"        L1[Layer 1]\n",[32,334,335],{"class":34,"line":53},[32,336,337],{},"        L2[Layer 2]\n",[32,339,340],{"class":34,"line":59},[32,341,342],{},"        L3[Layer 3]\n",[32,344,345],{"class":34,"line":65},[32,346,347],{},"        L1 --> L2 --> L3\n",[32,349,350],{"class":34,"line":71},[32,351,80],{},[32,353,354],{"class":34,"line":77},[32,355,87],{"emptyLinePlaceholder":86},[32,357,358],{"class":34,"line":83},[32,359,360],{},"    L3 --> A[Image A]\n",[32,362,363],{"class":34,"line":90},[32,364,365],{},"    L3 --> B4[Layer 4] --> B[Image B]\n",[32,367,368],{"class":34,"line":96},[32,369,87],{"emptyLinePlaceholder":86},[32,371,372],{"class":34,"line":101},[32,373,374],{},"    subgraph LOCAL[로컬에 이미 다운로드된 레이어]\n",[32,376,377],{"class":34,"line":107},[32,378,379],{},"        L5[Layer 5]\n",[32,381,382],{"class":34,"line":113},[32,383,384],{},"        L6[Layer 6]\n",[32,386,387],{"class":34,"line":119},[32,388,389],{},"        L7[Layer 7]\n",[32,391,392],{"class":34,"line":125},[32,393,394],{},"        L5 --> L6 --> L7\n",[32,396,397],{"class":34,"line":131},[32,398,80],{},[32,400,401],{"class":34,"line":136},[32,402,87],{"emptyLinePlaceholder":86},[32,404,405],{"class":34,"line":141},[32,406,407],{},"    L7 --> C[Image C]\n",[32,409,410],{"class":34,"line":147},[32,411,412],{},"    L7 --> D8[Layer 8\u003Cbr\u002F>추가 다운로드 필요] --> D[Image D]\n",[32,414,415],{"class":34,"line":152},[32,416,417],{},"    L7 --> E8[Layer 8\u003Cbr\u002F>이미 존재] --> E[Image E\u003Cbr\u002F>다운로드 불필요]\n",[419,420,421,425,428,431],"ul",{},[422,423,424],"li",{},"이미지 A를 삭제해도 다른 이미지에서 사용하는 Layer 1, 2, 3은 삭제되지 않는다.",[422,426,427],{},"이미지 C를 이미 다운로드했다면 공통 Layer 5, 6, 7을 다시 다운로드하지 않는다.",[422,429,430],{},"이미지 D에 새로운 Layer 8이 있다면 Layer 8만 추가로 다운로드한다.",[422,432,433],{},"이미지 E의 모든 레이어가 로컬에 있다면 추가 다운로드가 필요하지 않다.",[306,435,437],{"id":436},"docker가-동일한-레이어를-판별하는-방법","Docker가 동일한 레이어를 판별하는 방법",[15,439,440],{},"그렇다면 Docker는 어떻게 동일한 레이어인지 판별하는 것일까?",[15,442,443],{},"솔직히.. 궁금하지 않은가? 난 궁금한데",[15,445,446,447,450],{},"Docker는 레이어의 내용을 SHA-256 방식으로 계산한 ",[29,448,449],{},"Digest","를 이용해 동일한 레이어인지 판별한다.",[22,452,454],{"className":24,"code":453,"language":26,"meta":27,"style":27},"flowchart LR\n    C[레이어의 파일 변경분] --> T[tar 형식의 Layer Blob]\n    T --> H[SHA-256 계산]\n    H --> D[Digest\u003Cbr\u002F>sha256:...]\n    D --> L{로컬에 같은\u003Cbr\u002F>Digest가 있는가?}\n    L -->|있음| R[기존 레이어 재사용]\n    L -->|없음| P[레이어 Push 또는 Pull]\n",[29,455,456,460,465,470,475,480,485],{"__ignoreMap":27},[32,457,458],{"class":34,"line":35},[32,459,38],{},[32,461,462],{"class":34,"line":41},[32,463,464],{},"    C[레이어의 파일 변경분] --> T[tar 형식의 Layer Blob]\n",[32,466,467],{"class":34,"line":47},[32,468,469],{},"    T --> H[SHA-256 계산]\n",[32,471,472],{"class":34,"line":53},[32,473,474],{},"    H --> D[Digest\u003Cbr\u002F>sha256:...]\n",[32,476,477],{"class":34,"line":59},[32,478,479],{},"    D --> L{로컬에 같은\u003Cbr\u002F>Digest가 있는가?}\n",[32,481,482],{"class":34,"line":65},[32,483,484],{},"    L -->|있음| R[기존 레이어 재사용]\n",[32,486,487],{"class":34,"line":71},[32,488,489],{},"    L -->|없음| P[레이어 Push 또는 Pull]\n",[15,491,492],{},"쉽게 말하면 레이어는 포장된 택배 상자이고, Digest는 상자 내용으로 만든 고유한 디지털 송장이라고 생각하면 편할 것 같다.",[22,494,499],{"className":495,"code":497,"language":498,"meta":27},[496],"language-text","Layer  = 파일 변경분을 담은 택배 상자\nDigest = 상자 내용으로 만든 고유 송장 번호\n","text",[29,500,497],{"__ignoreMap":27},[15,502,503],{},"Digest가 레이어를 더 작은 단위로 분리하는 것은 아니다. Dockerfile 명령으로 만들어진 파일 시스템 변경분이 하나의 레이어가 되고, 그 레이어 Blob에 Digest가 부여된다.",[22,505,508],{"className":506,"code":507,"language":498,"meta":27},[496],"Dockerfile 명령 실행\n→ 파일 시스템 변경분 생성\n→ 하나의 Layer로 묶음\n→ SHA-256 Digest 계산\n→ 저장,공유,중복 확인\n",[29,509,507],{"__ignoreMap":27},[15,511,512,513,516],{},"따라서 레이어는 Docker가 저장,전송,캐시하는 기본 단위이지만 반드시 크기가 작지는 않다. 하나의 ",[29,514,515],{},"RUN"," 명령에서 많은 파일을 설치하면 하나의 레이어가 수백 MB가 될 수도 있다.",[15,518,519],{},"Registry의 Image Manifest에는 이미지가 사용하는 레이어의 Digest가 순서대로 기록된다. Docker는 Image를 Pull할 때 Manifest의 Digest와 로컬에 저장된 레이어를 비교한다.",[22,521,524],{"className":522,"code":523,"language":498,"meta":27},[496],"Registry의 Layer Digest\n        ↓\n로컬에 같은 Digest가 있는가?\n        ├─ 있음 → Already exists, 다운로드 생략\n        └─ 없음 → 해당 레이어 다운로드\n",[29,525,523],{"__ignoreMap":27},[15,527,528],{},"이 구조를 통해 다음 작업이 가능하다.",[419,530,531,534,537,540],{},[422,532,533],{},"동일한 레이어의 중복 저장 방지",[422,535,536],{},"이미 존재하는 레이어의 다운로드 생략",[422,538,539],{},"변경된 레이어만 Push 또는 Pull",[422,541,542],{},"다운로드한 레이어의 손상 여부 확인",[15,544,545],{},"이미지의 RootFS 레이어 식별값은 다음 명령으로 확인할 수 있다.",[22,547,551],{"className":548,"code":549,"language":550,"meta":27,"style":27},"language-bash shiki shiki-themes github-light github-dark","docker image inspect nginx \\\n  --format '{{json .RootFS.Layers}}'\n","bash",[29,552,553,573],{"__ignoreMap":27},[32,554,555,559,563,566,569],{"class":34,"line":35},[32,556,558],{"class":557},"sScJk","docker",[32,560,562],{"class":561},"sZZnC"," image",[32,564,565],{"class":561}," inspect",[32,567,568],{"class":561}," nginx",[32,570,572],{"class":571},"sj4cs"," \\\n",[32,574,575,578],{"class":34,"line":41},[32,576,577],{"class":571},"  --format",[32,579,580],{"class":561}," '{{json .RootFS.Layers}}'\n",[582,583,584],"blockquote",{},[15,585,586,589,590,593,594,597],{},[29,587,588],{},"docker image inspect","의 ",[29,591,592],{},"RootFS.Layers","에는 압축을 해제한 레이어 내용의 식별값인 ",[29,595,596],{},"diff_id","가 표시된다. Registry Manifest에서 Push와 Pull에 사용하는 압축된 Layer Blob의 Digest와는 값이 다를 수 있지만, 둘 다 콘텐츠를 기반으로 계산한 식별값.",[15,599,600],{},"같은 Dockerfile 명령을 사용했다고 해서 항상 같은 레이어가 만들어지는 것은 아니다.",[22,602,604],{"className":243,"code":603,"language":245,"meta":27,"style":27},"RUN apt-get update\n",[29,605,606],{"__ignoreMap":27},[32,607,608],{"class":34,"line":35},[32,609,603],{},[15,611,612],{},"명령이 같아도 실행 시점에 내려받은 패키지나 생성된 파일이 달라지면 레이어 내용과 Digest도 달라진다. 반대로 이미지 이름이 다르더라도 실제 Layer Blob의 Digest가 같다면 해당 레이어를 공유할 수 있다.",[15,614,615],{},"레이어의 동일성 판별과 빌드 캐시 판별도 구분해야 한다.",[617,618,619,632],"table",{},[620,621,622],"thead",{},[623,624,625,629],"tr",{},[626,627,628],"th",{},"구분",[626,630,631],{},"주로 확인하는 정보",[633,634,635,644],"tbody",{},[623,636,637,641],{},[638,639,640],"td",{},"레이어 저장,Push,Pull",[638,642,643],{},"Layer Blob의 Digest",[623,645,646,649],{},[638,647,648],{},"Dockerfile 빌드 캐시",[638,650,651],{},"Dockerfile 명령, 부모 상태, 입력 파일과 빌드 설정",[15,653,654,655,659],{},"즉, Digest는 레이어를 잘게 나누는 도구가 아니라 ",[656,657,658],"strong",{},"이미 만들어진 레이어가 같은 내용인지 확인하는 디지털 식별자",".",[10,661,663],{"id":662},"컨테이너-이미지-pushpull","컨테이너 이미지 Push\u002FPull",[15,665,666],{},"기존 이미지에서 변경된 레이어만 Registry에 Push하고, 다른 환경에서도 필요한 레이어만 Pull한다.",[22,668,670],{"className":24,"code":669,"language":26,"meta":27,"style":27},"flowchart LR\n    subgraph BEFORE[1. 수정 전 이미지]\n        direction BT\n        B1[Bins \u002F Libs Layer]\n        A1[App Layer]\n        B1 --> A1\n    end\n\n    subgraph AFTER[2. 이미지 수정]\n        direction BT\n        B2[Bins \u002F Libs Layer\u003Cbr\u002F>기존 레이어]\n        A2[App Layer\u003Cbr\u002F>기존 레이어]\n        NB[변경된 Bins \u002F Libs Layer]\n        NA[변경된 App Layer]\n        B2 --> A2 --> NB --> NA\n    end\n\n    R[(3. Registry\u003Cbr\u002F>이미지 레이어 저장)]\n\n    subgraph ENGINE[4. 다른 Docker Engine]\n        direction BT\n        EB[Bins \u002F Libs Layer\u003Cbr\u002F>기존 보유]\n        EA[App Layer\u003Cbr\u002F>기존 보유]\n        ENB[변경된 Bins \u002F Libs Layer\u003Cbr\u002F>Pull]\n        ENA[변경된 App Layer\u003Cbr\u002F>Pull]\n        EB --> EA --> ENB --> ENA\n    end\n\n    C[5. 변경된 레이어가 추가된\u003Cbr\u002F>새 이미지로 Container 실행]\n\n    BEFORE -->|이미지 수정| AFTER\n    AFTER -->|변경된 레이어만 Push| R\n    R -->|없는 레이어만 Pull| ENGINE\n    ENGINE -->|docker run| C\n",[29,671,672,676,681,685,690,695,700,704,708,713,717,722,727,732,737,742,746,750,755,759,764,768,773,778,783,788,793,797,801,806,810,815,820,825],{"__ignoreMap":27},[32,673,674],{"class":34,"line":35},[32,675,38],{},[32,677,678],{"class":34,"line":41},[32,679,680],{},"    subgraph BEFORE[1. 수정 전 이미지]\n",[32,682,683],{"class":34,"line":47},[32,684,50],{},[32,686,687],{"class":34,"line":53},[32,688,689],{},"        B1[Bins \u002F Libs Layer]\n",[32,691,692],{"class":34,"line":59},[32,693,694],{},"        A1[App Layer]\n",[32,696,697],{"class":34,"line":65},[32,698,699],{},"        B1 --> A1\n",[32,701,702],{"class":34,"line":71},[32,703,80],{},[32,705,706],{"class":34,"line":77},[32,707,87],{"emptyLinePlaceholder":86},[32,709,710],{"class":34,"line":83},[32,711,712],{},"    subgraph AFTER[2. 이미지 수정]\n",[32,714,715],{"class":34,"line":90},[32,716,50],{},[32,718,719],{"class":34,"line":96},[32,720,721],{},"        B2[Bins \u002F Libs Layer\u003Cbr\u002F>기존 레이어]\n",[32,723,724],{"class":34,"line":101},[32,725,726],{},"        A2[App Layer\u003Cbr\u002F>기존 레이어]\n",[32,728,729],{"class":34,"line":107},[32,730,731],{},"        NB[변경된 Bins \u002F Libs Layer]\n",[32,733,734],{"class":34,"line":113},[32,735,736],{},"        NA[변경된 App Layer]\n",[32,738,739],{"class":34,"line":119},[32,740,741],{},"        B2 --> A2 --> NB --> NA\n",[32,743,744],{"class":34,"line":125},[32,745,80],{},[32,747,748],{"class":34,"line":131},[32,749,87],{"emptyLinePlaceholder":86},[32,751,752],{"class":34,"line":136},[32,753,754],{},"    R[(3. Registry\u003Cbr\u002F>이미지 레이어 저장)]\n",[32,756,757],{"class":34,"line":141},[32,758,87],{"emptyLinePlaceholder":86},[32,760,761],{"class":34,"line":147},[32,762,763],{},"    subgraph ENGINE[4. 다른 Docker Engine]\n",[32,765,766],{"class":34,"line":152},[32,767,50],{},[32,769,770],{"class":34,"line":158},[32,771,772],{},"        EB[Bins \u002F Libs Layer\u003Cbr\u002F>기존 보유]\n",[32,774,775],{"class":34,"line":164},[32,776,777],{},"        EA[App Layer\u003Cbr\u002F>기존 보유]\n",[32,779,780],{"class":34,"line":170},[32,781,782],{},"        ENB[변경된 Bins \u002F Libs Layer\u003Cbr\u002F>Pull]\n",[32,784,785],{"class":34,"line":176},[32,786,787],{},"        ENA[변경된 App Layer\u003Cbr\u002F>Pull]\n",[32,789,790],{"class":34,"line":182},[32,791,792],{},"        EB --> EA --> ENB --> ENA\n",[32,794,795],{"class":34,"line":188},[32,796,80],{},[32,798,799],{"class":34,"line":193},[32,800,87],{"emptyLinePlaceholder":86},[32,802,803],{"class":34,"line":198},[32,804,805],{},"    C[5. 변경된 레이어가 추가된\u003Cbr\u002F>새 이미지로 Container 실행]\n",[32,807,808],{"class":34,"line":204},[32,809,87],{"emptyLinePlaceholder":86},[32,811,812],{"class":34,"line":209},[32,813,814],{},"    BEFORE -->|이미지 수정| AFTER\n",[32,816,817],{"class":34,"line":215},[32,818,819],{},"    AFTER -->|변경된 레이어만 Push| R\n",[32,821,822],{"class":34,"line":221},[32,823,824],{},"    R -->|없는 레이어만 Pull| ENGINE\n",[32,826,827],{"class":34,"line":227},[32,828,829],{},"    ENGINE -->|docker run| C\n",[22,831,834],{"className":832,"code":833,"language":498,"meta":27},[496],"기존 이미지 → 이미지 수정 → 변경된 레이어만 Push → Registry\n                                                    ↓\n컨테이너 실행 ← 새 이미지 구성 ← 없는 레이어만 Pull ← 다른 Docker Engine\n",[29,835,833],{"__ignoreMap":27},[419,837,838,841,844,847],{},[422,839,840],{},"변경되지 않은 기존 레이어는 다시 업로드하거나 다운로드하지 않는다.",[422,842,843],{},"수정된 App, Bins\u002FLibs 레이어만 Registry로 Push한다.",[422,845,846],{},"다른 Docker Engine은 로컬에 없는 레이어만 Pull한다.",[422,848,849],{},"기존 레이어와 새 레이어를 결합한 이미지로 컨테이너를 실행한다.",[306,851,853],{"id":852},"이미지-생성과-push","이미지 생성과 Push",[15,855,856],{},"공개 이미지를 그대로 사용할 수도 있지만, 필요한 실행 환경이 없다면 Dockerfile로 직접 이미지를 만든 뒤 Registry에 Push한다.",[10,858,860],{"id":859},"멀티-스테이지-빌드","멀티 스테이지 빌드",[15,862,863],{},"멀티 스테이지 빌드는 빌드 환경과 실행 환경을 분리한다.\n(쓸모없는 파일을 굳이 컨테이너에 올릴 필요가 없지 않는가!)",[15,865,866],{},"Vue를 에시로 들어보자.",[15,868,869],{},"아래와 같이 진행하면 멀---티 스테이지 빌드이다.",[871,872,873,880],"ol",{},[422,874,875,876,879],{},"Node.js, npm, Vite로 Vue 소스 코드를 빌드해 ",[29,877,878],{},"dist"," 디렉터리를 만든다.",[422,881,882,883,885],{},"실행 단계의 Nginx 이미지에는 ",[29,884,878],{}," 디렉터리만 복사한다.",[22,887,890],{"className":888,"code":889,"language":498,"meta":27},[496],"Vue 소스 코드\n  ↓ Node.js, npm, Vite로 빌드\ndist 디렉터리 생성\n  ↓ dist만 복사\nNginx가 정적 파일 제공\n",[29,891,889],{"__ignoreMap":27},[15,893,894],{},"장점은 다음과 같다.",[419,896,897,900,903,906],{},[422,898,899],{},"최종 이미지 크기 감소",[422,901,902],{},"배포와 다운로드 속도 향상",[422,904,905],{},"소스 코드와 빌드 도구가 최종 이미지에서 제외되어 공격 표면 감소",[422,907,908],{},"빌드 환경과 실행 환경의 명확한 분리",[306,910,912],{"id":911},"vue-애플리케이션-예시","Vue 애플리케이션 예시",[22,914,916],{"className":243,"code":915,"language":245,"meta":27,"style":27},"# Stage 1: Vue 프로젝트 빌드\nFROM node:22-alpine AS builder\n\nWORKDIR \u002Fapp\n\n# 의존성 파일을 먼저 복사하여 Docker 빌드 캐시 활용\nCOPY package*.json .\u002F\nRUN npm ci\n\n# 소스 코드를 복사하고 배포용 파일 생성\nCOPY . .\nRUN npm run build\n\n# Stage 2: Nginx를 이용한 실제 서비스\nFROM nginx:stable-alpine\n\nWORKDIR \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\n\n# Nginx 기본 페이지 제거\nRUN rm -rf .\u002F*\n\n# builder 단계에서 생성한 dist만 복사\nCOPY --from=builder \u002Fapp\u002Fdist .\n\nEXPOSE 80\n\n# Nginx를 포그라운드로 실행\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n",[29,917,918,923,928,932,937,941,946,951,956,960,965,970,975,979,984,989,993,998,1002,1007,1012,1016,1021,1026,1030,1035,1039,1044],{"__ignoreMap":27},[32,919,920],{"class":34,"line":35},[32,921,922],{},"# Stage 1: Vue 프로젝트 빌드\n",[32,924,925],{"class":34,"line":41},[32,926,927],{},"FROM node:22-alpine AS builder\n",[32,929,930],{"class":34,"line":47},[32,931,87],{"emptyLinePlaceholder":86},[32,933,934],{"class":34,"line":53},[32,935,936],{},"WORKDIR \u002Fapp\n",[32,938,939],{"class":34,"line":59},[32,940,87],{"emptyLinePlaceholder":86},[32,942,943],{"class":34,"line":65},[32,944,945],{},"# 의존성 파일을 먼저 복사하여 Docker 빌드 캐시 활용\n",[32,947,948],{"class":34,"line":71},[32,949,950],{},"COPY package*.json .\u002F\n",[32,952,953],{"class":34,"line":77},[32,954,955],{},"RUN npm ci\n",[32,957,958],{"class":34,"line":83},[32,959,87],{"emptyLinePlaceholder":86},[32,961,962],{"class":34,"line":90},[32,963,964],{},"# 소스 코드를 복사하고 배포용 파일 생성\n",[32,966,967],{"class":34,"line":96},[32,968,969],{},"COPY . .\n",[32,971,972],{"class":34,"line":101},[32,973,974],{},"RUN npm run build\n",[32,976,977],{"class":34,"line":107},[32,978,87],{"emptyLinePlaceholder":86},[32,980,981],{"class":34,"line":113},[32,982,983],{},"# Stage 2: Nginx를 이용한 실제 서비스\n",[32,985,986],{"class":34,"line":119},[32,987,988],{},"FROM nginx:stable-alpine\n",[32,990,991],{"class":34,"line":125},[32,992,87],{"emptyLinePlaceholder":86},[32,994,995],{"class":34,"line":131},[32,996,997],{},"WORKDIR \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\n",[32,999,1000],{"class":34,"line":136},[32,1001,87],{"emptyLinePlaceholder":86},[32,1003,1004],{"class":34,"line":141},[32,1005,1006],{},"# Nginx 기본 페이지 제거\n",[32,1008,1009],{"class":34,"line":147},[32,1010,1011],{},"RUN rm -rf .\u002F*\n",[32,1013,1014],{"class":34,"line":152},[32,1015,87],{"emptyLinePlaceholder":86},[32,1017,1018],{"class":34,"line":158},[32,1019,1020],{},"# builder 단계에서 생성한 dist만 복사\n",[32,1022,1023],{"class":34,"line":164},[32,1024,1025],{},"COPY --from=builder \u002Fapp\u002Fdist .\n",[32,1027,1028],{"class":34,"line":170},[32,1029,87],{"emptyLinePlaceholder":86},[32,1031,1032],{"class":34,"line":176},[32,1033,1034],{},"EXPOSE 80\n",[32,1036,1037],{"class":34,"line":182},[32,1038,87],{"emptyLinePlaceholder":86},[32,1040,1041],{"class":34,"line":188},[32,1042,1043],{},"# Nginx를 포그라운드로 실행\n",[32,1045,1046],{"class":34,"line":193},[32,1047,304],{},[10,1049,1051,1052,1054],{"id":1050},"dockerfile의-run-명령","Dockerfile의 ",[29,1053,515],{}," 명령",[15,1056,1057,1059],{},[29,1058,515],{},"은 이미지를 빌드하는 과정에서 명령을 실행할 때 사용한다.",[22,1061,1063],{"className":243,"code":1062,"language":245,"meta":27,"style":27},"RUN apt-get update && \\\n    apt-get install -y --no-install-recommends nginx && \\\n    rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*\nRUN mkdir -p \u002Fapp && chmod 755 \u002Fapp\n",[29,1064,1065,1069,1074,1079],{"__ignoreMap":27},[32,1066,1067],{"class":34,"line":35},[32,1068,271],{},[32,1070,1071],{"class":34,"line":41},[32,1072,1073],{},"    apt-get install -y --no-install-recommends nginx && \\\n",[32,1075,1076],{"class":34,"line":47},[32,1077,1078],{},"    rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*\n",[32,1080,1081],{"class":34,"line":53},[32,1082,1083],{},"RUN mkdir -p \u002Fapp && chmod 755 \u002Fapp\n",[15,1085,1086],{},"주요 용도는 다음과 같다.",[419,1088,1089,1092,1095,1098],{},[422,1090,1091],{},"프로그램과 패키지 설치",[422,1093,1094],{},"파일과 디렉터리 생성",[422,1096,1097],{},"파일 권한 변경",[422,1099,1100],{},"애플리케이션 빌드",[15,1102,1103,1105,1106,1108],{},[29,1104,515],{},"으로 파일 시스템에 적용된 변경 사항은 이미지 레이어에 저장된다. 서로 관련된 명령은 하나의 ",[29,1107,515],{},"으로 묶으면 불필요한 중간 파일을 같은 레이어에서 제거할 수 있다.",[582,1110,1111],{},[15,1112,1113],{},"레이어를 너무 많이 만드는 것도 좋지 않다. 최대한 서로 관련된 기능은 최대한 묶는 것을 추천한다.",[10,1115,1117],{"id":1116},"빌드-캐시","빌드 캐시",[15,1119,1120],{},"Docker는 이전 빌드 결과를 캐시한다. Dockerfile 명령과 관련 파일이 바뀌지 않았다면 기존 결과를 재사용해 빌드 시간을 줄인다.",[22,1122,1124],{"className":548,"code":1123,"language":550,"meta":27,"style":27},"# 일반 빌드: 사용할 수 있는 캐시 재사용\ndocker build -t my-app .\n\n# 캐시 없이 모든 단계를 다시 실행\ndocker build --no-cache -t my-app .\n",[29,1125,1126,1132,1148,1152,1157],{"__ignoreMap":27},[32,1127,1128],{"class":34,"line":35},[32,1129,1131],{"class":1130},"sJ8bj","# 일반 빌드: 사용할 수 있는 캐시 재사용\n",[32,1133,1134,1136,1139,1142,1145],{"class":34,"line":41},[32,1135,558],{"class":557},[32,1137,1138],{"class":561}," build",[32,1140,1141],{"class":571}," -t",[32,1143,1144],{"class":561}," my-app",[32,1146,1147],{"class":561}," .\n",[32,1149,1150],{"class":34,"line":47},[32,1151,87],{"emptyLinePlaceholder":86},[32,1153,1154],{"class":34,"line":53},[32,1155,1156],{"class":1130},"# 캐시 없이 모든 단계를 다시 실행\n",[32,1158,1159,1161,1163,1166,1168,1170],{"class":34,"line":59},[32,1160,558],{"class":557},[32,1162,1138],{"class":561},[32,1164,1165],{"class":571}," --no-cache",[32,1167,1141],{"class":571},[32,1169,1144],{"class":561},[32,1171,1147],{"class":561},[582,1173,1174],{},[15,1175,1176,1179],{},[29,1177,1178],{},"--no-cache","는 이전 빌드 결과를 재사용하지 않고 Dockerfile의 모든 단계를 새로 실행하는 옵션이다.",[306,1181,1183],{"id":1182},"명령-순서와-캐시-무효화","명령 순서와 캐시 무효화",[15,1185,1186],{},"Dockerfile은 위에서 아래로 처리된다. 특정 단계의 입력이 바뀌어 캐시를 사용할 수 없으면 그 단계와 이후 단계도 다시 실행된다.",[22,1188,1190],{"className":243,"code":1189,"language":245,"meta":27,"style":27},"FROM ubuntu:22.04\n\nRUN apt-get update && \\\n    apt-get install -y --no-install-recommends python3 && \\\n    rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*\n\nWORKDIR \u002Fapp\nCOPY webserver.py .\n\nCMD [\"python3\", \"webserver.py\"]\n",[29,1191,1192,1196,1200,1204,1209,1213,1217,1221,1226,1230],{"__ignoreMap":27},[32,1193,1194],{"class":34,"line":35},[32,1195,257],{},[32,1197,1198],{"class":34,"line":41},[32,1199,87],{"emptyLinePlaceholder":86},[32,1201,1202],{"class":34,"line":47},[32,1203,271],{},[32,1205,1206],{"class":34,"line":53},[32,1207,1208],{},"    apt-get install -y --no-install-recommends python3 && \\\n",[32,1210,1211],{"class":34,"line":59},[32,1212,1078],{},[32,1214,1215],{"class":34,"line":65},[32,1216,87],{"emptyLinePlaceholder":86},[32,1218,1219],{"class":34,"line":71},[32,1220,936],{},[32,1222,1223],{"class":34,"line":77},[32,1224,1225],{},"COPY webserver.py .\n",[32,1227,1228],{"class":34,"line":83},[32,1229,87],{"emptyLinePlaceholder":86},[32,1231,1232],{"class":34,"line":90},[32,1233,1234],{},"CMD [\"python3\", \"webserver.py\"]\n",[15,1236,1237,1238,1241,1242,1245],{},"예를 들어 ",[29,1239,1240],{},"webserver.py","만 변경되면 앞의 패키지 설치 단계는 캐시를 재사용하고, ",[29,1243,1244],{},"COPY webserver.py ."," 이후 단계만 다시 처리할 수 있다. 이를 활용하려면 자주 바뀌지 않는 명령을 앞쪽에, 자주 바뀌는 소스 코드 복사를 뒤쪽에 배치한다.",[582,1247,1248],{},[15,1249,1250],{},"여기서 앞뒤 관계는 Dockerfile 명령과 이미지 레이어의 순서를 설명하는 표현이다. 공식적인 \"부모,자식 컨테이너\" 종류가 따로 있는 것은 아니다.",[1252,1253,1254],"style",{},"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 .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 pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":27,"searchDepth":41,"depth":41,"links":1256},[1257,1261,1264,1267,1269],{"id":12,"depth":41,"text":13,"children":1258},[1259,1260],{"id":308,"depth":47,"text":309},{"id":436,"depth":47,"text":437},{"id":662,"depth":41,"text":663,"children":1262},[1263],{"id":852,"depth":47,"text":853},{"id":859,"depth":41,"text":860,"children":1265},[1266],{"id":911,"depth":47,"text":912},{"id":1050,"depth":41,"text":1268},"Dockerfile의 RUN 명령",{"id":1116,"depth":41,"text":1117,"children":1270},[1271],{"id":1182,"depth":47,"text":1183},[1273,1274],"DevOps","Docker","2026-08-17 12:25:00 +0900","컨테이너 이미지 Layer, Push와 Pull, 멀티 스테이지 빌드, RUN, 빌드 캐시와 CMD를 정리한다.","md",null,"\u002Fdevops\u002Fdocker\u002F2026\u002F08\u002F17\u002Fdocker-06-image-build-and-layers\u002F",{"layout":1281},"post","6","\u002Fposts\u002Fci-cd-docker\u002Fdocker\u002F2026-08-17-docker-06-image-build-and-layers",{"title":5,"description":1276},"Docker 기초","posts\u002FCI-CD-Docker\u002FDocker\u002F2026-08-17-docker-06-image-build-and-layers",[1274,1288,1289,1290,1291,1292],"Image","Layer","Registry","Multi-stage Build","Build Cache","98VCgRrMO1hXLwy4HzjiHxL8IwgB1YNSHveyazjy_-g",1788744787638]