Docker에 대해서 알아보자 - 2.Dockerfile

2025. 4. 26. 00:02·프로젝트

--cache-from

📌 Dockerfile 개념

Dockerfile은 "docker image를 어떻게 만들 것인가?"에 대한 레시피라고 생각해도 좋습니다.
Dockerfile은 docker image를 만드는 방법이 담겨있고, docker-compose는 "image를 어떻게 실행할 것인가"에 대한 방법이 담겨있습니다. 이번 게시물에서는 Dockerfile의 명령어들과 어떻게 Dockerfile을 작성해야 하는지 알아보겠습니다.
  • ADD : 로컬/원격 파일 추가, 압축 파일 자동 해제
  • ARG : 빌드 시 사용되는 변수 설정
  • CMD : 컨테이너 실행 시 기본 명령
  • COPY : 로컬 파일/디렉터리 복사
  • ENTRYPOINT : 고정 실행 명령어 설정
  • ENV : 환경 변수 설정
  • EXPOSE : 애플리케이션이 사용하는 포트 명시 (문서용)
  • FROM : 베이스 이미지 지정 (Dockerfile 시작 필수)
  • HEALTHCHECK : 컨테이너 상태 확인 명령 설정
  • LABEL : 이미지에 메타데이터 추가
  • MAINTAINER : 이미지 작성자 지정 (구식, LABEL로 대체 권장)
  • ONBUILD : 자식 이미지 빌드 시 실행할 명령
  • RUN : 이미지 빌드 도중 실행할 명령
  • SHELL : 기본 쉘 설정 (RUN 등에 사용될 쉘)
  • STOPSIGNAL : 컨테이너 종료 시 사용할 시스템 콜 지정
  • USER : 명령을 실행할 사용자/그룹 지정
  • VOLUME : 외부 마운트용 디렉터리 생성
  • WORKDIR : 이후 명령 기준 디렉터리 설정 (cd 없이 경로 이동)
Dockerfile은 다음과 같은 설정들을 해줄 수 있으며, 기본적으로 Dockerfile에 쓰여있는 코드를 순서대로 실행합니다.

공식 문서에 작성된 Dockerfile 사용법과 위의 명령어들에 대해서 알아보겠습니다.

 

Parser Directives(지시자) 및 주석

  • Dockerfile은 반드시 FROM 명령어부터 시작해야 합니다(ARG, 주석, 지시자 등은 그전에 올 수 있음)

주석은 말 그대로 #이 달린 주석을 의미합니다.

# 주석의 예시
RUN echo hello
RUN echo world

 

지시자는 Dockerfile의 해석 방식에 영향을 주는 특별한 주석입니다.

  • # directive=value 형식의 특수 주석으로 선택적입니다.
  • 이미지 빌드 레이어를 만들지 않고, 빌드 단계에도 나타나지 않습니다.
  • 지시자는 Dockerfile 맨 위에서만 유효합니다. 주석, 빈 줄, 명령어 중 어떤 것이든 한 줄이라도 지나가면 더 이상 parser directive로 인식되지 않고 일반 주석으로 처리됩니다.
  • 한 지시자당 한 번만 사용이 가능하며 줄 바꿈 없이 첫 줄에 모두 작성해야 합니다.

지원되는 지시자는 3가지 종류가 있습니다.

 

1️⃣ syntax : 어떤 Dockerfile 구문(syntax version)을 사용할지 지정 이 값을 명시하지 않으면, BuildKit은 자체적으로 내장된 Dockerfile 파서를 사용한다.

예시 :
# syntax=docker/dockerfile:1

 

⚠️ BuildKit의 캐시 마운트나 시크릿 같은 고급 기능을 사용하려면 최신 문법을 사용하도록 명시해야 함.

# syntax=docker/dockerfile:1.4

 

2️⃣ escape : 줄 바꿈 시 사용할 이스케이프 문자 설정 (기본은 \)

 

3️⃣ check : 빌드 검사(Build checks)를 어떻게 처리할지 설정하는 데 사용된다.

  • 기본적으로 모든 체크가 실행되고, 실패해도 경고로만 표시된다.

환경 변수 치환

 

Dockerfile에서 ENV 명령으로 선언한 환경 변수는 여러 Dockerfile 명령에서 사용할 수 있다.

  • $VAR_NAME
  • ${VAR_NAME} ← 보통 이걸 더 권장
ENV FOO=/bar
WORKDIR ${FOO}_app  # /bar_app
Dockerfile에서 환경 변수의 값은 명령어 별로 고정되고 다음 명령에서 바뀝니다.
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc
  • 두 번째 줄 : def=$abc일 때 $abc는 이전 값인 hello → def=hello
  • 세 번째 줄 : ghi=$abc일 때 $abc는 바로 위에서 변경된 bye → ghi=bye

