글 목록으로 돌아가기

CloudNative

Dockerfile

Dockerfile의 주요 Instruction과 Image Build 방법 및 작성 시 주의 사항

Dohyeon Kim
Dohyeon Kim 2026년 8월 18일 · 13분 읽기 · 수정 2026년 8월 20일
CloudNative AutoEverSW Docker

1 ) Infrastructure as Code, IaC


Infrastructure를 CLI로 직접 구성하면 설치 순서, Package의 의존 관계, 환경 설정을 모두 사람이 관리해야 한다. APM(Apache, PHP, MySQL)처럼 여러 Software가 연동되는 환경은 작업 누락과 설정 오류가 발생하기 쉽고, 동일한 환경을 다시 만드는 데에도 많은 시간이 든다.

IaC(Infrastructure as Code)는 Infrastructure의 구성과 절차를 Code로 정의하고 Version을 관리하는 방식이다. Dockerfile 역시 Application 실행 환경과 Image 생성 과정을 Text로 기록하므로 동일한 Image를 반복해서 Build하고 배포할 수 있다.

2 ) Dockerfile 개요


Dockerfile은 Docker Image를 생성하기 위한 Instruction을 순서대로 작성한 Text File이다.

  • Image를 어떤 Base Image와 명령으로 만들었는지 기록할 수 있다.

  • 동일한 실행 환경을 반복해서 생성할 수 있다.

  • Source Code와 함께 Version을 관리하고 배포할 수 있다.

  • Container가 시작될 때 실행할 명령과 기본 설정을 정의할 수 있다.

Dockerfile의 각 Instruction은 Image Layer와 Build Cache에 영향을 준다. 변경 빈도가 낮은 작업을 앞에, Source Code처럼 자주 변경되는 작업을 뒤에 배치하면 Cache를 효율적으로 재사용할 수 있다.

3 ) Dockerfile 주요 Instruction


FROM

Build의 기반이 되는 Base Image를 지정한다. 일반적인 Dockerfile은 FROM으로 시작하며 Multi-stage Build에서는 여러 번 사용할 수 있다.

FROM ubuntu:24.04
FROM python:3.13-slim

Tag를 생략하면 기본적으로 latest가 사용되지만 Build 결과를 예측하기 어려워질 수 있으므로 Version Tag나 Digest를 명시하는 편이 좋다. slim이나 alpine은 Image 크기를 줄이는 데 도움이 되지만 C Library와 Package 구성이 다르므로 Application 호환성을 먼저 확인한다.

MAINTAINER

MAINTAINER는 Image 작성자 정보를 기록하던 Instruction이다.

MAINTAINER adam.park <itstudy@example.com>

현재 MAINTAINER는 사용이 중단된 Deprecated Instruction이다. 새 Dockerfile에서는 OCI Annotation에 해당하는 LABEL을 사용한다.

LABEL org.opencontainers.image.authors="adam.park <itstudy@example.com>"

LABEL

Image의 Version, 설명, License와 같은 Metadata를 Key-Value 형식으로 기록한다. 여러 개를 지정할 수 있다.

LABEL org.opencontainers.image.version="1.0"
LABEL org.opencontainers.image.description="web service"
LABEL org.opencontainers.image.licenses="MIT"

RUN

Image를 Build하는 동안 Package를 설치하거나 File을 변경하는 명령을 실행한다. Shell 형식과 Exec 형식을 사용할 수 있다.

