⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Dockerfile 작성 기초 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 11. 26. · 6 Views

Dockerfile 작성 기초 완벽 가이드

Docker 컨테이너를 만들기 위한 Dockerfile 작성법을 처음부터 차근차근 배워봅니다. 베이스 이미지 선택부터 CMD와 ENTRYPOINT의 차이까지, 실무에서 바로 활용할 수 있는 핵심 내용을 담았습니다.


목차

  1. Dockerfile 기본 구조
  2. FROM으로 베이스 이미지 선택
  3. RUN, COPY, ADD 명령어
  4. WORKDIR과 ENV 설정
  5. EXPOSE와 포트 설정
  6. CMD vs ENTRYPOINT 차이

1. Dockerfile 기본 구조

김개발 씨는 입사 첫 주에 선배로부터 이런 요청을 받았습니다. "이 프로젝트 Docker로 배포할 수 있게 Dockerfile 좀 만들어줘." Dockerfile이라는 단어는 들어봤지만, 막상 만들려니 어디서부터 시작해야 할지 막막했습니다.

Dockerfile은 Docker 이미지를 만들기 위한 설계도입니다. 마치 요리 레시피처럼, 어떤 재료(베이스 이미지)를 쓰고 어떤 순서로 조리(명령어 실행)할지 적어놓은 문서입니다.

이 파일 하나만 있으면 어떤 컴퓨터에서든 동일한 환경을 재현할 수 있습니다.

다음 코드를 살펴봅시다.

# Dockerfile의 기본 구조 예시
# 1. 베이스 이미지 지정 (필수)
FROM python:3.11-slim

# 2. 작업 디렉토리 설정
WORKDIR /app

# 3. 파일 복사
COPY requirements.txt .

# 4. 명령어 실행
RUN pip install -r requirements.txt

# 5. 애플리케이션 코드 복사
COPY . .

# 6. 포트 노출
EXPOSE 8000

# 7. 실행 명령어
CMD ["python", "app.py"]

김개발 씨는 회사에서 처음으로 Docker를 접하게 되었습니다. 지금까지는 로컬 컴퓨터에서 개발하고, 서버에 직접 접속해서 배포하는 방식으로 일해왔습니다.

그런데 새로 합류한 팀에서는 모든 서비스를 Docker 컨테이너로 관리한다고 합니다. 선배 개발자 박시니어 씨가 김개발 씨 옆에 앉아 차근차근 설명을 시작했습니다.

"Dockerfile이 뭔지 알아? 쉽게 말하면 컴퓨터 환경을 코드로 정의하는 거야." 그렇다면 Dockerfile이란 정확히 무엇일까요?

쉽게 비유하자면, Dockerfile은 마치 이케아 가구 조립 설명서와 같습니다. 설명서에는 어떤 부품이 필요하고, 어떤 순서로 조립해야 하는지 단계별로 적혀있습니다.

Dockerfile도 마찬가지입니다. 어떤 운영체제를 사용하고, 어떤 프로그램을 설치하고, 어떤 파일을 복사할지 순서대로 적어놓은 것입니다.

Dockerfile이 없던 시절에는 어땠을까요? 개발자들은 새 서버를 세팅할 때마다 수동으로 모든 환경을 구성해야 했습니다.

Python 버전 설치하고, 라이브러리 설치하고, 환경 변수 설정하고... 이 과정에서 실수가 발생하기 쉬웠습니다.

더 큰 문제는 "내 컴퓨터에서는 되는데요?"라는 악명 높은 상황이었습니다. 개발 환경과 운영 환경이 달라서 생기는 버그는 원인을 찾기도 어려웠습니다.

바로 이런 문제를 해결하기 위해 Dockerfile이 등장했습니다. Dockerfile을 사용하면 환경의 일관성이 보장됩니다.

개발자의 노트북, 테스트 서버, 운영 서버 모두 동일한 환경에서 애플리케이션이 실행됩니다. 또한 버전 관리도 가능해집니다.