⭐. dockerIgnore 사용법

📁 내 프로젝트/
├── .dockerignore     ← 이 파일
├── Dockerfile
├── node_modules/
├── .git/
├── .env
├── src/

 

프로젝트를 진행할 경우 Dockerfile이 있는 디렉터리에 위치시켜서. gitignore처럼 이미지 빌드시 Docker가 무시해야 할 파일이나 디렉토리를 지정하는 데 사용합니다.

# 빌드에 필요 없는 디렉터리
node_modules
.git
build/
tmp/

# 민감 파일
.env
*.pem

# 로그
*.log

 

다음과 같이 gitignore 문법과 비슷하게 민감한 파일, 필요 없는 파일, 그리고 로그 파일 등을 build 과정에서 제외시킬 수 있음.

이렇게 주요한 .env 정보들은 image에 포함되지 않도록 할 수 있습니다. 이후에 꼭 필요한 env라면...

 

1️⃣ 실행 시에 넣어준다.

docker run --env-file .env my-image

 

2️⃣ Docker Compose에서 설정해 준다.

# docker-compose.yml
services:
  app:
    build: .
    env_file:
      - .env

 

ADD

ADD는 파일/디렉토리를 이미지의 파일 시스템에 복사하는 명령입니다.

COPY와 유사하지만, 로컬 빌드 컨텍스트에서만 복사 가능한 COPY와 다르게 ADD는 다음 기능들을 수행할 수 있습니다.

  • URL 다운로드
  • Git 저장소 복제 (clone 가능)
  • 로컬 tar 압축 해제 (원격 tar 파일은 단순 다운로드)
ADD file1.txt file2.txt /usr/src/things/      # 다수 파일 복사
ADD https://example.com/archive.zip /stuff/   # 원격 zip 다운로드
ADD git@github.com:user/repo.git /app/        # Git 저장소 복제

 

참고 옵션 :

  • --keep-git-dir=<boolean>
    : ADD 명령어로 Git 저장소를 이미지에 추가할 때, 기본적으로 .git 디렉토리는 제외됩니다.
    이 옵션을 true로 설정하면 .git 디렉토리도 함께 복사됩니다.
  • --checksum=sha256:<hash>
    : 원격 HTTP(S) 리소스를 복사할 때, 해당 파일의 SHA-256 해시값과 비교하여 무결성 검증을 수행합니다.
    해시값이 다르면 빌드가 실패하며, 캐시 제어에도 유용합니다.
  • --chown=<user>:<group>
    : 복사한 파일이나 디렉토리의 소유자 및 그룹을 설정합니다.
    보통 비루트 사용자 환경에서 권한 설정을 위해 사용합니다.
  • --chmod=<permissions>
    : 복사 대상의 파일 또는 디렉토리 권한을 설정합니다.
    예를 들어, 실행 스크립트는 755, 설정 파일은 644 등으로 설정할 수 있습니다.
  • --link
    : BuildKit 전용 기능으로, 복사 대상 파일이 기존 파일과 동일한 경우 하드링크 방식으로 복사합니다.
    디스크 공간을 절약하고 속도를 향상할 수 있습니다.
  • --exclude=<pattern>
    : BuildKit 실험적 기능으로, 복사 과정에서 지정한 파일/디렉토리를 제외할 수 있습니다.
    .dockerignore와 유사하지만, 명령어 단위로 제외 지정이 가능합니다.

COPY

Dockerfile에서 로컬 파일 또는 디렉토리를 이미지로 복사하는 명령어입니다.

COPY <src>... <dest>
COPY ["<src>", ..., "<dest>"]  # 경로에 공백이 있을 때 필수

 

  • src: 소스 경로 (하나 이상)
  • dest: 목적지 경로 (항상 마지막 인자)

✅ Source 조건

  • 소스가 디렉토리인 경우 내부에 있는 파일들이 복사됩니다.
  • 소스가 파일일 경우 파일과 메타 데이터가 복사됩니다.

✅ 디렉토리 조건

  • /로 시작하는 목적지는 절대 경로로 저장됩니다.
  • 그렇지 않으면 WORKDIR 기준 상대 경로로 저장됩니다.
# 절대 경로
# file.txt → /abs라는 파일로 복사됨
COPY file.txt /abs

# 이렇게 작성하면 /abs/file.txt로 저장됩니다.
COPY file.txt /abs/

# 작업 디렉토리를 /app으로 설정
WORKDIR /app

# 상대 경로
# file.txt → /app/rel 디렉토리 안에 복사됨 → /app/rel/file.txt
COPY file.txt rel/

 