# Shell 형식
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      curl \
      nginx && \
    rm -rf /var/lib/apt/lists/*

# Exec 형식
RUN ["/bin/bash", "-c", "echo build-complete > /build.txt"]

RUN은 Layer를 만들므로 관련 작업은 하나의 RUN에서 수행하고 Package 목록도 같은 Layer에서 제거한다.

apt autoclean, apt autoremove 명령은 현재도 사용할 수 있지만 Container Image Build에서는 설치 직후 /var/lib/apt/lists/*를 제거하고 불필요한 Package를 처음부터 설치하지 않는 방식이 더 명확하다.

Multi-stage Build는 Compiler와 Build 도구가 들어 있는 Stage에서 결과물을 만든 뒤 실행에 필요한 File만 최종 Stage로 복사하여 Image 크기와 공격 표면을 줄이는 방식이다.

CMD

Container가 시작될 때 실행할 기본 명령 또는 ENTRYPOINT의 기본 인자를 지정한다. 여러 번 작성해도 마지막 CMD만 적용된다.

CMD ["nginx", "-g", "daemon off;"]
CMD ["python", "app.py"]

Exec 형식은 불필요한 Shell을 거치지 않아 Signal 전달을 예측하기 쉬우므로 일반적으로 권장된다. docker run IMAGE COMMAND처럼 Image 뒤에 명령을 전달하면 CMD를 대체할 수 있다.

ENTRYPOINT

Container의 주 실행 명령을 지정한다. ENTRYPOINT를 실행 파일로, CMD를 기본 인자로 조합할 수 있다.

ENTRYPOINT ["python"]
CMD ["runapp.py"]

이 Image를 인자 없이 실행하면 python runapp.py가 실행된다. docker run IMAGE other.py로 실행하면 CMD만 바뀌어 python other.py가 실행된다. ENTRYPOINTdocker run --entrypoint로 변경할 수 있으므로 절대 변경할 수 없는 명령이라는 의미는 아니다.

초기화 Script를 사용하는 예시는 다음과 같다.

COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

COPY

Build Context의 File이나 Directory를 Image에 복사한다. Build Context 밖의 경로는 복사할 수 없다.

COPY index.html /usr/share/nginx/html/index.html
COPY runapp.py /app/runapp.py

단순한 Local File 복사에는 동작이 명확한 COPY를 우선 사용한다.

ADD

COPY처럼 File을 복사하며 Local Tar Archive의 자동 압축 해제와 Remote URL 등의 추가 기능을 제공한다.

ADD application.tar.gz /app/
ADD https://example.com/data.json /app/data.json

ADD는 현재도 사용되는 Instruction이지만 자동 압축 해제 같은 동작이 필요할 때만 사용한다. Remote File은 RUN curl 또는 RUN wget으로 내려받으면 검증과 권한 설정 과정을 더 명시적으로 작성할 수 있다.

ENV

Build 과정과 실행될 Container에서 사용할 환경 변수를 설정한다.

ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk
ENV PATH="${JAVA_HOME}/bin:${PATH}"

EXPOSE

Container가 어떤 Port와 Protocol에서 요청을 받을지 문서화한다.

EXPOSE 80
EXPOSE 8080/tcp

EXPOSE만으로 Host Port가 공개되지는 않는다. 외부에서 접근하려면 Container 실행 시 -p HOST_PORT:CONTAINER_PORT 또는 -P를 사용해야 한다.

구분 역할 실제 Listen 여부 Host 외부 접근
애플리케이션 Nginx, Spring Boot 등이 Container 내부 Port에서 요청을 대기한다. O X
EXPOSE 80 Container가 80 Port를 사용한다는 것을 명시하는 메타데이터이다. X X
-p 8080:80 Host의 8080 Port를 Container의 80 Port에 직접 연결한다. X O
-P EXPOSE로 명시된 Port를 Host의 임의 Port에 자동으로 연결한다. X O

중간 정리

  • EXPOSE는 Port를 실제로 열거나 Listen 상태로 만드는 명령이 아니다.

  • 실제 Listen은 Container 내부의 Application이 수행한다.

  • -pEXPOSE 여부와 관계없이 Host와 Container의 Port를 직접 연결한다.

  • -PEXPOSE에 명시된 Port를 기준으로 Host의 임의 Port에 연결한다.

VOLUME

Container에서 Volume으로 사용할 Mount Point를 지정한다.

VOLUME ["/var/log"]
VOLUME ["/var/www/html"]

VOLUME은 해당 경로를 Host의 /var/lib/docker와 문자 그대로 Bind Mount하는 명령이 아니다. Container 실행 시 Docker가 관리하는 Anonymous Volume의 Mount Point를 선언한다. Volume 이름, Host Bind Mount 경로 등 구체적인 연결은 docker run --mount나 Compose File에서 지정하는 편이 관리하기 쉽다.

USER

이후의 RUN과 Container 실행 시 사용할 사용자 또는 Group을 지정한다. 기본 사용자는 root이므로 Application에 관리자 권한이 필요하지 않다면 별도 사용자를 만드는 것이 안전하다.

RUN useradd --create-home appuser
USER appuser

WORKDIR

이후 RUN, CMD, ENTRYPOINT, COPY, ADD의 기준 Directory를 지정한다. 경로가 없으면 자동으로 생성된다.

WORKDIR /workspace
COPY . .

ARG

Build할 때만 사용할 변수를 선언하고 --build-arg로 값을 전달한다.

ARG DB_NAME=itstudy
RUN echo "database=${DB_NAME}"
docker build --build-arg DB_NAME=production -t my-app:1.0 .

ARG는 비밀번호나 Secret을 안전하게 숨기는 기능이 아니다. 값이 Image History나 Build Cache에 남을 수 있으므로 민감 정보를 전달하는 용도로 사용하지 않는다.

ONBUILD

현재 Image가 다른 Dockerfile의 Base Image로 사용될 때 실행할 Trigger Instruction을 등록한다.

ONBUILD COPY . /app/src

ONBUILD는 현재도 지원되지만 상속된 동작이 눈에 잘 드러나지 않아 예상하지 못한 Build 실패를 만들 수 있다. 전용 Base Image처럼 사용 목적이 명확한 경우에 제한적으로 사용한다.

STOPSIGNAL

Container를 중지할 때 주 Process에 전달할 System Call Signal을 지정한다. 기본값은 일반적으로 SIGTERM이다.

STOPSIGNAL SIGTERM

STOPSIGNAL SIGKILL도 문법상 지정할 수 있지만 Process가 종료 작업을 수행할 기회를 잃으므로 일반적인 설정으로는 권장하지 않는다.

SHELL

Shell 형식의 RUN, CMD, ENTRYPOINT에 사용할 기본 Shell을 변경한다.

SHELL ["/bin/bash", "-c"]

HEALTHCHECK

Container 내부에서 명령을 주기적으로 실행하여 Application의 상태를 확인한다. 여러 번 작성하면 마지막 하나만 적용된다.

HEALTHCHECK --interval=1m --timeout=3s --retries=5 \
  CMD curl --fail http://localhost/ || exit 1
종료 Code 의미
0 정상 상태
1 비정상 상태
2 예약된 값이므로 사용하지 않음

기본 intervaltimeout은 각각 30초이며 기본 retries는 3회이다.

4 ) Dockerfile 작성 방법


Dockerfile은 보통 다음 원칙으로 작성한다.

  • 필요한 Application이나 Runtime이 이미 설치된 신뢰할 수 있는 Base Image를 선택한다.

  • Version Tag를 명시하여 Build 결과를 일정하게 유지한다.

  • 변경 빈도가 낮은 Instruction을 먼저 배치하여 Build Cache를 활용한다.

  • Package 설치와 Cache 삭제를 같은 RUN에서 처리한다.

  • 가능한 경우 권한이 제한된 사용자로 Application을 실행한다.

  • Compiler가 필요한 Application은 Multi-stage Build를 고려한다.

5 ) Image Build


기본 형식은 다음과 같다.

docker build [OPTIONS] PATH | URL | -
# 현재 Directory를 Build Context로 사용하고 이름과 Tag 지정
docker build -t apache-example:1.0 .

# 다른 이름의 Dockerfile 지정
docker build -f Dockerfile.dev -t apache-example:dev .

# Git Repository를 Build Context로 사용
docker build -t php-server:2.0 \
  https://github.com/example/docker-phpserver.git

# 표준 입력으로 전달한 Tar Archive를 Build Context로 사용
docker build - < context.tar.gz

-t는 Image의 이름과 Tag를 지정하고 -f는 Dockerfile의 경로 또는 이름을 지정한다. 마지막 인자인 PATH, URL, -는 Dockerfile 자체가 아니라 Build Context를 뜻한다. 압축된 Build Context는 -f가 아니라 표준 입력으로 전달한다.

6 ) Apache Main Page Image


index.html

<!doctype html>
<html lang="ko">
  <head>
    <meta charset="utf-8">
    <title>Docker Image Build</title>
  </head>
  <body>
    <h1>Apache Web Server</h1>
  </body>
</html>

Dockerfile

FROM httpd:2.4
COPY index.html /usr/local/apache2/htdocs/index.html

Build 및 실행

docker build -t apache-example:1.0 .
docker container run \
  --name apache-example \
  -d \
  -p 8000:80 \
  apache-example:1.0

Browser 또는 curl http://localhost:8000으로 변경된 Page를 확인한다.

7 ) Nginx Main Page Image


Nginx 공식 Image의 기본 문서 경로는 /usr/share/nginx/html이다.

본 실습의 /var/www/html은 Ubuntu 등에 Package로 설치한 Nginx에서 자주 사용하는 경로이며 공식 Docker Image의 기본 경로와 다르다.

Dockerfile

FROM nginx
COPY index.html /usr/share/nginx/html/index.html

Build 및 실행

docker build -t nginx-example:1.0 .
docker container run \
  --name nginx-example \
  -d \
  -p 9000:80 \
  nginx-example:1.0

중간 정리

  • Dockerfile과 index.html을 같은 Build Context에 두고 Image를 Build한다.

  • Apache 공식 Image의 기본 문서 경로는 /usr/local/apache2/htdocs이다.

  • Nginx 공식 Image의 기본 문서 경로는 /usr/share/nginx/html이다.

  • Build한 Image를 Container로 실행하고 Host Port를 연결하여 Page를 확인한다.

8 ) Dockerfile을 사용하는 이유


Dockerfile을 사용하지 않아도 실행 중인 Container를 수정한 뒤 docker commit으로 Image를 만들 수 있다. 다음 실습은 Ubuntu Container에 Apache와 PHP를 직접 설치하고 그 결과를 Image로 저장한다.

Container를 직접 수정하여 Image 생성

  1. Ubuntu Container를 생성하고 Terminal에 접속한다.

    docker container run \
      --name myweb \
      -p 8000:80 \
      -it \
      ubuntu:20.04
    
  2. Package 정보를 갱신하고 Apache와 PHP를 설치한다. Base Image는 실행에 필요한 최소 Package만 포함하므로 설치 전에 Package Index를 갱신해야 한다.

    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y apache2 php
    service apache2 start
    
  3. 기본 Page를 보관하고 새로운 HTML 및 PHP Page를 작성한다.

    mv /var/www/html/index.html /var/www/html/index.html.org
    echo '<h1>Hello Docker Application</h1>' > /var/www/html/index.html
    printf '%s\n' '<?php phpinfo(); ?>' > /var/www/html/index.php
    
  4. 다른 Terminal에서 두 Page에 접속한다.

    curl http://localhost:8000
    curl http://localhost:8000/index.php
    
  5. 실행 중인 Container를 새로운 Image로 저장한다.

    docker container commit myweb myphpapp:1.0
    
  6. 저장한 Image로 새로운 Container를 실행한다. 기존 myweb이 Host의 8000번 Port를 사용하고 있다면 먼저 중지하거나 다른 Host Port를 선택한다.

    docker container stop myweb
    docker container run \
      --name myphpapp \
      -d \
      -p 8000:80 \
      myphpapp:1.0
    

docker commit은 현재 Container의 File System 변경 사항을 빠르게 Image로 저장한다. 하지만 설치 명령과 수정 순서가 Image만으로는 드러나지 않으므로 같은 환경을 검토하고 반복해서 만들기 어렵다.

강의에서 사용한 ubuntu:14.04php5 조합은 과거 환경을 재현하는 Legacy 예제이다. Ubuntu 14.04의 일반 지원은 종료되었으며 PHP 5도 공식 지원이 종료되었다. 실습의 수동 구성 흐름은 유지하되, 실행 가능한 예제에는 ubuntu:20.04와 해당 배포판에서 제공하는 php Package를 사용한다.

Dockerfile로 동일한 과정 자동화

수동 작업을 Dockerfile로 옮기면 Image 생성 절차를 Source Code와 함께 관리할 수 있다.

FROM ubuntu:20.04

LABEL org.opencontainers.image.authors="adam <itstudy@kakao.com>"
LABEL org.opencontainers.image.title="IaC PHP Web Application"

RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      apache2 \
      php && \
    rm -rf /var/lib/apt/lists/*

ENV APACHE2_RUN_USER=www-data \
    APACHE2_RUN_GROUP=www-data \
    APACHE2_LOG_DIR=/var/log/apache2 \
    APACHE2_WEB_DIR=/var/www/html \
    APACHE2_PID_FILE=/var/run/apache2/apache2.pid

RUN echo 'Hello Docker Application.' > /var/www/html/index.html && \
    printf '%s\n' '<?php phpinfo(); ?>' > /var/www/html/index.php

WORKDIR /var/www/html

EXPOSE 80

CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

Image를 Build하고 Container로 실행한다.

docker build -t myphpapp:latest .
docker container run \
  --name myphpapp \
  -d \
  -p 8000:80 \
  myphpapp:latest

두 방식의 차이는 다음과 같다.

구분 Container 수정 후 commit Dockerfile Build
작업 기록 별도로 기록해야 함 Instruction으로 남음
반복 생성 수동 작업을 다시 수행 같은 명령으로 재Build
변경 검토 Image 결과만으로 확인하기 어려움 Text 변경 이력으로 검토 가능
주요 용도 임시 상태 저장, 조사 반복 가능한 Image 제작

Dockerfile은 Package, 설정, Source Code와 실행 명령을 선언하므로 다음 장점이 있다.

  • 누락하기 쉬운 설치 및 설정 순서를 Code로 고정한다.

  • 동일한 실행 환경을 반복해서 생성한다.

  • 변경 이력을 Version Control로 관리한다.

  • CI/CD Pipeline에서 대화 없이 Image를 자동으로 Build한다.

다만 Dockerfile만으로 Database와 Web Application처럼 여러 Container의 실행 관계까지 모두 정의하지는 않는다. 여러 Service의 Network, Volume, 환경 변수와 실행 순서는 Docker Compose 같은 도구로 함께 관리한다.

9 ) Image Build 과정과 주의 사항


Dockerfile Build는 사용자와 대화하면서 명령을 처리하지 않는다. Docker는 Dockerfile의 Instruction을 위에서 아래로 읽고 각 단계를 자동으로 실행하므로 Package 설치와 설정 명령도 비대화식으로 완료되어야 한다.

Package 설치 오류

다음 Dockerfile은 Package Index를 갱신하지 않아 Unable to locate package 오류가 발생할 수 있다.

FROM ubuntu:20.04

RUN apt-get install -y python3

Package Index를 먼저 갱신하더라도 -y가 없으면 설치 확인 질문에 답할 수 없어 Build가 중단될 수 있다. 두 작업은 같은 RUN에서 수행한다.

FROM ubuntu:20.04

RUN apt-get update && \
    apt-get install -y --no-install-recommends python3 && \
    rm -rf /var/lib/apt/lists/*

이 형식에는 다음 이유가 있다.

  • apt-get update로 현재 Package Index를 내려받는다.

  • -y로 설치 확인 질문에 자동으로 동의한다.

  • --no-install-recommends로 필수적이지 않은 권장 Package 설치를 줄인다.

  • 같은 Layer에서 /var/lib/apt/lists를 삭제하여 불필요한 Package Index가 Image에 남지 않게 한다.

원문의 apt install python은 Ubuntu 20.04에서 의도한 Python 3 Package를 정확히 가리키지 않으므로 apt-get install python3로 정리했다. Script와 Dockerfile에서는 사용자용 CLI인 apt보다 안정적인 Interface를 제공하는 apt-get을 사용한다.

Build Context와 .dockerignore

docker build의 마지막 인자는 Build Context이다.

docker build -t myapp:1.0 .

.을 지정하면 현재 Directory가 Build Context가 된다. 불필요한 File이 많으면 Builder로 전송되는 범위가 커지고 COPY . .에 의도하지 않은 File이 포함될 수 있으므로 다음 순서로 준비한다.

  1. Application Build에 필요한 File만 별도 Directory에 모은다.

  2. Dockerfile을 해당 Directory에 작성한다.

  3. Log, 가상 환경, Build 산출물과 Secret File을 .dockerignore에 등록한다.

  4. 해당 Directory를 Build Context로 지정한다.

Dockerfile의 이름이나 위치가 기본값과 다르면 -f로 지정한다.

docker build \
  -f docker/Dockerfile.dev \
  -t myapp:dev \
  .

여기서 docker/Dockerfile.dev는 Dockerfile 경로이고 마지막 .은 Build Context이다.

Build Cache

Docker는 이전 Build 결과를 Cache로 저장하고 Dockerfile의 각 단계에서 재사용할 수 있는지 판단한다. Cache를 효율적으로 사용하려면 변경 빈도가 낮은 File을 먼저 복사하고 자주 변경되는 Source Code는 뒤에서 복사한다.

COPY requirements.txt ./
RUN pip install -r requirements.txt

COPY . .

이 순서에서는 Source Code만 변경된 경우 Package 설치 Layer를 다시 사용할 수 있다.

주요 Cache Option은 다음과 같다.

Option 용도
--no-cache 기존 Build Cache를 사용하지 않고 모든 단계를 다시 실행
--cache-from 지정한 외부 Cache Source에서 Cache 가져오기
--cache-to Build Cache를 지정한 위치로 내보내기
docker build --no-cache -t myapp:clean .
docker build --cache-from myapp:cache -t myapp:latest .

Build Secret

비밀번호, Token, 인증서 같은 민감 정보는 ARG, ENV, COPY로 Image에 넣지 않는다. 값이 Image Metadata, History 또는 Layer에 남을 수 있기 때문이다. Build 중에만 필요한 Secret은 BuildKit의 Secret Mount로 전달한다.

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=github_token \
    GITHUB_TOKEN="$(cat /run/secrets/github_token)" ./download-private-source.sh
docker build \
  --secret id=github_token,src=github-token.txt \
  -t myapp:latest \
  .

Secret을 Mount하면 해당 Build Step에서만 /run/secrets/github_token으로 읽을 수 있으며 최종 Image에 File로 복사되지 않는다.

10 ) Image 용량 절감


Image가 커지면 Registry에 저장하는 공간이 늘고 Network를 통한 Push와 Pull에도 시간이 더 걸린다. Ubuntu 같은 OS Base Image에서 Package를 설치할 때는 실행에 필요하지 않은 Package와 Package Manager의 Cache가 최종 Image에 남지 않도록 관리한다.

Package 설치와 Cache 정리

다음 예제는 Ubuntu에 PHP와 Apache를 설치하고 index.html을 배치한다.

<h1>Hello Docker</h1>

Package 설치와 정리를 여러 RUN으로 분리하면 앞선 Layer에 저장된 File이 그대로 남을 수 있다.

FROM ubuntu:20.04

RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata php apache2
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

WORKDIR /var/www/html
COPY index.html ./

EXPOSE 80

CMD ["apachectl", "-D", "FOREGROUND"]

Image Layer의 크기를 줄이려면 Package 설치와 Cache 삭제를 하나의 RUN에서 처리한다.

FROM ubuntu:20.04

RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      tzdata \
      php \
      apache2 && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

WORKDIR /var/www/html
COPY index.html ./

EXPOSE 80

CMD ["apachectl", "-D", "FOREGROUND"]

관련 명령의 역할은 다음과 같다.

명령 역할 Dockerfile에서의 주의 사항
apt-get clean 내려받은 Package File 정리 Package 설치와 같은 Layer에서 실행
apt-get autoclean 더 이상 내려받을 수 없는 오래된 Package File 정리 Build에 필요한 명령은 아님
apt-get autoremove 자동 설치된 뒤 불필요해진 의존 Package 제거 필요한 Runtime 의존성까지 제거하지 않는지 확인
rm -rf /var/lib/apt/lists/* apt-get update로 받은 Package Index 삭제 이후 Package 설치가 필요하면 다시 apt-get update 실행

Host OS에서는 이미 저장된 File을 삭제하면 사용 가능한 Disk 공간이 늘어난다. 반면 Docker Image는 이전 Layer가 보존되므로 후속 Layer에서 File을 삭제하는 것만으로는 앞선 Layer의 크기가 줄지 않는다. 생성한 Layer 안에서 불필요한 File을 함께 제거해야 효과가 있다.

11 ) Dockerfile 작성 시 유의사항


작은 Image 구성

  • Application 실행에 필요한 Runtime을 포함한 slim, alpine 등의 Base Image를 검토한다.

  • 불필요한 Package를 설치하지 않고 --no-install-recommends 같은 Option을 활용한다.

  • 서로 관련된 Package 설치와 정리 작업은 하나의 RUN에서 수행한다.

  • Build 도구가 실행 단계에 필요하지 않다면 Multi-stage Build로 분리한다.

경량 Base Image는 크기를 줄이는 데 도움이 되지만 Library와 Package 구성이 다를 수 있으므로 Application 호환성을 함께 확인한다.

Build Cache 활용

Docker는 변경되지 않은 Layer를 재사용한다. 변경 빈도가 낮은 의존성 File을 먼저 복사하고 자주 변경되는 Source Code를 나중에 복사한다.

COPY package*.json ./
RUN npm install

COPY . .

Build Context에는 Image 생성에 필요한 File만 포함한다. .dockerignore에는 다음과 같은 대상을 등록할 수 있다.

.git/
node_modules/
*.log
.env

보안과 관리

  • Container Process를 항상 root로 실행하지 않고 USER로 일반 사용자를 지정한다.

  • latest에만 의존하지 않고 용도를 확인할 수 있는 Version Tag를 사용한다.

  • 비밀번호와 Token은 Dockerfile의 ARG, ENV, COPY에 직접 기록하지 않는다.

  • 하나의 Container에는 하나의 주요 관심사를 배치하여 Application 간 결합을 낮춘다.

하나의 관심사가 반드시 하나의 Process만을 의미하는 것은 아니다. 핵심은 Web Server, Database, Message Broker처럼 독립적으로 배포하고 확장해야 하는 구성 요소를 서로 다른 Container로 분리하는 것이다.

Directory 단위의 Build Context

Image Build는 지정한 Directory를 Build Context로 사용한다. 다음 순서로 작업 Directory를 구성하면 불필요한 File이 Image에 포함되는 것을 줄일 수 있다.

  1. Application마다 별도의 Directory를 생성한다.

  2. Source Code, 의존성 File과 Dockerfile을 함께 관리한다.

  3. Build에 필요하지 않은 File을 .dockerignore로 제외한다.

  4. 해당 Directory를 Build Context로 지정한다.

Dockerfile로 만든 Image는 Container 기반 Serverless Platform에도 배포할 수 있다. 다만 Dockerfile 자체가 Serverless 환경을 생성하거나 Server의 배포·확장·고가용성을 자동으로 관리하는 것은 아니다. 이러한 기능은 Image를 실행하는 Cloud Platform이 제공한다.

12 ) Multi-stage Build


Multi-stage Build는 Dockerfile을 여러 Build Stage로 나누는 방식이다. 각 Stage는 FROM으로 시작하며 AS로 이름을 지정할 수 있다.

FROM build-image AS builder

# 의존성 설치와 Application Build

FROM runtime-image

COPY --from=builder /path/to/artifact /app/artifact

# Application 실행

동작 순서는 다음과 같다.

  1. Builder Stage에서 Compiler, Package Manager와 Build 도구를 사용한다.

  2. Source Code를 Build하여 JAR, 실행 Binary 또는 정적 File 같은 산출물을 생성한다.

  3. Runtime Stage를 새로운 FROM으로 시작한다.

  4. COPY --from=builder로 실행에 필요한 산출물만 가져온다.

  5. 최종 Image에는 Builder와 중간 File을 포함하지 않는다.

각 Stage는 독립된 File System을 사용하지만 앞선 Stage의 File을 선택적으로 복사할 수 있다. 최종 Image는 마지막 Stage를 기준으로 생성되므로 Compiler와 Build Cache를 제외하여 Image 크기와 공격 표면을 줄일 수 있다.

Node.js, Spring Boot, Go, Python, React에 Multi-stage Build를 적용하는 구체적인 차이는 다음 문서에서 다룬다.

최종 정리

  • Dockerfile은 Image 생성 과정을 Code로 기록하여 동일한 환경을 반복해서 Build할 수 있게 한다.

  • FROM으로 Base Image를 지정하고 RUN, COPY, ENV 등으로 실행 환경을 구성한다.

  • ENTRYPOINT는 주 실행 명령을, CMD는 기본 명령이나 기본 인자를 정의한다.

  • Dockerfile의 Instruction 순서는 Image Layer와 Build Cache에 영향을 준다.

  • Package 설치와 Cache 삭제를 같은 Layer에서 수행해야 Image 용량을 줄이는 데 효과가 있다.

  • .dockerignore로 불필요한 Build Context를 제외하고 Build Secret은 전용 Secret Mount로 전달한다.

  • Multi-stage Build는 Build 환경과 Runtime 환경을 분리하여 최종 Image에 필요한 산출물만 남긴다.

  • Dockerfile은 Serverless Platform에 배포할 Image를 만들 수 있지만 확장과 운영 자동화는 실행 Platform의 역할이다.

  • MAINTAINER처럼 현재 Deprecated된 Instruction은 그대로 이해하되 새 Dockerfile에서는 대체 방법을 사용한다.

댓글