Dockerfile 작성법 기초 (FROM, COPY, RUN, 멀티 스테이지 빌드)
Docker Hub의 공식 이미지를 받아 쓰는 데 익숙해졌다면, 다음 단계는 내 애플리케이션을 이미지로 만드는 것이다. 이미지를 만드는 설계도가 Dockerfile이다. 이 글에서는 Dockerfile 작성법 기초 명령어와 빌드, 이미지 크기를 줄이는 방법, 실무에서 주의할 점까지 정리한다. Docker 설치와 기본 명령은 Docker 설치와 기본 명령어에 먼저 정리해 두었다.

Dockerfile 작성법 기본 명령어
| 명령 | 뜻 |
|---|---|
FROM | 기반 이미지(반드시 첫 줄) |
WORKDIR | 작업 디렉터리 지정 |
COPY | 내 파일을 이미지 안으로 복사 |
RUN | 이미지를 만들 때 실행할 명령(패키지 설치 등) |
ENV | 환경 변수 |
EXPOSE | 컨테이너가 쓰는 포트 표시(문서 역할) |
USER | 실행 계정 |
CMD | 컨테이너가 시작될 때 실행할 기본 명령 |
Dockerfile 작성법 첫 예제: 간단한 웹 앱
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
# 의존성 목록을 먼저 복사해 빌드 캐시 활용
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 소스 복사
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]# 빌드: 현재 디렉터리(.)의 Dockerfile 로 이미지 만들기
docker build -t myapp:1.0 .
# 실행
docker run -d --name myapp -p 127.0.0.1:8000:8000 myapp:1.0
docker logs -f myappDockerfile 작성법 예제를 실행할 전체 파일 준비
위 Dockerfile은 app.py와 requirements.txt가 있어야 실행할 수 있다. 빈 디렉터리를 하나 만들고 Dockerfile, app.py, requirements.txt, .dockerignore를 같은 위치에 저장하자. 이 작은 예제는 Python 표준 라이브러리만 사용하므로 외부 패키지가 없다. requirements.txt는 주석 한 줄만 두고, 이후 패키지를 추가할 때의 설치 위치를 보여 주기 위해 Dockerfile의 pip 설치 단계를 남겨 두었다.
myapp/
├── Dockerfile
├── app.py
├── requirements.txt
└── .dockerignoreimport json
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = json.dumps({"message": "hello docker"}).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
if __name__ == "__main__":
host = os.environ.get("BIND_HOST", "0.0.0.0")
port = int(os.environ.get("PORT", "8000"))
HTTPServer((host, port), Handler).serve_forever()# requirements.txt
# 표준 라이브러리만 사용하는 예제: 외부 패키지 없음HTTP 서버는 컨테이너 안에서 0.0.0.0:8000에 바인딩한다. 컨테이너 내부에서 127.0.0.1에만 바인딩하면 포트 매핑으로 접근할 수 없기 때문이다. 반면 호스트 쪽 공개 범위는 앞의 -p 127.0.0.1:8000:8000처럼 루프백으로 제한했다. Python http.server는 학습용이며 운영 웹 서버를 대체하는 구성은 아니다.
curl --fail http://127.0.0.1:8000/
# 기대 응답: {"message": "hello docker"}
# 실습 종료 후 생성한 컨테이너 정리
docker stop myapp
docker rm myapp이 예제의 Python 응답 코드는 별도 프로세스로 실행해 HTTP 200과 JSON 응답을 확인했다. 위 curl 주석은 컨테이너가 정상 실행됐을 때의 기대 응답이다. 컨테이너 빌드 성공과 실행 상태는 각자의 Docker 환경에서 docker build, docker ps, docker logs 순서로 확인하자.
시작되지 않으면 오류 메시지부터 나눠 보자. requirements.txt를 찾지 못하면 파일 위치와 .dockerignore를, app.py를 찾지 못하면 COPY와 WORKDIR를 확인한다. 이름 myapp이 이미 존재하면 기존 컨테이너 상태를 먼저 보고 다른 이름으로 실행하며, 포트가 사용 중이면 기존 서비스를 무작정 종료하지 말고 앞에서 연결한 포트 점검 절차를 따른다.
빌드 캐시와 명령 순서
Docker는 Dockerfile의 각 줄을 층(layer)으로 쌓고, 바뀌지 않은 줄은 이전 결과를 재사용한다. 그래서 자주 바뀌는 소스 코드 복사는 뒤쪽에, 잘 안 바뀌는 의존성 설치는 앞쪽에 두는 것이 좋다. 위 예제처럼 requirements.txt만 먼저 복사해 설치하면, 소스만 고쳤을 때 패키지 설치를 다시 하지 않아 빌드가 빨라진다.
.dockerignore로 불필요한 파일 빼기
# .dockerignore
.git
*.log
.env
node_modules
__pycache__COPY . .는 디렉터리 전체를 복사하므로, .git이나 비밀번호가 든 .env가 이미지에 들어가지 않도록 반드시 .dockerignore를 만든다. Git의 .gitignore와 같은 역할이다(Git 사용법 기초).
Dockerfile 작성법 실전: Java 멀티 스테이지 빌드
빌드 도구는 이미지를 만들 때만 필요하고 실행할 때는 필요 없다. 멀티 스테이지 빌드를 쓰면 빌드 단계와 실행 단계를 나눠 최종 이미지를 작게 만들 수 있다.
# 1단계: 빌드
FROM eclipse-temurin:21-jdk AS build
WORKDIR /src
COPY . .
RUN ./gradlew bootJar --no-daemon
# 2단계: 실행 (JRE 만)
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /src/build/libs/*.jar app.jar
RUN useradd --system app
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]이미지를 빌드하거나 컨테이너를 실행할 때 자원이 부족하다면 리눅스 서버 모니터링 도구로 CPU와 메모리부터 확인하자.
JDK 설치 방법은 자바 JDK 설치에 있다.
CMD와 ENTRYPOINT 차이
CMD는 docker run 뒤에 다른 명령을 주면 통째로 바뀌고, ENTRYPOINT는 고정된 실행 명령이라 뒤에 준 값이 인자로 붙는다. 하나의 프로그램만 실행하는 이미지라면 ENTRYPOINT에 실행 파일을, CMD에 기본 옵션을 두는 방식이 많이 쓰인다. 둘 다 [“명령”, “인자”] 같은 배열 형태로 쓰는 것을 권한다.
주의할 점
- 비밀번호나 API 키는 Dockerfile이나 이미지에 넣지 않는다. 실행할 때 환경 변수나 파일로 넘긴다.
- USER로 일반 계정을 지정해 root로 실행하지 않는다.
- 기반 이미지는 slim처럼 작은 버전을 쓰고 태그를 고정한다(latest 대신 3.12-slim 등).
- RUN 명령은 &&로 묶어 층을 줄이고, 설치 후 캐시를 지운다.
컨테이너가 포트 충돌로 시작되지 않으면 포트 사용 중 오류 해결 방법에서 사용 중인 프로세스를 찾는 순서를 확인할 수 있다.
명령어 전체는 Dockerfile 공식 레퍼런스에 있다.
Dockerfile 작성법 정리
FROM으로 시작해 WORKDIR, COPY, RUN으로 환경을 만들고, CMD나 ENTRYPOINT로 실행 방법을 정하는 것이 기본 흐름이다. 의존성은 앞에, 소스는 뒤에, 비밀 정보는 밖에 두는 원칙만 지켜도 빠르고 안전한 이미지를 만들 수 있다. Docker 실행과 로그 확인이 익숙하지 않다면 Docker 기본 명령어부터 함께 확인하자.
첫 댓글을 남겨보세요