참고 옵션 :

  • --from=<source>
    : COPY 명령어로 복사할 때, 소스를 현재 빌드 컨텍스트가 아닌 다른 빌드 스테이지, 외부 이미지, 또는 명명된 컨텍스트에서 가져올 수 있도록 합니다. 멀티 스테이지 빌드에서 중간 결과를 다음 스테이지로 복사할 때 자주 사용됩니다.
  • --chown=<user>:<group>
    : 복사한 파일이나 디렉토리의 소유자 및 그룹을 설정합니다.
    사용자 이름 또는 UID, 그룹 이름 또는 GID를 사용할 수 있으며, 비루트 사용자 환경에서 권한 오류를 방지하는 데 유용합니다.
  • --chmod=<permissions>
    : 복사 대상 파일 또는 디렉토리의 퍼미션을 8진수로 설정합니다.
    실행 파일은 755, 설정 파일은 644처럼 권한을 명확하게 설정할 수 있습니다.
  • --link
    : BuildKit 전용 기능으로, 복사 대상 파일을 독립적인 레이어로 구성하여 이전 레이어와의 의존성을 줄입니다.
    빌드 캐시 재사용성을 향상하며, 이미지 리베이스 시 성능 최적화에 효과적입니다.
  • --parents[=<boolean>]
    : 복사할 소스 파일의 부모 디렉토리 구조를 목적지에 그대로 유지하여 복사합니다.
    복사 시 디렉토리 구조를 유지하고 싶은 경우 유용하며, BuildKit 실험적 기능입니다.
  • --exclude=<pattern>
    : 지정한 패턴에 해당하는 파일이나 디렉토리를 복사 대상에서 제외합니다.
    .dockerignore와 유사하지만, COPY 명령어 단위로 제외 파일을 제어할 수 있으며 BuildKit 실험적 기능입니다.

ARG

Docker 이미지 빌드 시점에 전달할 수 있는 변수를 정의합니다.

ARG <name>[=<default_value>]
FROM busybox
ARG user1=guest
ARG buildno=1

 

예를 들어서 위와 같이 빌드 시점 즉, Dockerfile 안에서만 사용할 수 있는 user1이라는 변수를 만들어 놓은 채로 기본값은 guest를 넣어준 것입니다.

docker build --build-arg <변수명>=<값>

 

위와 같은 명령어를 사용해서 지정해 놓은 변수에 새로운 값을 넣어서 빌드할 수도 있고 디폴트 값을 사용할 수도 있습니다.

FROM busybox
USER ${username:-some_user}    # username 아직 선언 전 → fallback 사용됨
ARG username
USER $username                 # username 사용 가능 (build-time 값이 있으면 반영됨)
  • ARG는 선언된 다음 줄부터 적용이 되며 이전에 이용할 경우 빈 문자열("")로 읽어짐
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER
  • ARG보다 ENV가 우선됩니다. 위에서 --build-arg CONT_IMG_VER=v2.0.1으로 빌드해도 RUN echo는 v1.0.0 출력 

⚠️ 변수의 역할을 하지만 당연하게도 노출될 수 있기 때문에 비밀번호, API 토큰 같은 민감한 정보를 넣어서는 안 됩니다.
(대신 RUN --mount=type=secret 방식을 사용하라고 합니다)

 

ENV

Dockerfile에서 환경변수(key=value) 를 설정하는 명령어로 빌드 스테이지 이후 모든 명령어에서 사용되며 이미지에도 영구 저장됩니다.

# 예시
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
ENV GREETING="Hello" RUN echo "$GREETING World" # Hello World 출력
  • RUN, CMD, ENTRYPOINT 등에서 $변수명으로 참조할 수 있고
docker run --env GREETING=Hi my-image
  • ARG와 동일하게 run으로 덮어쓸 수 있습니다.

⚠️ ENV에도 역시 민감한 정보를 넣어서는 안 됩니다. ENV는 컨테이너가 실행될 때에도 유지되고 docker inspect로 이미지 메타데이터에서 확인이 가능하기 때문입니다.

ENV SPRING_PROFILES_ACTIVE=prod
ENV APP_PORT=8080
ENV JAVA_OPTS="-Xms512m -Xmx1024m"
  • 따라서, 다음과 같이 민감하지 않은 포트나 프로필 정도를 ENV로 사용할 수 있습니다.

⭐ ENTRYPOINT & CMD

ENTRYPOINT는 컨테이너를 하나의 실행 가능한 프로그램처럼 동작하게 만드는 데 사용되는 Dockerfile 명령어입니다.
즉, 이미지를 실행하면 특정 명령이 자동으로 실행되도록 고정할 수 있습니다.

 