Dockerfile을 Git에 저장하면 환경 변경 이력을 추적할 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

FROM python:3.11-slim은 베이스 이미지를 지정합니다. 이것은 우리 컨테이너의 기반이 되는 운영체제와 기본 프로그램을 정의합니다.

WORKDIR /app은 이후 명령어들이 실행될 작업 디렉토리를 설정합니다. COPYRUN은 각각 파일을 복사하고 명령어를 실행합니다.

마지막으로 CMD는 컨테이너가 시작될 때 실행할 기본 명령어를 지정합니다. 실제 현업에서는 이 구조를 기본으로 삼아 다양하게 확장합니다.

예를 들어 웹 서비스를 개발한다면, 먼저 의존성 파일만 복사해서 설치하고 그 다음에 소스 코드를 복사합니다. 이렇게 하면 소스 코드만 변경되었을 때 의존성 설치 단계를 캐시로 재사용할 수 있어 빌드 시간이 크게 단축됩니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 Dockerfile에 불필요한 파일까지 모두 복사하는 것입니다.

node_modules나 가상환경 폴더까지 복사하면 이미지 크기가 불필요하게 커집니다. 이를 방지하려면 .dockerignore 파일을 작성해야 합니다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "생각보다 어렵지 않네요.

그냥 설치 과정을 코드로 적어놓는 거잖아요!" 맞습니다. Dockerfile의 기본 구조를 이해하면 나머지는 필요한 명령어를 하나씩 배워나가면 됩니다.

여러분도 간단한 프로젝트부터 Dockerfile을 작성해보세요.

실전 팁

💡 - Dockerfile은 항상 프로젝트 루트 디렉토리에 'Dockerfile'이라는 이름으로 저장합니다

  • .dockerignore 파일을 함께 작성하여 불필요한 파일이 이미지에 포함되지 않도록 합니다
  • 명령어 순서를 잘 배치하면 캐시를 활용하여 빌드 시간을 단축할 수 있습니다

2. FROM으로 베이스 이미지 선택

김개발 씨가 처음 Dockerfile을 작성하려고 에디터를 열었습니다. 첫 줄에 무엇을 써야 할지 검색해보니 FROM이라는 명령어가 나옵니다.

그런데 'python:3.11'도 있고, 'python:3.11-slim'도 있고, 'python:3.11-alpine'도 있습니다. 대체 어떤 것을 선택해야 할까요?

FROM은 Dockerfile의 첫 번째 명령어로, 컨테이너의 기반이 될 이미지를 지정합니다. 마치 집을 지을 때 어떤 땅 위에 지을지 선택하는 것과 같습니다.

베이스 이미지 선택에 따라 컨테이너의 크기, 보안, 호환성이 크게 달라지므로 신중하게 선택해야 합니다.

다음 코드를 살펴봅시다.

# 다양한 베이스 이미지 선택 예시

# 1. 공식 이미지 - 가장 안전한 선택
FROM python:3.11

# 2. slim 버전 - 경량화된 이미지 (권장)
FROM python:3.11-slim

# 3. alpine 버전 - 초경량 이미지 (주의 필요)
FROM python:3.11-alpine

# 4. 특정 운영체제 기반
FROM ubuntu:22.04

# 5. 버전 태그 고정 (프로덕션 권장)
FROM python:3.11.6-slim-bookworm

# 6. 멀티 스테이지 빌드의 시작
FROM python:3.11-slim AS builder

김개발 씨는 Docker Hub에 접속해서 python 이미지를 검색해봤습니다. 그런데 태그가 수십 개나 됩니다.

python:3.11, python:3.11-slim, python:3.11-alpine, python:3.11-bullseye... 어떤 것을 선택해야 할지 혼란스럽습니다.

박시니어 씨가 다가와 화면을 보더니 이렇게 설명했습니다. "베이스 이미지 선택은 정말 중요해.

