본문 바로가기
[MLOps] 모델 서빙과 배포/Docker와 컨테이너

Dockerfile 작성법 ─ 이미지 빌드부터 Docker Hub 푸시까지

by 파이로그 2026. 7. 11.
이미지를 직접 만드는 법을 정리한다.
Dockerfile의 핵심 명령어와 작성 순서, 빌드와 실행,
그리고 완성한 이미지를 Docker Hub에 올려 공유하는 것까지

 

Python, Nginx 같은 이미지는 받아서 쓰면 된다.

 

그런데 진짜 필요한 건 내 코드가 담긴 이미지다.

내 모델, 내 서버 코드, 실행에 필요한 환경이 함께 담긴 이미지.

그건 어디에도 없어서 직접 만들어야 한다.

 

그 설계도를 쓰는 파일이 Dockerfile이다.

 

Docker에서 머리를 쓰는 구간은 사실상 여기부터라고 느꼈다.

 

 

Dockerfile은 이미지를 만드는 설명서다

전체 흐름은 이렇게 이어진다.

Dockerfile 작성 → docker build → 이미지 완성 → docker run → 컨테이너 실행

 

Dockerfile은 "이 환경을 어떻게 만드는지"를 순서대로 적은 텍스트 파일이다.

 

어떤 Python 이미지를 기반으로 하고, 라이브러리는 뭘 설치하고, 코드는 어디에 두고, 마지막에 어떤 명령으로 실행하는지

환경 설치 과정 전체를 코드로 적어두는 것이다.

 

이렇게 적어두면 좋은 점은 명확하다.

사람이 손으로 설치하면 매번 결과가 조금씩 다르지만, 파일로 적어두면 몇 번을 빌드해도 똑같은 이미지가 나온다.

 

 

프로젝트 폴더 준비

Dockerfile을 쓰기 전에, 폴더 구조부터 잡는 게 순서다. 기본형은 이렇다.

my-app/
├── Dockerfile          ← 이미지 설계도
├── .dockerignore       ← 이미지에 넣지 않을 파일 목록
├── requirements.txt    ← 파이썬 라이브러리 목록
└── src/
    └── main.py         ← 실제 코드

 

.dockerignore는 Git의 .gitignore와 같은 역할이다.

가상환경 폴더, 캐시, 데이터처럼 이미지에 들어갈 필요 없는 것들을 적어두면 빌드할 때 제외된다.

 

 

Dockerfile 한 줄씩 뜯어보기

가장 기본적인 Dockerfile 완성본을 먼저 보고, 한 줄씩 뜯어보자.

FROM python:3.12-slim

WORKDIR /app

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

COPY src ./src

CMD ["python", "-m", "src.main"]

 

Docker를 로봇이라고 생각하면 이해가 쉽다.

로봇이 빈 방에서 이 설명서를 위에서부터 한 줄씩 실행하며 이미지를 조립해 나간다.

 

FROM ─ 바닥에 깔 베이스 이미지. 여기서는 경량 Python 이미지를 기반으로 삼았다. 항상 첫 줄이다.
WORKDIR ─ 로봇의 작업 위치 지정. "이제부터 /app 폴더에서 일해"라는 뜻이다.
COPY ─ 내 컴퓨터의 파일을 이미지 안으로 복사. 헷갈리기 쉬운 부분인데, COPY의 앞과 뒤에서 앞은 내 컴퓨터 쪽 경로, 뒤는 이미지 안쪽 경로다
RUN ─ 이미지 안에서 명령어 실행. 여기서는 라이브러리 설치.
CMD ─ 이 이미지로 컨테이너를 시작할 때 실행할 명령. 빌드 중에 실행되는 RUN과 달리, CMD는 나중에 컨테이너가 켜질 때 실행된다.

 

 

왜 requirements를 코드보다 먼저 복사할까

위 Dockerfile에서 이상한 점이 하나 있다.

requirements.txt만 먼저 복사해서 설치하고, 코드는 그 뒤에 따로 복사한다.

왜 굳이 두 번에 나눌까?

 

이유는 캐시다.

 

Dockerfile의 한 줄이 이미지의 한 층(레이어)이 되고,

다시 빌드할 때 바뀌지 않은 층은 캐시에서 재사용된다.

대신 어떤 층이 바뀌면 그 아래 줄들은 전부 다시 실행된다.

 

코드는 매일 고치지만 라이브러리 목록은 가끔 바뀐다.

 

그래서 라이브러리 설치를 코드 복사보다 위에 두면, 코드만 고쳤을 때 설치 단계는 캐시로 건너뛴다.

코드만 수정하고 재빌드하면:

FROM python:3.12-slim        → 캐시 재사용
WORKDIR /app                 → 캐시 재사용
COPY requirements.txt .      → 캐시 재사용
RUN pip install -r ...       → 캐시 재사용 (몇 분 절약!)
COPY src ./src               → 여기부터만 다시 실행
CMD [...]

 

AI 프로젝트라면 이 차이가 특히 크다.

PyTorch 같은 라이브러리는 설치에 몇 분씩 걸리는데,

순서를 잘못 잡으면 코드 한 줄 고칠 때마다 그 시간을 다시 기다리게 된다.

 

원칙은 하나다. 자주 바뀌는 줄일수록 아래로.

 

 

빌드하고 실행하기

Dockerfile을 다 썼으면 빌드한다.