보통 배포에서 Docker를 사용하는 경우, 프로젝트를 image파일로 만들게 되고...
배포 환경에서 image를 container로 실행시킬 경우 자동으로 어플리케이션을 시작하도록 구성을 할 것입니다.

ENTRYPOINT는 그것을 가능하게 만들어 주는 명령어입니다.

ENTRYPOINT ["python3", "main.py"]

 

위의 예시처럼 ENTRYPOINT는 Exec형식으로 작성되도록 권장하고 있습니다. Container에서 image가 돌아가는 즉시 python3 main.py 명령어를 실행해 어플리케이션이 시작되게 됩니다.

 

또한, exec 형식 ENTRYPOINT일 경우, docker run 시 전달한 인자는 ENTRYPOINT 뒤에 붙게 됩니다.

ENTRYPOINT ["python3"]

 

예를 들어서, 다음과 같이 ENTRYPOINT만을 가지고 있는 이미지를 만들고 docker run my-python-image main.py 명령어를 입력해도 동일하게 main.py가 실행되게 됩니다.

 

그런데, 이때 기본 입력값의 역할을 해주는 것이 CMD입니다.

FROM ubuntu
ENTRYPOINT ["python3"]
CMD ["main.py"]

 

ENTRYPOINT는 명령어의 역할을 하고 CMD는 Default 실행인자의 역할을 하게 됩니다. 
위와 같은 경우 동일하게 image를 컨테이너에서 실행해 주게 된다면 python3 main.py가 실행되게 되며,
run에서 다른 실행 인자를 넘겨주게 된다면 CMD가 무시됩니다.

docker run my-image another_script.py
# 실행: python3 another_script.py

 

EXPOSE

EXPOSE는 Dockerfile에서 컨테이너가 런타임에 열어둘 포트를 지정하는 명령이지만, 실제로는 포트를 열거나 외부에 노출하지는 않습니다. "이 컨테이너는 이 포트를 내부적으로 사용한다"라는 문서화 역할입니다.

EXPOSE 80         # TCP 80포트
EXPOSE 80/udp     # UDP 80포트

 

FROM

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

새로운 빌드 단계(build stage) 를 초기화하고 이후 명령어들이 기반으로 삼을 베이스 이미지(base image) 를 설정하는 명령어입니다.

모든 Dockerfile은 반드시 FROM 명령어로 시작해야 합니다 (ARG, 주석, 그리고 지시어 제외)

✅ --platform 옵션

FROM --platform=linux/amd64 node:20

 

다중 플랫폼 이미지를 사용할 경우 어떤 아키텍처로 빌드할지 설정할 수 있습니다. 기본값은 현재 빌드 머신의 플랫폼.
ex) linux/amd64, linux/arm64, windows/amd64

 

✅ 멀티 스테이지 빌딩 관련 기능

FROM node:20 AS builder
# node로 빌드 작업

FROM nginx:alpine
# nginx로 결과 복사

 

Dockerfile 안에서는 여러 개의 FROM을 사용할 수 있고 각각 새로운 빌드 스테이지를 시작합니다.

FROM node:20 AS build-stage
RUN npm run build

FROM nginx
COPY --from=build-stage /app/build /usr/share/nginx/html

또한 AS로 이름을 붙이고 COPY --from으로 참조할 수 있습니다.

HEALTHCHECK
HEALTHCHECK [옵션] CMD <명령어>

 

Docker 컨테이너의 상태를 주기적으로 확인하는 기능입니다 (무한 루프도 감지 가능)

  • --interval=DURATION : 헬스체크 실행 간격 (기본 30s)
  • --timeout=DURATION : 명령어 실행 시간제한. 초과하면 실패로 간주 (기본 30s)
  • --start-period=DURATION : 컨테이너가 처음 기동된 뒤 유예 시간. 이 안에서 실패해도 카운트 안됨 (기본 0s)
  • --start-interval=DURATION : 시작 유예 기간 동안의 헬스체크 주기 (5s)
  • --retries=N : 연속 실패 횟수 기준. 이 이상 실패하면 unhealthy 상태로 판단 (기본 3회)

이걸 설정해 두면 docker ps로 상태 체크가 가능합니다.

 

LABEL

LABEL <key>=<value> [<key>=<value>...]

 

이미지에 키-값 형태의 메타데이터로 개발자들이 남겨놓는 메모와 비슷한 느낌입니다. MAINTAINER가 LABEL로 대체되었습니다.

 

ONBUILD

ONBUILD <INSTRUCTION>

 

현재 이미지에 다음 이미지를 위한 트리거 명령어를 등록만 해둡니다. 이는 다른 이미지가 FROM으로 현재 이미지를 불러올 경우 수행됩니다.