잘못 선택하면 나중에 고생해." 그렇다면 FROM 명령어와 베이스 이미지는 정확히 무엇일까요? 쉽게 비유하자면, 베이스 이미지는 프라모델의 기본 키트와 같습니다.

어떤 키트를 선택하느냐에 따라 조립 난이도도 다르고, 완성품의 품질도 달라집니다. 풀 키트를 사면 편하지만 비싸고, 기본 키트는 저렴하지만 추가 작업이 필요합니다.

Docker 이미지도 마찬가지입니다. 가장 기본적인 python:3.11 이미지는 Debian 운영체제 위에 Python과 다양한 시스템 라이브러리가 설치되어 있습니다.

용량이 약 900MB 정도로 크지만, 대부분의 Python 패키지가 문제없이 설치됩니다. 처음 시작할 때 가장 안전한 선택입니다.

python:3.11-slim 이미지는 불필요한 패키지를 제거한 경량 버전입니다. 용량이 약 150MB로 훨씬 가볍습니다.

대부분의 웹 애플리케이션에서 이 버전을 사용해도 충분합니다. 실무에서 가장 많이 권장되는 선택지입니다.

python:3.11-alpine 이미지는 Alpine Linux를 기반으로 하며 용량이 약 50MB에 불과합니다. 하지만 Alpine은 표준 C 라이브러리로 musl을 사용하기 때문에, glibc를 필요로 하는 일부 Python 패키지가 설치되지 않거나 빌드에 오랜 시간이 걸릴 수 있습니다.

그렇다면 어떤 기준으로 선택해야 할까요? 프로덕션 환경에서는 버전 태그를 명확히 고정하는 것이 중요합니다.

'python:3.11'처럼 태그를 지정하면 시간이 지나면서 이미지가 업데이트될 수 있습니다. 'python:3.11.6-slim-bookworm'처럼 구체적으로 지정하면 항상 동일한 이미지를 사용할 수 있습니다.

실제 현업에서는 대부분 slim 버전을 기본으로 사용합니다. 예를 들어 Django 웹 서비스를 배포한다면, python:3.11-slim으로 시작하는 것이 좋습니다.

만약 특정 패키지 설치에 문제가 생기면 그때 기본 이미지로 변경하면 됩니다. 처음부터 큰 이미지를 사용할 필요는 없습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 'latest' 태그를 사용하는 것입니다.

FROM python:latest라고 쓰면 편해 보이지만, 어느 날 갑자기 Python 버전이 올라가면서 애플리케이션이 깨질 수 있습니다. 항상 명시적인 버전 태그를 사용하세요.

김개발 씨는 고개를 끄덕이며 메모했습니다. "일단 slim으로 시작하고, 문제 생기면 기본 이미지로 바꾸면 되는 거죠?" 맞습니다.

베이스 이미지 선택에 정답은 없습니다. 상황에 맞게 적절한 이미지를 선택하고, 필요하면 변경하면 됩니다.

중요한 것은 왜 그 이미지를 선택했는지 이해하는 것입니다.

실전 팁

💡 - 처음에는 slim 버전으로 시작하고, 문제가 생기면 기본 이미지로 변경하세요

  • 프로덕션에서는 latest 대신 구체적인 버전 태그를 사용하세요
  • Docker Hub에서 Official Image 배지가 있는 공식 이미지를 사용하세요

3. RUN, COPY, ADD 명령어

김개발 씨가 베이스 이미지를 선택하고 나니, 이제 필요한 프로그램을 설치하고 코드를 복사해야 합니다. 그런데 RUN, COPY, ADD라는 비슷해 보이는 명령어들이 있습니다.

언제 무엇을 써야 할까요?

RUN은 이미지 빌드 시점에 명령어를 실행하고, COPY는 로컬 파일을 컨테이너에 복사하며, ADD는 COPY와 비슷하지만 URL 다운로드와 압축 해제 기능이 추가된 명령어입니다. 대부분의 경우 COPY를 사용하고, 특별한 이유가 있을 때만 ADD를 사용하는 것이 권장됩니다.