# 이미지 빌드 (-t: 이미지 이름 붙이기, 마지막 점: 현재 폴더 기준)
docker build -t my-app .

# 만든 이미지로 컨테이너 실행
docker run my-app

 

빌드 로그를 보면 Dockerfile의 각 줄이 순서대로 실행되는 게 그대로 보인다.

 

첫 빌드는 베이스 이미지를 받고 라이브러리를 까느라 몇 분 걸리지만,

코드만 고치고 다시 빌드하면 캐시 덕에 몇 초 만에 끝난다.

 

이 빌드-실행이 잘 되면, 내 코드가 담긴 이미지가 완성된 것이다.

 

 

알아두면 좋은 명령어들

기본형 외에 자주 만나는 명령어가 몇 개 있다.

 

ENV와 ARG

둘 다 변수를 설정하는데 수명이 다르다. ENV는 빌드할 때와 컨테이너 실행 중에도 유지되는 환경 변수고, ARG는 빌드하는 동안만 쓰이고 사라진다.

 

여기서 주의할 점 하나.

API 키나 비밀번호를 ENV에 직접 적으면 안 된다.

docker inspect 한 번이면 이미지의 환경 변수가 전부 노출되기 때문이다.

비밀값은 Dockerfile 안에 직접 적지 말고, .env 파일이나 배포 환경의 Secret 설정으로 분리해서 실행할 때 주입하는 게 안전하다.

 

EXPOSE

"이 이미지는 이 포트를 사용한다"는 표시. 문서 역할일 뿐 실제로 포트를 열지는 않는다.

실제 연결은 실행할 때 -p 옵션으로 한다.

 

ENTRYPOINT

CMD와 비슷하게 시작 명령을 정하는데, 차이가 있다.

CMD는 실행할 때 다른 명령으로 덮어쓸 수 있는 기본값이고, ENTRYPOINT는 고정 명령이다.

보통은 CMD만으로 충분하다.

 

 

Docker Hub에 올리기 ─ 이미지 공유

이미지를 만들었으면 공유할 차례다.

코드를 GitHub에 올리듯, 이미지는 Docker Hub에 올린다.

 

절차는 세 단계다. 로그인 → 태그 달기 → 푸시

1) Docker Hub 로그인
docker login

2) 태그 달기 (계정명/이미지명:버전)
docker tag my-app 내계정명/my-app:v1.0.0

3) 업로드
docker push 내계정명/my-app:v1.0.0

 

여기서 태그를 먼저 달아야 하는 이유가 있다.

Docker Hub는 계정명/이미지명 형식으로 저장소를 구분하기 때문에, 그 형식의 이름표를 붙여야 올라간다.

 

올리고 나면 hub.docker.com의 내 저장소에 이미지가 생기고, 이렇게 받을 수 있다.

docker pull 내계정명/my-app:v1.0.0

 

권한 설정에 따라 공개 이미지로 공유할 수도 있고, 제한된 사람만 받게 관리할 수도 있다.

이제 코드가 아니라 실행에 필요한 환경을 함께 주고받는 것이다.

 

 

버전 태그 규칙 ─ 숫자 세 개의 의미

태그의 v1.0.0 같은 숫자에는 관례가 있다. 세 자리가 각각 변화의 크기를 뜻한다.

v 1 . 2 . 3
  │   │   └─ patch : 버그 수정, 작은 개선
  │   └───── minor : 기능 추가 (기존 사용법은 그대로)
  └───────── major : 구조가 바뀌는 큰 변화 (호환 안 될 수 있음)

 

AI 서비스 기준으로 예를 들면 감이 온다.

  • 모델 자체를 다른 모델로 교체했다 → major를 올린다 (v2.0.0)

  • 전처리 단계를 하나 추가했다 → minor를 올린다 (v1.1.0)

  • 모델 로드할 때 한글 깨지던 버그를 고쳤다 → patch를 올린다 (v1.0.1)

이미지는 한번 만들면 수정할 수 없으니, 수정 = 재빌드 + 새 태그 + 푸시가 한 세트다.

이렇게 버전이 쌓이면 문제가 생겼을 때 이전 버전으로 바로 되돌아갈 수 있다.

 

 

정리

  • Dockerfile은 이미지를 만드는 설명서다. 환경 설치 과정을 코드로 적어두면 몇 번을 빌드해도 같은 이미지가 나온다.

  • 기본 골격은 FROM(베이스) → WORKDIR(작업 위치) → COPY+RUN(의존성 설치) → COPY(코드) → CMD(실행 명령) 순서다.

  • COPY는 앞이 내 컴퓨터, 뒤가 이미지 안쪽 경로다.

  • 한 줄이 한 레이어다. 자주 바뀌는 줄(코드)을 아래에 둬야 캐시를 살려 빌드가 빨라진다.

  • 비밀값은 ENV에 직접 적지 말고 .env 파일이나 배포 환경의 Secret 설정으로 분리한다. docker inspect로 노출될 수 있다.

  • 이미지 공유는 login → tag → push 세 단계. 받을 때는 pull이다.
  • 버전 태그는 major(큰 구조 변화). minor(기능 추가). patch(버그 수정) 규칙으로 올린다.

빈 폴더에서 시작해 Dockerfile을 쓰고, 빌드하고, Docker Hub에 올리기까지가 한 사이클이다.

 

이 사이클이 손에 익으면, "실행에 필요한 환경을 함께 공유한다"는 말이 더 이상 추상적으로 들리지 않는다.