# 공통 베이스 이미지 생성
FROM python:3.10
ONBUILD ADD . /app/src
ONBUILD RUN python /app/src/build.py

FROM my-python-builder
# 여기서 자동으로 ADD, RUN이 실행됨!

 

다음과 같이 공통 베이스 이미지를 만들어 놓고 다른 이미지에서 가져와 사용할 경우 실행되는 코드를 작성해 놓는 명령어입니다.

 

⭐ RUN

RUN <command>

# shell 형식
RUN apt-get update && apt-get install -y curl

# Json 형식
RUN ["apt-get", "install", "-y", "curl"]
  • RUN은 Dockerfile 내에서 명령어를 실행하고 새로운 레이어를 쌓게 됩니다.
  • shell 형식 또는 json 형식으로 입력할 수 있습니다.

✅ RUN의 캐시 로직

FROM ubuntu
COPY . /app
RUN apt-get update
RUN apt-get install -y curl

 

RUN과 COPY는 캐시 기능을 사용합니다. 

따라서, 위와 같은 Dockerfile을 사용하는 상태에서 반복적으로 빌드를 하게 되면 최초 실행 이후부터는 캐시를 이용해서 효율적으로 빌드를 할 수 있습니다.

 

⚠️ 하지만, COPY 하는 소스 파일이 하나라도 바뀌게 되면 그 아래에 있는 RUN들은 모두 재실행되게 됩니다. 이는 멀티 스테이지 빌드를 하는 이유 중 하나이기도 합니다.

 

✅ RUN --mount 

임시 마운트를 연결해서 빌드 중에 접근 가능하게 함

 

--mount=type=bind : 호스트 파일이나 디렉토리를 읽기 전용으로 바인드 마운트:

  • target: 컨테이너 내 경로
  • source: 외부 경로
  • from: 빌드 스테이지나 컨텍스트
  • rw: 읽기/쓰기 허용 (기본은 읽기 전용)

--mount=type=cache : 패키지 매니저, 컴파일 캐시를 저장하는 디렉토리 캐시용

  • target: 캐시를 마운트할 경로
  • id: 캐시 식별자
  • sharing: shared, private, locked → 공유 방법
  • mode/uid/gid: 권한 설정

--mount=type=tmpfs : 임시 메모리 파일시스템을 마운트 (컨테이너 내 휘발성)

  • target : 마운트할 경로
  • size : 메모리 크기

--mount=type=secret : 시크릿 파일(토큰, 키 등)을 마운트함 (보안에 중요)

  • id : 시크릿을 식별하는 이름
  • target : 컨테이너에서 시크릿을 마운트할 경로
  • env : 시크릿 파일 경로를 환경변수로 자동으로 설정
  • mode : 파일 권한
  • uid, gid : 파일 소유자 UID/GID

--mount=type=ssh : SSH 에이전트를 통해 SSH 키 사용

 

  • id : SSH 마운트 식별자. 
  • target : 컨테이너 내부에 SSH 에이전트 소켓을 마운트할 경로.
  • required : SSH 에이전트가 반드시 필요할지 여부 (true/false).
  • mode : 마운트 된 소켓/파일의 권한 비트.
  • uid, gid : 소켓/파일의 소유자 UID/GID.

✅ RUN --network

RUN --network=<TYPE> <command>

 

Dockerfile 빌드 시 개별 RUN 명령에 대해 네트워크 접근 방식을 제어합니다.

# 1) default (기본)
RUN --network=default curl https://example.com

# 2) none (오프라인 빌드)
RUN --network=none apk add --no-cache bash       # 외부 리포지토리 접근 차단

# 3) host (호스트 네트워크)
RUN --network=host apt-get update && apt-get install -y git

 

3가지 타입이 존재합니다.

  • default :  기본 설정이며 인터넷 및 로컬 네트워크 모두 사용가능
  • none : 인터넷 완전 차단, 자기 내부(루프백)만 사용 가능
  • host :  호스트(내 PC)의 네트워크를 그대로 사용

✅ RUN --security

# ※ 현재 stable 문법이 아닌 labs 문법(예: docker/dockerfile:1-labs)에서만 사용 가능
# syntax=docker/dockerfile:1-labs
RUN --security=<MODE> <command>

 

BuildKit 기반 Dockerfile에서 개별 RUN 명령의 보안 격리 수준을 제어합니다. 2가지 모드가 존재합니다.

  • sandbox : 기본 동작 컨테이너 격리 모드로 실행
  • insecure : 격리 해제 --privileged와 유사하게 권한을 모두 허용.

SHELL

SHELL 명령은 Dockerfile에서 기본적으로 사용하는 셸(shell)을 다른 셸로 변경할 수 있게 해 줍니다.

FROM microsoft/windowsservercore