다음 코드를 살펴봅시다.

FROM python:3.11-slim

WORKDIR /app

# COPY: 로컬 파일을 컨테이너로 복사
COPY requirements.txt .
COPY src/ ./src/

# RUN: 명령어 실행 (패키지 설치)
RUN pip install --no-cache-dir -r requirements.txt

# RUN: 여러 명령어를 &&로 연결 (레이어 최소화)
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

# ADD: 압축 파일 자동 해제 (특수한 경우에만 사용)
ADD archive.tar.gz /app/data/

COPY . .

김개발 씨는 requirements.txt 파일을 컨테이너에 넣고 pip install을 실행해야 합니다. 인터넷에서 찾아보니 COPY로 파일을 복사하고 RUN으로 설치하라고 합니다.

그런데 ADD라는 명령어도 있다고 합니다. 뭐가 다른 걸까요?

박시니어 씨가 화이트보드에 세 명령어를 적으며 설명을 시작했습니다. "이 세 가지는 자주 헷갈리는 명령어야.

하나씩 알아보자." 먼저 RUN 명령어를 살펴봅시다. RUN은 마치 터미널에서 명령어를 입력하는 것과 같습니다.

'pip install', 'apt-get install', 'npm install' 등 설치 명령어는 모두 RUN으로 실행합니다. 중요한 점은 RUN은 이미지를 빌드할 때 실행된다는 것입니다.

컨테이너가 시작될 때 실행되는 것이 아닙니다. RUN 명령어를 사용할 때는 레이어라는 개념을 이해해야 합니다.

Dockerfile의 각 명령어는 하나의 레이어를 만듭니다. 레이어가 많으면 이미지 크기가 커지고 빌드 시간이 늘어납니다.

따라서 관련된 명령어는 **&&**로 연결하여 하나의 RUN으로 실행하는 것이 좋습니다. 다음으로 COPY 명령어를 살펴봅시다.

COPY는 말 그대로 로컬 파일을 컨테이너로 복사하는 명령어입니다. 'COPY requirements.txt .'는 현재 디렉토리의 requirements.txt 파일을 컨테이너의 현재 작업 디렉토리로 복사합니다.

'COPY src/ ./src/'처럼 디렉토리 전체를 복사할 수도 있습니다. 그렇다면 ADD는 언제 사용할까요?

ADD는 COPY의 확장 버전입니다. 두 가지 추가 기능이 있습니다.

첫째, URL에서 파일을 다운로드할 수 있습니다. 둘째, tar 압축 파일을 자동으로 해제합니다.

하지만 Docker 공식 문서에서는 대부분의 경우 COPY를 사용하라고 권장합니다. ADD의 자동 해제 기능이 예상치 못한 결과를 초래할 수 있기 때문입니다.

실제 현업에서는 어떻게 활용할까요? 의존성 설치를 최적화하는 패턴이 있습니다.

먼저 requirements.txt만 복사하고 pip install을 실행합니다. 그 다음에 나머지 소스 코드를 복사합니다.

이렇게 하면 소스 코드만 변경되었을 때 pip install 단계가 캐시되어 빌드 속도가 빨라집니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 RUN 명령어를 너무 많이 나누는 것입니다. 'RUN apt-get update'와 'RUN apt-get install'을 따로 쓰면 캐시 문제로 오래된 패키지 목록을 사용하게 될 수 있습니다.

항상 update와 install은 같은 RUN에서 실행하세요. 김개발 씨는 정리하며 말했습니다.

"그러니까 파일 복사는 COPY, 명령어 실행은 RUN, ADD는 웬만하면 안 쓴다. 맞죠?" 정확합니다.

단순하고 명확한 원칙을 세우면 헷갈리지 않습니다. COPY와 RUN만 잘 사용해도 대부분의 Dockerfile을 작성할 수 있습니다.

실전 팁

