Dockerfile 작성하는 방법
Dockerfile은 Docker 이미지를 빌드하기 위한 청사진(Blueprint)이자 레시피임.
컨테이너 환경을 어떻게 설정하고 동작시킬지 정의함.
조직적이고 효율적으로 Dockerfile을 작성하면 이미지를 더 작고 빠르게 만들 수 있으며, 재사용성과 유지 보수성도 높아짐.
Dockerfile의 기본 개념 이해
1. Docker 이미지
Docker 이미지는 레이어(layer)로 구성된 불변(immutable) 데이터임.
각 명령어(Instruction)는 새로운 레이어를 생성함.
이미지가 커지면 빌드와 배포 속도가 느려지므로, Dockerfile 작성 시 레이어를 적절히 최적화하는 것이 중요함.
2. Docker 컨테이너
이미지를 실행(run)하면 컨테이너가 만들어지고, 이는 격리된 프로세스 환경을 제공함.
애플리케이션 배포 시 동일한 이미지를 사용하므로, 운영 환경이 일관성 있게 유지됨.
3. Dockerfile
이미지 생성 과정을 텍스트 파일 형태로 스크립트처럼 기술함.
일반적으로 Dockerfile이라는 파일명을 사용함.
표준 Dockerfile 지침(Instruction)으로는 FROM, RUN, CMD, ENTRYPOINT, COPY, ADD, EXPOSE, ENV, WORKDIR, USER 등이 있음.
Dockerfile 작성 시 사용되는 주요 명령어
1. FROM
베이스 이미지(Base Image)를 지정함.
예시는 다음과 같음.
FROM ubuntu:20.04
모범 사례는 다음과 같음.
가능한 한 경량 이미지를 사용하는 것이 좋음.
예를 들면, alpine, ubuntu, debian, python:3.9-alpine 등.
보안 이슈를 고려하여 최신 버전(가능하면 LTS 버전)을 사용하거나, 빈번한 보안 패치를 통해 관리되는 이미지를 선택함.
2. RUN
컨테이너 내부에서 명령어를 실행하고, 그 결과를 새로운 이미지 레이어로 커밋함.
예시는 다음과 같음.
RUN apt-get update && apt-get install -y python3 python3-pip
모범 사례는 다음과 같음.
여러 RUN 명령을 하나로 묶어 사용하면 레이어 수가 감소하여 이미지 크기가 줄어듬.
예를 들면, RUN apt-get update && apt-get install -y pkg1 pkg2 && rm -rf /var/lib/apt/lists/*
&&를 사용해서 명령어를 연결하고, 마지막에 캐시를 지워서 이미지 용량을 최소화함.
3. COPY / ADD
파일(또는 디렉터리)을 호스트에서 이미지 내부로 복사함.
예시는 다음과 같음.
COPY . /app
차이점은 다음과 같음.
COPY: 단순 파일 복사만 수행한다.
ADD: 파일 복사 + URL에서 파일 다운로드 + TAR 압축 해제 기능이 있음.
보안상 이유로, 보통은 COPY를 권장하고, ADD 기능이 꼭 필요한 경우에만 사용함.
4. WORKDIR
작업 디렉터리를 설정함.
이후 명령어(RUN, CMD 등)는 지정된 폴더 기준으로 실행됨.
예시는 다음과 같음.
WORKDIR /app
모범 사례는 다음과 같음.
WORKDIR를 여러 번 선언해도 되지만, 명시적으로 하나의 작업 디렉터리를 유지하는 것이 구조를 단순화하기 좋음.
5. ENV
환경 변수를 설정함.
예시는 다음과 같음.
ENV PYTHONUNBUFFERED=1
ENV APP_ENV=production
모범 사례는 다음과 같음.
컨테이너에서 필요한 환경 변수들을 ENV로 정의하면, 유지보수와 버전 관리를 용이하게 할 수 있음.
중요하거나 민감한 값은 Dockerfile에 직접 넣지 않고, 빌드 시점 또는 런타임 시점에 --build-arg나 환경 변수로 전달하는 방법을 고려해야 함.
6. CMD
컨테이너가 실행되었을 때 기본적으로 실행될 명령을 지정함.
형태는 주로 두 가지 방식 사용함.
쉘 형식: CMD echo "Hello World"
Exec 형식: CMD ["executable", "param1", "param2"]
예시는 다음과 같음
CMD ["python3", "main.py"]
모범 사례는 다음과 같음.
하나의 Dockerfile에는 CMD를 단 하나만 명시하는 것이 좋음.
여러 개를 명시하면 마지막 것만 유효함.
기본 진입점을 제공하면서도 필요 시 docker run ... <command>로 다른 명령어를 실행할 수 있게 해줌.
7. ENTRYPOINT
컨테이너가 실행되었을 때 항상 실행되어야 하는 진입점을 설정함.
예시는 다음과 같음.
ENTRYPOINT ["python3", "main.py"]
CMD와의 관계는 다음과 같음.
ENTRYPOINT는 이미지 자체의 고정된 진입점을 지정할 때 사용함.
CMD는 ENTRYPOINT에 전달되는 기본 인자(매개변수)의 역할을 하도록 설정할 수 있음.
일반적으로 ENTRYPOINT ["executable"]와 CMD ["arg1", "arg2"] 조합으로 사용하면, docker run 시 추가 인자를 넣을 수 있음.
8. EXPOSE
컨테이너가 사용하는 포트를 명시적으로 드러냄.
실제로 네트워크 오픈을 강제하지는 않음.
예시는 다음과 같음.
EXPOSE 8080
모범 사례는 다음과 같음.
문서화 목적이 강함.
컨테이너 실행 시 -p 8080:8080과 같은 옵션으로 실제 포트를 개방해야 함.
9. USER
컨테이너에서 애플리케이션을 실행할 사용자 계정을 설정함.
예시는 다음과 같음.
USER nobody
모범 사례는 다음과 같음.
보안 관점에서 루트 권한(root)으로 컨테이너를 실행하는 것은 위험함.
운영 환경에서는 별도의 비루트 사용자 계정을 생성하여 컨테이너가 해당 권한으로 동작하게 하는 것이 좋음.
10. ONBUILD
자식 이미지를 빌드할 때 트리거되는 명령을 설정함.
예시는 다음과 같음.
ONBUILD RUN apt-get update && apt-get install -y some-package
모범 사례는 다음과 같음.
보통 팀 내에서 베이스 이미지를 제작하여 다른 Dockerfile에서 상속받아 사용할 때 유용하지만, 예측하기 어려운 동작을 초래할 수 있으므로 주의가 필요함.
Dockerfile 작성 구조 예시
아래는 파이썬 Flask 애플리케이션을 컨테이너화한다고 가정했을 때의 Dockerfile 예시임.
# 1. 베이스 이미지 설정
FROM python:3.9-slim
# 2. 환경 변수를 설정
ENV PYTHONUNBUFFERED=1 \
APP_ENV=production
# 3. 작업 디렉터리 설정
WORKDIR /app
# 4. 의존성 파일 복사
COPY requirements.txt .
# 5. 의존성 설치
RUN pip install --no-cache-dir -r requirements.txt
# 6. 애플리케이션 소스 복사
COPY . /app
# 7. 포트 노출 (문서화 목적)
EXPOSE 5000
# 8. (선택) 비루트 사용자 생성 후 전환
# RUN useradd -m myuser
# USER myuser
# 9. 컨테이너 실행 명령
CMD ["python", "app.py"]
멀티 스테이지 빌드 : 개념
복잡한 애플리케이션을 여러 단계(Stage)에 걸쳐 빌드하고, 최종적으로 필요한 결과물만 가져와서 이미지를 최소화하는 기법임.
예를 들어, 빌드 도구나 테스트를 위한 의존성은 빌드 단계에서만 사용하고, 최종 스테이지에는 빌드 결과물만 복사해서 경량 이미지를 만듬.
멀티 스테이지 빌드 : 예시
Go 애플리케이션 빌드 예시임.
# 1) 빌드 단계
FROM golang:1.18-alpine AS build
WORKDIR /app
COPY . .
RUN go build -o myapp
# 2) 최종 실행 단계
FROM alpine:3.16
WORKDIR /app
COPY --from=build /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
첫 번째 스테이지(build)에서 Go 소스를 빌드하는 데 필요한 도구가 모두 들어있는 golang:1.18-alpine 이미지를 사용함.
빌드 완료 후, 두 번째 스테이지에서 alpine:3.16 이미지를 사용해 실행 환경을 최소화함.
COPY --from=build /app/myapp .를 통해 빌드 단계에서 생성된 실행 파일만 가져옴.
모범사례 요약
1. 레어링 최소화
여러 명령을 가능한 한 RUN 명령에 묶어서 작성하되, 가독성도 고려해야 함.
불필요한 파일이나 빌드 도구, 캐시 등을 빌드 후 제거해 이미지 크기를 줄임.
2. 적절한 베이스 이미지 선택
Alpine, Slim 등 경량 이미지를 사용하면 배포 속도와 운영 비용을 크게 절약할 수 있음.
다만 Alpine 기반에서 일부 패키지가 정상적으로 동작하지 않는 사례가 있으므로 주의가 필요함.
3. 캐싱 활용
COPY와 RUN 순서를 전략적으로 배치하여 변경이 적은 부분(예: 의존성 설치)은 캐시가 활성화되도록 함.
의존성 파일(requirements.txt, package.json 등)을 먼저 복사 후 설치하고, 그다음에 소스 코드를 복사하는 방식으로 캐싱 효과를 극대화할 수 있음.
4. 보안 고려
이미지 내에 불필요한 도구와 라이브러리는 제거함.
민감한 정보(비밀번호, 인증서 등)는 직접 Dockerfile에 넣지 않음.
가능하면 루트가 아닌 사용자로 실행함.
USER 지침 사용해야 함.
5. 빌드 인자(ARG) 활용
빌드 시점에 특정 값을 전달해야 할 경우 ARG를 사용함.
예를 들면, ARG BUILD_VERSION=1.0.0
이 값은 Dockerfile 내부에서만 유효하며, ENV는 런타임에도 유효함.
6. 로그 및 디버깅
빌드 단계별로 로그를 확인하며, 캐시가 예상대로 작동하는지, 설치가 제대로 되는지 등을 꼼꼼히 체크함.
도커 빌드 시 docker build --progress=plain 옵션을 사용하면 전체 로그를 자세히 볼 수 있음.
7. 멀티 스테이지 빌드 적극 활용
프로덕션 이미지에서 빌드 도구와 테스트 의존성 등을 제거하면 보안과 성능 측면에서 유리함.
정리
전문가 수준의 Dockerfile 작성은 단순히 명령어를 나열하는 것이 아니라, 이미지 크기 최적화, 보안, 명확한 구조, 캐시 활용 등의 다양한 측면을 고려하는 작업임.
이를 통해 빌드와 배포 과정을 자동화하고, 다양한 환경에서 일관된 컨테이너 실행을 보장할 수 있음.
1. 베이스 이미지를 신중하게 선택하고,
2. 불필요한 레이어를 줄이며,
3. 보안과 유지보수를 고려하고,
4. 멀티 스테이지 빌드로 빌드/런타임 환경을 분리하며,
5. 로그와 캐시를 꼼꼼히 관리하는 습관을 들인다면, 보다 효율적이고 안정적인 Docker 이미지를 빌드할 수 있음.
'Operating System > Docker' 카테고리의 다른 글
[Docker] 컨테이너 실시간 모니터링 방법 (2) | 2025.01.24 |
---|---|
[Docker] 도커 컨테이너 자원과 로컬의 자원 (0) | 2025.01.24 |
[Docker] 도커와 도커의 네트워크 (0) | 2024.06.08 |
[Docker] 컨테이너가 실행중인지 확인하고 종료 및 삭제하는 쉘스크립트 (0) | 2023.05.31 |
[Docker] bash: add-apt-repository: command not found 에러 해결 방법 (0) | 2022.10.07 |
Dockerfile 작성하는 방법
Dockerfile은 Docker 이미지를 빌드하기 위한 청사진(Blueprint)이자 레시피임.
컨테이너 환경을 어떻게 설정하고 동작시킬지 정의함.
조직적이고 효율적으로 Dockerfile을 작성하면 이미지를 더 작고 빠르게 만들 수 있으며, 재사용성과 유지 보수성도 높아짐.
Dockerfile의 기본 개념 이해
1. Docker 이미지
Docker 이미지는 레이어(layer)로 구성된 불변(immutable) 데이터임.
각 명령어(Instruction)는 새로운 레이어를 생성함.
이미지가 커지면 빌드와 배포 속도가 느려지므로, Dockerfile 작성 시 레이어를 적절히 최적화하는 것이 중요함.
2. Docker 컨테이너
이미지를 실행(run)하면 컨테이너가 만들어지고, 이는 격리된 프로세스 환경을 제공함.
애플리케이션 배포 시 동일한 이미지를 사용하므로, 운영 환경이 일관성 있게 유지됨.
3. Dockerfile
이미지 생성 과정을 텍스트 파일 형태로 스크립트처럼 기술함.
일반적으로 Dockerfile이라는 파일명을 사용함.
표준 Dockerfile 지침(Instruction)으로는 FROM, RUN, CMD, ENTRYPOINT, COPY, ADD, EXPOSE, ENV, WORKDIR, USER 등이 있음.
Dockerfile 작성 시 사용되는 주요 명령어
1. FROM
베이스 이미지(Base Image)를 지정함.
예시는 다음과 같음.
FROM ubuntu:20.04
모범 사례는 다음과 같음.
가능한 한 경량 이미지를 사용하는 것이 좋음.
예를 들면, alpine, ubuntu, debian, python:3.9-alpine 등.
보안 이슈를 고려하여 최신 버전(가능하면 LTS 버전)을 사용하거나, 빈번한 보안 패치를 통해 관리되는 이미지를 선택함.
2. RUN
컨테이너 내부에서 명령어를 실행하고, 그 결과를 새로운 이미지 레이어로 커밋함.
예시는 다음과 같음.
RUN apt-get update && apt-get install -y python3 python3-pip
모범 사례는 다음과 같음.
여러 RUN 명령을 하나로 묶어 사용하면 레이어 수가 감소하여 이미지 크기가 줄어듬.
예를 들면, RUN apt-get update && apt-get install -y pkg1 pkg2 && rm -rf /var/lib/apt/lists/*
&&를 사용해서 명령어를 연결하고, 마지막에 캐시를 지워서 이미지 용량을 최소화함.
3. COPY / ADD
파일(또는 디렉터리)을 호스트에서 이미지 내부로 복사함.
예시는 다음과 같음.
COPY . /app
차이점은 다음과 같음.
COPY: 단순 파일 복사만 수행한다.
ADD: 파일 복사 + URL에서 파일 다운로드 + TAR 압축 해제 기능이 있음.
보안상 이유로, 보통은 COPY를 권장하고, ADD 기능이 꼭 필요한 경우에만 사용함.
4. WORKDIR
작업 디렉터리를 설정함.
이후 명령어(RUN, CMD 등)는 지정된 폴더 기준으로 실행됨.
예시는 다음과 같음.
WORKDIR /app
모범 사례는 다음과 같음.
WORKDIR를 여러 번 선언해도 되지만, 명시적으로 하나의 작업 디렉터리를 유지하는 것이 구조를 단순화하기 좋음.
5. ENV
환경 변수를 설정함.
예시는 다음과 같음.
ENV PYTHONUNBUFFERED=1
ENV APP_ENV=production
모범 사례는 다음과 같음.
컨테이너에서 필요한 환경 변수들을 ENV로 정의하면, 유지보수와 버전 관리를 용이하게 할 수 있음.
중요하거나 민감한 값은 Dockerfile에 직접 넣지 않고, 빌드 시점 또는 런타임 시점에 --build-arg나 환경 변수로 전달하는 방법을 고려해야 함.
6. CMD
컨테이너가 실행되었을 때 기본적으로 실행될 명령을 지정함.
형태는 주로 두 가지 방식 사용함.
쉘 형식: CMD echo "Hello World"
Exec 형식: CMD ["executable", "param1", "param2"]
예시는 다음과 같음
CMD ["python3", "main.py"]
모범 사례는 다음과 같음.
하나의 Dockerfile에는 CMD를 단 하나만 명시하는 것이 좋음.
여러 개를 명시하면 마지막 것만 유효함.
기본 진입점을 제공하면서도 필요 시 docker run ... <command>로 다른 명령어를 실행할 수 있게 해줌.
7. ENTRYPOINT
컨테이너가 실행되었을 때 항상 실행되어야 하는 진입점을 설정함.
예시는 다음과 같음.
ENTRYPOINT ["python3", "main.py"]
CMD와의 관계는 다음과 같음.
ENTRYPOINT는 이미지 자체의 고정된 진입점을 지정할 때 사용함.
CMD는 ENTRYPOINT에 전달되는 기본 인자(매개변수)의 역할을 하도록 설정할 수 있음.
일반적으로 ENTRYPOINT ["executable"]와 CMD ["arg1", "arg2"] 조합으로 사용하면, docker run 시 추가 인자를 넣을 수 있음.
8. EXPOSE
컨테이너가 사용하는 포트를 명시적으로 드러냄.
실제로 네트워크 오픈을 강제하지는 않음.
예시는 다음과 같음.
EXPOSE 8080
모범 사례는 다음과 같음.
문서화 목적이 강함.
컨테이너 실행 시 -p 8080:8080과 같은 옵션으로 실제 포트를 개방해야 함.
9. USER
컨테이너에서 애플리케이션을 실행할 사용자 계정을 설정함.
예시는 다음과 같음.
USER nobody
모범 사례는 다음과 같음.
보안 관점에서 루트 권한(root)으로 컨테이너를 실행하는 것은 위험함.
운영 환경에서는 별도의 비루트 사용자 계정을 생성하여 컨테이너가 해당 권한으로 동작하게 하는 것이 좋음.
10. ONBUILD
자식 이미지를 빌드할 때 트리거되는 명령을 설정함.
예시는 다음과 같음.
ONBUILD RUN apt-get update && apt-get install -y some-package
모범 사례는 다음과 같음.
보통 팀 내에서 베이스 이미지를 제작하여 다른 Dockerfile에서 상속받아 사용할 때 유용하지만, 예측하기 어려운 동작을 초래할 수 있으므로 주의가 필요함.
Dockerfile 작성 구조 예시
아래는 파이썬 Flask 애플리케이션을 컨테이너화한다고 가정했을 때의 Dockerfile 예시임.
# 1. 베이스 이미지 설정
FROM python:3.9-slim
# 2. 환경 변수를 설정
ENV PYTHONUNBUFFERED=1 \
APP_ENV=production
# 3. 작업 디렉터리 설정
WORKDIR /app
# 4. 의존성 파일 복사
COPY requirements.txt .
# 5. 의존성 설치
RUN pip install --no-cache-dir -r requirements.txt
# 6. 애플리케이션 소스 복사
COPY . /app
# 7. 포트 노출 (문서화 목적)
EXPOSE 5000
# 8. (선택) 비루트 사용자 생성 후 전환
# RUN useradd -m myuser
# USER myuser
# 9. 컨테이너 실행 명령
CMD ["python", "app.py"]
멀티 스테이지 빌드 : 개념
복잡한 애플리케이션을 여러 단계(Stage)에 걸쳐 빌드하고, 최종적으로 필요한 결과물만 가져와서 이미지를 최소화하는 기법임.
예를 들어, 빌드 도구나 테스트를 위한 의존성은 빌드 단계에서만 사용하고, 최종 스테이지에는 빌드 결과물만 복사해서 경량 이미지를 만듬.
멀티 스테이지 빌드 : 예시
Go 애플리케이션 빌드 예시임.
# 1) 빌드 단계
FROM golang:1.18-alpine AS build
WORKDIR /app
COPY . .
RUN go build -o myapp
# 2) 최종 실행 단계
FROM alpine:3.16
WORKDIR /app
COPY --from=build /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
첫 번째 스테이지(build)에서 Go 소스를 빌드하는 데 필요한 도구가 모두 들어있는 golang:1.18-alpine 이미지를 사용함.
빌드 완료 후, 두 번째 스테이지에서 alpine:3.16 이미지를 사용해 실행 환경을 최소화함.
COPY --from=build /app/myapp .를 통해 빌드 단계에서 생성된 실행 파일만 가져옴.
모범사례 요약
1. 레어링 최소화
여러 명령을 가능한 한 RUN 명령에 묶어서 작성하되, 가독성도 고려해야 함.
불필요한 파일이나 빌드 도구, 캐시 등을 빌드 후 제거해 이미지 크기를 줄임.
2. 적절한 베이스 이미지 선택
Alpine, Slim 등 경량 이미지를 사용하면 배포 속도와 운영 비용을 크게 절약할 수 있음.
다만 Alpine 기반에서 일부 패키지가 정상적으로 동작하지 않는 사례가 있으므로 주의가 필요함.
3. 캐싱 활용
COPY와 RUN 순서를 전략적으로 배치하여 변경이 적은 부분(예: 의존성 설치)은 캐시가 활성화되도록 함.
의존성 파일(requirements.txt, package.json 등)을 먼저 복사 후 설치하고, 그다음에 소스 코드를 복사하는 방식으로 캐싱 효과를 극대화할 수 있음.
4. 보안 고려
이미지 내에 불필요한 도구와 라이브러리는 제거함.
민감한 정보(비밀번호, 인증서 등)는 직접 Dockerfile에 넣지 않음.
가능하면 루트가 아닌 사용자로 실행함.
USER 지침 사용해야 함.
5. 빌드 인자(ARG) 활용
빌드 시점에 특정 값을 전달해야 할 경우 ARG를 사용함.
예를 들면, ARG BUILD_VERSION=1.0.0
이 값은 Dockerfile 내부에서만 유효하며, ENV는 런타임에도 유효함.
6. 로그 및 디버깅
빌드 단계별로 로그를 확인하며, 캐시가 예상대로 작동하는지, 설치가 제대로 되는지 등을 꼼꼼히 체크함.
도커 빌드 시 docker build --progress=plain 옵션을 사용하면 전체 로그를 자세히 볼 수 있음.
7. 멀티 스테이지 빌드 적극 활용
프로덕션 이미지에서 빌드 도구와 테스트 의존성 등을 제거하면 보안과 성능 측면에서 유리함.
정리
전문가 수준의 Dockerfile 작성은 단순히 명령어를 나열하는 것이 아니라, 이미지 크기 최적화, 보안, 명확한 구조, 캐시 활용 등의 다양한 측면을 고려하는 작업임.
이를 통해 빌드와 배포 과정을 자동화하고, 다양한 환경에서 일관된 컨테이너 실행을 보장할 수 있음.
1. 베이스 이미지를 신중하게 선택하고,
2. 불필요한 레이어를 줄이며,
3. 보안과 유지보수를 고려하고,
4. 멀티 스테이지 빌드로 빌드/런타임 환경을 분리하며,
5. 로그와 캐시를 꼼꼼히 관리하는 습관을 들인다면, 보다 효율적이고 안정적인 Docker 이미지를 빌드할 수 있음.
'Operating System > Docker' 카테고리의 다른 글
[Docker] 컨테이너 실시간 모니터링 방법 (2) | 2025.01.24 |
---|---|
[Docker] 도커 컨테이너 자원과 로컬의 자원 (0) | 2025.01.24 |
[Docker] 도커와 도커의 네트워크 (0) | 2024.06.08 |
[Docker] 컨테이너가 실행중인지 확인하고 종료 및 삭제하는 쉘스크립트 (0) | 2023.05.31 |
[Docker] bash: add-apt-repository: command not found 에러 해결 방법 (0) | 2022.10.07 |