# 기본 cmd로 실행됨 → cmd /S /C echo default
RUN echo default

# 여전히 cmd로 실행됨 → cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# 이제부터 powershell로 실행됨
SHELL ["powershell", "-command"]
RUN Write-Host hello

# 다시 cmd로 전환
SHELL ["cmd", "/S", "/C"]
RUN echo hello

 

STOPSIGNAL

STOPSIGNAL 설정에 따라서 docker stop 명령어에서 어떻게 종료할 것인지 설정합니다.

  • SIG <이름> 형식의 시그널 이름(SIGKILL, SIGTERM 등)으로 지정할 수도 있고, 커널의 시스템 콜 테이블(syscall table)에서 위치에 해당하는 부호 없는 숫자(9 같은 숫자)로 지정할 수도 있습니다.

USER

USER <user>[:<group>]
또는
USER <UID>[:<GID>]

 

Dockerfile 안에서 기본으로 사용할 사용자(user)와 그룹(group)을 설정하는 명령어입니다.

 

VOLUME

VOLUME ["/data"]

VOLUME 명령어는 컨테이너 내부에 마운트 포인트(mount point)를 만들고 외부에서 연결된 볼륨을 담을 수 있도록 여기에 "나중에 외부 저장소가 연결이 될 것이다"를 표시하는 명령어입니다.

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

 

예를 들어, /myvol에 greeting 파일을 만들어두고, /myvol을 VOLUME으로 지정했습니다.

 

이 이미지로 컨테이너를 실행하는 순간, Docker는 호스트에 새로운 익명 볼륨을 자동으로 생성하고,
컨테이너 안의 /myvol은 이 볼륨과 연결됩니다.

 

그리고 /myvol에 원래 있던 greeting 파일도 새로 생성된 볼륨 안에 자동으로 복사됩니다.

 

⚠️ 만약 이미 volume이 존재한다면 연결만 하고 초기 데이터를 복사하지 않습니다.

 

WORKDIR

WORKDIR /path/to/workdir

 

WORKDIR은 이후에 나오는 RUN, CMD, ENTRYPOINT, COPY, ADD 명령어가 실행될 작업 디렉토리(작업 경로)를 설정하는 Dockerfile 명령어입니다 ( cd 명령어와 비슷하다고 생각해도 좋습니다).

 

설정하지 않는다면 기본 디렉토리로 설정됩니다.

📌 변수 주입에 대한 Comment

Dockerfile의 개념을 공부하면서 평소에도 의문을 가졌던 부분입니다. 이 부분에 대해서 공식 문서를 읽으면서 개인적으로 내린 결론을 말씀드리고자 합니다.

빌드 시점에 필요한 변수

빌드 시점이란 image가 만들어지는 시점을 의미합니다. 이때 필요한 변수들이 존재할 수 있습니다.

 

1️⃣ 민감한 정보를 넣어야 합니다

 

민감한 정보의 예시로는,

  • Private repo 액세스 키
  • 레지스트리 로그인
  • 코드 서명 키

등이 있을 수 있습니다. 당연하게도 이 정보들은 민감한 정보이기 때문에 Dockerfile에 작성되어서는 안 됩니다.
따라서, ARG를 사용하지 않고 RUN --mount=type=secret를 사용해서 안전하게 주입해 주면 됩니다.

 

이때 중요한 점은 주입하려는 파일(env, txt 등)을 배포 환경에 넘겨줄 필요는 없다는 점입니다.

image를 만드는 과정에서만 필요하기 때문입니다.

  • Github Action을 사용한다면 git secrets로 관리하다가 build 하는 과정에 넣어주기
  • 로컬에서 가지고 있고 git ignore + docker ignore 상태로 가지고 있다가 build 하는 과정에 넣어준다

다음과 같이 여러 가지 방법이 있겠습니다.

 

2️⃣ 민감하지 않은 정보를 넣어야 합니다

 

민감하지 않은 정보의 예시로는,

  • 앱 버전
  • 컴파일 옵션
  • 환경 구분

등이 존재할 수 있습니다. 이 정보들은 위의 방법처럼 복잡하게 할 필요 없이 Dockerfile에 ARG로 작성해 주는 편이 좋을 것 같습니다.

 

실행 시점에 필요한 변수

말 그대로 container로 실행되는 시점에 필요한 변수들을 의미합니다.

이 경우에는 배포 환경에 어떤 형태로든 이 변수들을 넘겨줄 필요가 있습니다.
왜냐하면 로컬에서 만들어진 이미지는 결국 배포 환경에서 실행되어야 하기 때문입니다.

 

1️⃣ 민감한 정보를 넣어야 합니다

 