💡 - RUN 명령어는 &&로 연결하여 레이어 수를 줄이세요

  • apt-get 사용 후에는 캐시를 삭제하여 이미지 크기를 줄이세요
  • 변경이 적은 파일(requirements.txt)을 먼저 복사하여 캐시를 활용하세요

4. WORKDIR과 ENV 설정

김개발 씨가 Dockerfile을 작성하다가 이상한 점을 발견했습니다. COPY와 RUN을 할 때마다 경로를 전부 입력하니 코드가 지저분해집니다.

또한 환경 변수를 설정해야 하는데 어떻게 해야 할지 모르겠습니다.

WORKDIR은 이후 명령어들의 작업 디렉토리를 설정하고, ENV는 환경 변수를 정의합니다. WORKDIR을 설정하면 상대 경로를 사용할 수 있어 코드가 깔끔해지고, ENV를 사용하면 설정값을 한 곳에서 관리할 수 있습니다.

다음 코드를 살펴봅시다.

FROM python:3.11-slim

# WORKDIR: 작업 디렉토리 설정 (없으면 자동 생성)
WORKDIR /app

# ENV: 환경 변수 설정
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# 여러 환경 변수를 한 번에 설정
ENV APP_HOME=/app \
    APP_ENV=production \
    DEBUG=false

# WORKDIR 덕분에 상대 경로 사용 가능
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

# ENV 변수를 명령어에서 사용
RUN echo "Running in $APP_ENV mode"

CMD ["python", "main.py"]

김개발 씨의 첫 번째 Dockerfile은 이런 모습이었습니다. 'RUN cd /app && pip install...', 'COPY ./src /app/src'...

모든 줄에 /app이 반복됩니다. 뭔가 비효율적이라는 느낌이 듭니다.

박시니어 씨가 코드를 보더니 웃으며 말했습니다. "WORKDIR을 쓰면 훨씬 깔끔해질 거야." WORKDIR은 마치 cd 명령어와 비슷하지만, 더 강력합니다.

쉽게 비유하자면, WORKDIR은 사무실에서 자기 자리를 정하는 것과 같습니다. 자리를 정해두면 "책상 위에 있는 서류 가져와"라고 할 수 있지만, 자리가 없으면 매번 "3층 개발팀 김개발 책상 위에 있는 서류 가져와"라고 말해야 합니다.

WORKDIR은 그런 불필요한 반복을 없애줍니다. WORKDIR /app을 설정하면 이후의 RUN, COPY, CMD 등 모든 명령어는 /app을 기준으로 실행됩니다.

'COPY . .'이라고 쓰면 현재 디렉토리의 모든 파일이 /app으로 복사됩니다.

또한 해당 디렉토리가 없으면 자동으로 생성해줍니다. 다음으로 ENV 명령어를 살펴봅시다.

ENV는 컨테이너 내부의 환경 변수를 설정합니다. Python 개발자라면 PYTHONDONTWRITEBYTECODE와 PYTHONUNBUFFERED 두 가지를 자주 보게 됩니다.

전자는 .pyc 파일 생성을 막고, 후자는 출력 버퍼링을 비활성화합니다. 컨테이너 환경에서는 이 두 설정이 거의 필수입니다.

ENV로 설정한 변수는 빌드 시점런타임 모두에서 사용할 수 있습니다. 예를 들어 'ENV APP_ENV=production'으로 설정하면, RUN 명령어에서 $APP_ENV로 접근할 수 있고, 컨테이너가 실행된 후에도 애플리케이션에서 이 값을 읽을 수 있습니다.

설정값을 한 곳에서 관리하면 나중에 수정하기도 쉽습니다. 실제 현업에서는 어떻게 활용할까요?

데이터베이스 연결 정보나 API 키 같은 민감한 정보는 ENV에 직접 넣지 않습니다. 대신 기본값만 설정해두고, 컨테이너 실행 시 -e 옵션이나 docker-compose.yml에서 덮어씁니다.