application.yml이나 env로 관리되는 DB의 비밀번호와 같은 것들은 실행 시점에 필요한 대표적인 민감한 정보에 속합니다.

이 경우 env파일이나 application.yml을 배포 환경으로 옮겨주고 docker run 시점에 주입해 주면 됩니다.

 

민감한 정보는 이미지에 포함되지 않도록 꼭 docker ignore 해주셔야 합니다.

 

2️⃣ 민감하지 않은 정보를 넣어야 합니다

 

실행 환경에 필요한 민감하지 않은 정보의 예시로는,

  • 실행 프로필
  • 포트 
  • 로깅 옵션

등이 있을 수 있습니다. 컨테이너의 실행 방식과 밀접한 옵션이라면 docker-compose.yml을 작성해서 배포 환경으로 복사할 수도 있고. Dockerfile에 env 파일로 포함시켜도 무방합니다.

📌 Best Practice

✅ Multi-stage 빌드를 사용하자

멀티스테이지 빌드를 사용하면, 이미지 빌드 단계와 실제 배포에 필요한 결과물을 깔끔하게 분리하여 최종 이미지 크기를 줄일 수 있습니다. Dockerfile 지시어를 서로 다른 스테이지로 나누면, 최종 단계에는 애플리케이션 실행에 꼭 필요한 파일만 포함되도록 보장할 수 있습니다.

또한, 여러 스테이지를 병렬로 처리함으로써 빌드를 더욱 효율적으로 수행할 수도 있습니다.

 

# 예시
FROM maven:3.8-jdk AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests

FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=build /app/target/myapp.jar .
CMD ["java", "-jar", "myapp.jar"]
  • 빌드 스테이지: 컴파일, 패키징, 의존성 설치 같은 무거운 작업을 수행
  • 런타임 스테이지: 빌드 결과물(예: JAR, 바이너리)과 최소한의 런타임 라이브러리만 복사
  • 장점
    • 이미지 용량 감소
    • 보안 표면 축소(불필요한 빌드 도구 미포함)
    • BuildKit을 쓰면 독립 스테이지 병렬 실행 가능 → 빌드 속도 개선
결과적으로 모든 이미지는 build 되기 위해서는 임시 컨테이너가 만들어지고 그 내부에서 빌드되게 됩니다!

컴파일, 패키징, 의존성 설치 같은 무거운 작업 수행을 빌드 스테이지로 분리하게 되면, 런타임 스테이지에서는 빌드가 완료된 최종 아티팩트 레이어만 가져오기 때문에 빌드에 필요한 무거운 레이어들을 제외한 채로 마지막 스냅샷만 가져오게 됩니다.

그리고 런타임을 위한 베이스 이미지는 최대한 가벼운 것을 사용하면서 경량화하게 됩니다.
또한 각 스테이지(FROM 으로 분리된) 부분은 독립된 환경에서 병렬적으로 실행하기 때문에 빌드 속도가 개선되고 캐시를 효율적으로 사용할 수 있습니다.

 

공통 스테이지를 분리하고 이전 이미지를 다시 베이스 삼기

# 1) 공통 베이스 스테이지 정의
FROM ubuntu:22.04 AS common-base
RUN apt-get update && apt-get install -y \
    curl \
    ca-certificates \
    git

# 2) 서비스 A 스테이지
FROM common-base AS service-a
WORKDIR /app
COPY service-a/ .
RUN ./build-service-a.sh

# 3) 서비스 B 스테이지
FROM common-base AS service-b
WORKDIR /app
COPY service-b/ .
RUN npm install && npm run build

 

다음과 같이 공통 스테이지를 분리해 주는 것이 좋습니다. 이렇게 할 경우에는 베이스 이미지가 경량화되지는 않겠지만, 병렬적 실행과 캐시 이점은 챙길 수 있습니다.

 

외부 이미지 활용하기

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

 

다음과 같이 Dockerfile 안에서 직접 정의하지 않은 “외부 이미지”를 스테이지로 삼아, 그 안의 파일을 복사해 올 수도 있습니다. 로컬에 없으면 레지스트리에서 자동으로 당겨옵니다.

✅ 이미지를 자주 재빌드 해주자

이미지를 자주 재빌드하여 최신 상태로 유지해 주는 것이 좋습니다.

docker build --no-cache -t my-image:my-tag .

 

이때, --no-cache를 사용하여 캐시를 사용하지 않고 최신 이미지로 업데이트되도록 해야 합니다.

✅  캐시를 적극 활용해 주자

이건 제 프로젝트에 있던 내용을 예시로 들겠습니다. 이번에 docker를 공부하면서 이렇게 리펙토링 해야겠다고 생각하고 있습니다.

name: CI/CD Pipeline

on:
  push:
    branches: [ develop ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: make application-secret.yml
        run: |
          touch ./src/main/resources/application-secret.yml
          echo "${{ secrets.APPLICATION_SECRET }}" > ./src/main/resources/application-secret.yml
        shell: bash

      - name: Build with Gradle Wrapper
        run: |
          ./gradlew --stop && ./gradlew clean --refresh-dependencies
          ./gradlew clean build
          ls -l build/libs

 

현재 github action의 deploy.yml의 일부입니다. github action을 사용하게 되면 매번 새로운 환경에서 실행되기 때문에 이미지의 캐시를 제대로 사용하기 쉽지 않습니다. 따라서, 매번 다시 대용량의 이미지를 만들게 되면서 비효율적인 방식으로 작동하게 됩니다.

따라서,

buildx의--cache-from사용하면 캐시를 더 효율적으로 사용할 수 있습니다.

# 예시
FROM maven:3.8-jdk AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests

FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=build /app/target/myapp.jar .
CMD ["java", "-jar", "myapp.jar"]

 

위에서 사용했던 예시입니다. 만약 다음과 같은 Dockerfile의 캐시를 이용했다면 src 파일만 바뀐다는 상황이라는 가정하에..

  • FROM의 베이스 이미지들을 캐싱하여 활용합니다.
  • pom.xml에서 이루어지는 의존성 설정을 캐싱하여 활용합니다 

⭐ 이때 순서를 꼭 잘 바뀌지 않는 것들을 먼저 작성해 주어야 합니다. 위에서 pom.xml과 src의 COPY 순서가 바뀌게 되면 src가 바뀌어도 dependency를 관리하는 pom.xml도 다시 build 하게 됩니다.

 

✅  RUN에서 새로운 패키지를 설치하는 경우

RUN apt-get update && \
    apt-get install -y --no-install-recommends <패키지 리스트> && \
    rm -rf /var/lib/apt/lists/*

 

apt-get update && apt-get install -y --no-install-recommend 패턴을 사용할 것.

일반적으로 jar파일만 배포하는 상황에서는 사용할 일이 크게 없습니다.

✅  ADD와 COPY

일반적인 파일 복사의 경우 거의 항상 COPY를 사용하는 것이 좋고 원격 다운로드 또는 압축 해제는 ADD를 사용하는 것이 캐시 검증 이점이 있습니다.

  • RUN --mount=type=bind,source=req.txt,target=/tmp/req.txt pip install … 처럼, 한 번만 필요한 파일은 bind mount가 레이어를 늘리지 않아 더 가볍습니다.
  • 최종 이미지에 남겨야 한다면 COPY로 가져와야 합니다.

✅  WORKDIR

# 프로젝트 루트로 작업 디렉터리 설정
WORKDIR /app

# 이후 모든 명령이 /app 기준으로 실행됨
COPY . .
RUN make build

 

WORKDIR에 절대 경로를 사용해, 이후 RUN, CMD, COPY 등이 해당 디렉터리에서 실행되도록 하는 것이 RUN에 cd를 사용하는 것보다 좋습니다.

 

Reference:

도커 공식 문서 🔗

'프로젝트' 카테고리의 다른 글

Clokey 프로젝트 리펙토링 - Github Actions & Docker 최적화  (0) 2025.04.29
Docker에 대해서 알아보자 - 3. docker build & docker compose  (0) 2025.04.26
Docker에 대해서 알아보자 - 1.Docker의 개념과 생태계  (0) 2025.04.18
배포의 모든 것 - 4. 도메인 연결과 HTTPS 적용하기  (0) 2025.04.03
배포의 모든 것 - 3. Github Action을 통한 CI/CD  (0) 2025.03.18
'프로젝트' 카테고리의 다른 글
  • Clokey 프로젝트 리펙토링 - Github Actions & Docker 최적화
  • Docker에 대해서 알아보자 - 3. docker build & docker compose
  • Docker에 대해서 알아보자 - 1.Docker의 개념과 생태계
  • 배포의 모든 것 - 4. 도메인 연결과 HTTPS 적용하기
potato-farm
potato-farm
개발 혼자 공부하기
  • potato-farm
    감자밭
    potato-farm
  • 전체
    오늘
    어제
    • 분류 전체보기 (27)
      • ETC (2)
      • 알고리즘 (0)
      • Java (0)
      • DB (2)
      • Spring (0)
      • 프로젝트 (15)
      • Server (3)
      • CS (0)
        • 운영체제 (0)
      • Infra (4)
        • IAC (1)
        • AWS (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • hELLO· Designed By정상우.v4.10.3
potato-farm
Docker에 대해서 알아보자 - 2.Dockerfile
상단으로

티스토리툴바