이렇게 하면 같은 이미지를 개발, 스테이징, 프로덕션 환경에서 각각 다른 설정으로 실행할 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 비밀번호나 API 키를 ENV에 직접 하드코딩하는 것입니다. Dockerfile은 이미지에 포함되므로, 누군가 이미지를 분석하면 비밀 정보가 노출됩니다.

민감한 정보는 반드시 실행 시점에 주입하세요. 김개발 씨는 자신의 Dockerfile을 리팩토링하기 시작했습니다.

WORKDIR /app을 추가하고, 반복되는 경로들을 상대 경로로 바꿨습니다. 코드가 훨씬 깔끔해졌습니다.

WORKDIR과 ENV는 Dockerfile을 더 깔끔하고 유지보수하기 좋게 만들어줍니다. 작은 명령어지만 잘 활용하면 큰 차이를 만들어냅니다.

실전 팁

💡 - WORKDIR은 절대 경로로 설정하고, 이후에는 상대 경로를 사용하세요

  • Python 컨테이너에서는 PYTHONDONTWRITEBYTECODE와 PYTHONUNBUFFERED를 설정하세요
  • 민감한 정보는 ENV에 직접 넣지 말고 실행 시점에 주입하세요

5. EXPOSE와 포트 설정

김개발 씨가 Flask 웹 서버를 Docker로 띄우려고 합니다. 컨테이너는 실행되는데 브라우저에서 접속이 안 됩니다.

"분명히 5000번 포트로 실행했는데 왜 안 되지?" 포트 설정의 미로에 빠져버렸습니다.

EXPOSE는 컨테이너가 특정 포트에서 연결을 기다린다는 것을 문서화하는 명령어입니다. 중요한 점은 EXPOSE만으로는 외부에서 접근할 수 없다는 것입니다.

실제로 포트를 열려면 docker run 시 -p 옵션을 사용해야 합니다. EXPOSE는 일종의 메모이자 docker run -P 사용 시 참조되는 정보입니다.

다음 코드를 살펴봅시다.

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# EXPOSE: 컨테이너가 리스닝할 포트를 문서화
EXPOSE 8000

# 여러 포트를 노출할 수도 있음
EXPOSE 8000 8001

# 프로토콜 지정 (기본값은 tcp)
EXPOSE 8000/tcp
EXPOSE 53/udp

# 실제 애플리케이션 실행
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

# 실행 시: docker run -p 8080:8000 myapp
# 호스트의 8080 포트를 컨테이너의 8000 포트에 연결

김개발 씨는 당황스러웠습니다. Dockerfile에 'EXPOSE 5000'이라고 썼는데 왜 접속이 안 되는 걸까요?

분명히 포트를 열라는 명령어인 것 같은데요. 박시니어 씨가 다가와 질문을 던졌습니다.

"docker run 할 때 -p 옵션 썼어?" EXPOSE는 많은 초보자를 혼란스럽게 만드는 명령어입니다. 쉽게 비유하자면, EXPOSE는 건물에 문을 설치하는 것과 같습니다.

문을 설치했다고 해서 외부인이 들어올 수 있는 것은 아닙니다. 문을 열어야(포트 매핑) 비로소 출입이 가능해집니다.

EXPOSE는 "이 컨테이너는 8000번 포트에서 무언가를 서비스할 예정입니다"라는 선언일 뿐입니다. 그렇다면 EXPOSE는 왜 필요할까요?

첫째, 문서화 역할을 합니다. Dockerfile을 읽는 다른 개발자에게 이 컨테이너가 어떤 포트를 사용하는지 알려줍니다.

둘째, docker run -P 명령어와 함께 사용됩니다. -P(대문자)를 사용하면 EXPOSE로 지정된 모든 포트를 호스트의 임의 포트에 자동으로 매핑합니다.

실제로 외부에서 접근하려면 -p 옵션을 사용해야 합니다. 'docker run -p 8080:8000 myapp'이라고 하면, 호스트의 8080 포트로 들어오는 요청이 컨테이너의 8000 포트로 전달됩니다.

왼쪽이 호스트 포트, 오른쪽이 컨테이너 포트입니다. 이 둘은 같아도 되고 달라도 됩니다.

또 하나 중요한 점은 --host 0.0.0.0 설정입니다. Flask나 uvicorn 같은 웹 서버를 실행할 때, 기본값으로 127.0.0.1(localhost)에서만 리스닝하는 경우가 많습니다.

컨테이너 내부의 localhost는 컨테이너 자신을 의미하므로, 외부에서 접근할 수 없습니다. 반드시 0.0.0.0으로 설정하여 모든 네트워크 인터페이스에서 연결을 받아야 합니다.

실제 현업에서는 어떻게 활용할까요? docker-compose를 사용할 때 EXPOSE 정보가 유용합니다.

같은 네트워크 안에 있는 다른 컨테이너는 EXPOSE된 포트로 직접 통신할 수 있습니다. 예를 들어 웹 서버 컨테이너와 데이터베이스 컨테이너가 같은 네트워크에 있다면, 웹 서버는 DB 컨테이너의 EXPOSE 포트로 접근할 수 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 EXPOSE만 쓰고 포트 매핑을 잊어버리는 것입니다.

컨테이너는 실행되지만 외부에서 접근할 수 없어서 당황하게 됩니다. 또 다른 실수는 애플리케이션이 localhost에서만 리스닝하게 두는 것입니다.

김개발 씨는 'docker run -p 5000:5000 myapp'으로 다시 실행했습니다. 드디어 브라우저에서 접속이 됩니다!

"EXPOSE는 문서화고, -p가 진짜 문을 여는 거군요." 맞습니다. EXPOSE와 -p의 차이를 이해하면 Docker 네트워킹의 기본은 파악한 것입니다.

항상 두 가지를 함께 생각하세요.

실전 팁

💡 - EXPOSE는 문서화 목적이며, 실제 포트 개방은 -p 옵션으로 합니다

  • 웹 서버 실행 시 --host 0.0.0.0을 잊지 마세요
  • docker-compose에서는 ports 설정으로 포트를 매핑합니다

6. CMD vs ENTRYPOINT 차이

김개발 씨가 Dockerfile 마지막에 CMD를 썼습니다. 그런데 문서를 보니 ENTRYPOINT라는 것도 있습니다.

둘 다 컨테이너 실행 명령어를 지정하는 것 같은데, 뭐가 다른 걸까요? 언제 어떤 것을 써야 할까요?

CMD는 컨테이너 실행 시 기본 명령어를 지정하며, docker run 시 쉽게 덮어쓸 수 있습니다. ENTRYPOINT는 컨테이너의 주 실행 파일을 지정하며, 덮어쓰기가 어렵습니다.

둘을 함께 사용하면 ENTRYPOINT는 고정 명령어, CMD는 기본 인자로 동작합니다.

다음 코드를 살펴봅시다.

# 예시 1: CMD만 사용 (가장 일반적)
FROM python:3.11-slim
WORKDIR /app
COPY . .
CMD ["python", "app.py"]
# 실행: docker run myapp
# 덮어쓰기: docker run myapp python other.py

# 예시 2: ENTRYPOINT만 사용
FROM python:3.11-slim
WORKDIR /app
COPY . .
ENTRYPOINT ["python", "app.py"]
# 실행: docker run myapp
# 인자 추가: docker run myapp --debug

# 예시 3: ENTRYPOINT + CMD 조합 (권장 패턴)
FROM python:3.11-slim
WORKDIR /app
COPY . .
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8000"]
# 실행: docker run myapp (기본값 --port 8000 사용)
# 덮어쓰기: docker run myapp --port 3000

김개발 씨는 프로젝트마다 다른 방식으로 작성된 Dockerfile을 보았습니다. 어떤 것은 CMD만 있고, 어떤 것은 ENTRYPOINT만 있고, 어떤 것은 둘 다 있습니다.

패턴이 이해가 안 됩니다. 박시니어 씨가 화이트보드를 꺼내며 말했습니다.

"이건 좀 헷갈릴 수 있어. 차근차근 설명해줄게." 먼저 CMD부터 살펴봅시다.

쉽게 비유하자면, CMD는 기본값 버튼과 같습니다. TV 리모컨에서 전원을 켜면 마지막으로 보던 채널이 나옵니다.

하지만 다른 채널 버튼을 누르면 쉽게 바꿀 수 있습니다. CMD도 마찬가지입니다.

컨테이너를 실행하면 CMD에 지정된 명령어가 실행되지만, docker run 뒤에 다른 명령어를 쓰면 쉽게 덮어쓸 수 있습니다. 'docker run myapp'을 실행하면 CMD에 지정된 'python app.py'가 실행됩니다.

하지만 'docker run myapp bash'라고 하면 python app.py 대신 bash가 실행됩니다. 이런 유연성이 CMD의 특징입니다.

다음으로 ENTRYPOINT를 살펴봅시다. ENTRYPOINT는 고정된 실행 파일을 지정합니다.

마치 전용 리모컨처럼, 특정 기기만 조작할 수 있습니다. DVD 플레이어 리모컨으로 TV 채널을 바꿀 수 없듯이, ENTRYPOINT로 지정된 명령어는 쉽게 변경되지 않습니다.

ENTRYPOINT가 지정된 경우, docker run 뒤에 오는 인자는 ENTRYPOINT의 추가 인자로 전달됩니다. 'docker run myapp --debug'라고 하면 'python app.py --debug'가 실행됩니다.

명령어 자체를 바꾸려면 --entrypoint 옵션을 명시적으로 사용해야 합니다. 그렇다면 둘을 함께 사용하면 어떻게 될까요?

ENTRYPOINT와 CMD를 함께 사용하면, ENTRYPOINT는 고정된 명령어가 되고 CMD는 기본 인자가 됩니다. 이것이 가장 권장되는 패턴입니다.

사용자는 docker run 시 인자만 변경하여 다양한 옵션을 전달할 수 있고, 명령어 자체는 안전하게 고정됩니다. 실제 현업에서는 어떻게 활용할까요?

단순한 웹 애플리케이션이라면 CMD만 사용해도 충분합니다. 디버깅이 필요할 때 bash로 쉽게 접근할 수 있어서 편리합니다.

CLI 도구나 유틸리티 컨테이너를 만들 때는 ENTRYPOINT + CMD 조합이 좋습니다. 기본 명령어는 고정하고, 사용자가 옵션만 변경할 수 있게 합니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 shell form과 exec form을 혼용하는 것입니다.

CMD python app.py처럼 쓰는 것은 shell form이고, CMD ["python", "app.py"]처럼 쓰는 것은 exec form입니다. exec form을 권장합니다.

shell form은 /bin/sh -c를 통해 실행되어 시그널 처리에 문제가 생길 수 있습니다. 김개발 씨는 정리하며 말했습니다.

"그러니까 대부분은 CMD만 쓰고, 인자 변경이 필요한 CLI 도구는 ENTRYPOINT + CMD 조합을 쓰면 되는 거죠?" 정확합니다. 복잡하게 생각하지 않아도 됩니다.

상황에 맞게 선택하면 되고, 둘의 차이를 이해하고 있으면 어떤 Dockerfile을 봐도 의도를 파악할 수 있습니다.

실전 팁

💡 - 대부분의 경우 CMD만 사용해도 충분합니다

  • exec form(대괄호 문법)을 사용하여 시그널 처리 문제를 방지하세요
  • CLI 도구는 ENTRYPOINT + CMD 조합으로 유연성을 확보하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Docker#Dockerfile#Container#DevOps#컨테이너#Docker,Container,DevOps

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스