도커파일이란?
도커파일은 도커 이미지를 생성하기 위한 텍스트 파일이다.
즉, 도커파일엔 설치할 패키지, 환경변수 설정, 파일 복사, 실행 명령어 등의 코드가 포함된다. 이러한 도커파일을 빌드 시키면, 읽기 전용 이미지가 만들어지게 되고, 컨테이너를 사용하고 싶은 사용자는 이 이미지를 실행하여 컨테이너를 동작시킬 수 있다.

도커파일 작성하기 (구성요소)
# 도커파일 예시
# 베이스 이미지 설정
FROM ubuntu:latest
# 작업 디렉토리 설정
WORKDIR /app
# 필요한 파일 복사
COPY . .
# 패키지 설치
RUN apt-get update && apt-get install -y curl
# 환경 변수 설정
ENV PORT=8080
# 컨테이너 시작 시 실행할 명령어 설정
CMD ["echo", "Hello, Docker!"]
🔷 FROM : 베이스 이미지를 지정
FROM 명령어는 도커 이미지의 기반이 되는 베이스 이미지를 지정하는 역할을 한다.
도커파일의 첫 번째 명령어로 사용되며, 이후의 모든 명령어는 이 베이스 이미지 위에서 실행된다.
FROM ubuntu:latest
- ubuntu기반으로 새로운 이미지를 생성한다.
- latest는 태그로, 가장 최신 버전의 Ubuntu 이미지를 사용하겠다는 의미다.
- FROM은 필수 명령어이며, 최소한 하나 이상의 FROM 명령어가 있어야 한다.
🤔 베이스 이미지는 어디서 가져오나?
- Docker Hub에서 제공하는 이미지를 가져올 수 있다.
- docker pull 명령어를 사용하면 미리 다운받아 도커에서 확인할 수도 있다.
🔷 WORKDIR : 작업 디렉토리 설정
WORKDIR 명령어는 컨테이너 안에서 사용할 디렉토리를 지정해주는 명령어이다.
이후 COPY, RUN, CMD 등의 명령어는 이 디렉토리 안에서 실행된다.
WORKDIR /app
- 컨테이너 내부의 /app 디렉토리를 기본 작업 경로로 설정한다.
- 이후 명령어들은 /app 디렉토리에서 실행된다.
🤔 설정해주는 이유?
설정을 안해주면 자동으로 루트 디렉토리에 저장이 되는데, 이는 가독성과 유지보수에 좋지 않기 때문에 디렉토리 안에 파일을 두는 것이 선호된다.
이때 WORKDIR을 안 쓰면, 명령어마다 경로를 직접 지정해야 한다.
사용을 안한다면, COPY . /app RUN cd /app && npm install
이렇게 cd로 이동해야하는데, WORKDIR을 쓰면 그냥 RUN npm install만 사용하면 된다.
🔷 COPY, ADD : 파일 복사
COPY와 ADD는 호스트(로컬)에서 컨테이너 내부로 파일을 복사한다.
COPY
COPY . /app
COPY package.json /app
- 현재 디렉토리 (.)의 모든 파일을 컨테이너의 /app 디렉토리로 복사한다.
- package.json만 /app으로 복사한다.
ADD
#파일 복사
ADD myfile.txt /app
# URL 복사
ADD https://example.com/file.txt /app/file.txt
# 압축파일 복사
ADD archive.tar.gz /path/in/container/
- 파일 복사: myfile.txt 를 /app 디렉토리로 복사한다.
- URL 복사: URL에서 직접 다운로드 할 수 있다.
- 압축파일 복사: archive파일이 컨테이너 내 /path/in/container 경로로 복사되면서 자동으로 압축이 풀린다.
COPY와 ADD의 차이점
ADD는 COPY와 비슷하지만, URL에서 파일을 가져오거나 압축 파일을 자동으로 해제할 수 있다.
보통 예측 가능하고, 불필요한 동작을 피할 수 있는 COPY를 사용하는 것이 권장되며, 압축 해제를 원하는 경우에만 ADD를 사용한다.
🔷 RUN : 이미지 빌드 시 기본 명령어
run 명령어는 도커파일로 부터 도커 이미지를 빌드하는 순간에 실행되는 명령어이다.
따라서, 라이브러리 설치 같은 작업을 할때 쓰인다.
🔷 CMD, ENTRYPOINT: 컨테이너 실행 시 기본 명령어
CMD와 ENTRYPOINT는 컨테이너 실행 시 최초로 실행될때 수행된다.
일반적으로 둘 중 하나만 사용하거나, 함께 사용할 때는 역할에 따라 다르게 사용된다.
함께 사용할 시에는 ENTRYPOINT는 CMD보다 우선순위가 높아 먼저 동작한다.
# CMD
CMD ["echo", "CMD test"]
# ENTRYPOINT
ENTRYPOINT ["echo", "ENTRYPOINT test"]
- 이미지를 빌드하고 실행해보면 CMD를 사용했을땐 CMD test라고 출력되고, ENTRYPOINT를 사용했을땐 ENTRYPOINT test 가 출력된다.
🤔 CMD와 ENTRYPOINT 차이점?
cmd와 차이점은, cmd는 명령어를 덮어쓸 수 있고, 엔트리는 고정적으로 설정된다.
docker run cmd_test echo hello
위의 코드는 cmd_test라는 이미지를 실행하는 코드이다.
위와 같이 실행하면, cmd의 경우 CMD test가 아닌, hello코드가 출력될 것이다.
이유는, 덮어쓰여지기 때문이다.
그렇다면 엔트리의 경우엔 ENTRYPOINT test 가 아닌, ENTRYPOINT test hello 코드가 출력된다.
이유는, 엔트리는 고정적이기 때문에 뒤에 추가로 넣어줬던 echo hello가 붙어서 실행이 되기 때문이다.
따라서 CMD는 변경할 수 있을 때 , 엔트리는 컨테이너가 최초에 꼭 실행해야만 하는 명령어가 있을 때 활용하면 된다.
🔷 기타: EXPOSE, ENV
EXPOSE: 컨테이너 내부에서 사용할 포트를 지정한다.
EXPOSE 8080
- 컨테이너가 내부적으로 8080포트를 사용한다는 것을 명시한다.
ENV : 컨테이너 내부에서 사용할 환경변수를 설정할 수 있다. 보통 보안 때문에 외부에 env파일을 놓고 사용하는 경우가 많다.
# 환경변수 설정
ENV NODE_ENV=production
ENV PORT=8080
도커 이미지 빌드 과정
1️⃣ 도커 파일 생성
우선 프로젝트 루트에 Dockerfile이라는 이름의 파일을 생성하고 위에서 설명한 내용을 작성한다.
docker init: 해당 명령을 사용하면 Dockerfile뿐만 아니라, .dockerignore , compose.yaml파일도 자동으로 생성된다.
dockerignore: 불필요한 파일들이 이미지에 포함되지 않도록 관리할 수 있다.
docker-compose.yml: 여러 개의 컨테이너를 관리하는 데 유용하다.
2️⃣ 이미지 빌드
로컬 컴퓨터에서 터미널을 열고 도커파일이 있는 디렉토리로 이동한다.
그 이후, docker build 명령어를 사용하여 도커파일을 이미지로 빌드한다.
# 도커 파일을 자동으로 찾아서 빌드
docker build -t my-app .
# 도커 파일을 직접 지정해서 빌드
docker build -t cmd_test -f dockerfile .
자동
-t : my-app이라는 이름으로 이미지 생성
.: 현재 디렉토리에서 Dockerfile을 찾아서 빌드
수동
-t cmd_test : cmd_test라는 이름으로 이미지 생성
-f dockerfile : 사용할 Dockerfile 지정
. : 도커가 사용할 폴더 위치
3️⃣ 새 이미지 저장
도커는 도커파일을 한 줄씩 읽고 각 명령을 실행한다.
도커파일의 모든 지침이 실행되면 도커가 새 이미지를 만든다.
새 이미지는 도커의 로컬 이미지 레지스트에 저장된다.
그리고 docker images 명령을 사용하여 로컬 컴퓨터의 모든 이미지 목록을 볼 수 있다.
4️⃣ 컨테이너 실행
이미지가 빌드되면 docker run 명령을 사용하여 컨테이너를 실행하는데 사용할 수 있다.
# my-app 이미지 실행
docker run my-app
5️⃣ 컨테이너에서 커밋 (선택)
컨테이너에서 수정한 사항을 새로운 이미지로 저장할 수 있다. 기존이미지는 수정되지 않으며, 변경 내용을 포함한 새로운 이미지가 만들어진다. 이를 위해서는 docker commit 명령을 사용한다.
docker commit <컨테이너_ID> <새로운_이미지_이름>
❗️주의
docker commit으로 생성된 이미지는 직접적으로 어떤 변경이 이루어졌는지 알기 어려울 수 있다. 이는 Dockerfile을 통해 이미지를 빌드하는 것보다 추적이 어려운 점이 있다. 따라서, 가능한 한 Dockerfile을 사용하여 이미지를 빌드하고, docker commit은 보조적인 용도로 사용하는 것이 좋다.

도커 이미지의 구조 (레이어)
Docker 이미지는 기본적으로 특정 시점의 파일 시스템 스냅샷인 여러 레이어로 구성된다. 각 레이어는 해당 이미지를 구성하는데 필요한 파일이나 디렉토리, 설정, 라이브러리 등의 정보를 담고 있고, 새 파일 추가 또는 기존 파일 수정과 같이 파일 시스템에 대한 특정 변경 사항을 나타낸다.
원래 기존 도커는 레이어 개념이 없었다. 따라서 기존 이미지에 변경사항이 생겨 새 이미지를 받아와야 했었다면, 이제는 레이어 개념을 통해 기존 레이어는 그대로 둔 채 새로 업데이트된 내용만 담고 있는 레이어만 쌓는 개념으로 관리한다.
따라서 변경되지 않은 부분은 재사용되고, 새로 업데이트된 부분만 새 레이어로 추가되므로, 이미지는 새로 만들어지지만, 이미지의 크기와 빌드 시간이 효율적으로 관리된다.
이미지 레이어 장점
1️⃣ 이미지 간에 레이어 공유 가능
동일한 기본 이미지에서 여러 이미지를 빌드하는 경우 동일한 레이어를 재사용할 수 있으므로 시간과 디스크 공간을 절약할 수 있다.
2️⃣ 레이어를 캐시 가능
레이어는 변경할 수 없기 때문에 Docker는 레이어를 로컬로 캐시하고 변경되지 않은 경우 재사용할 수 있다. 이렇게 하면 이미 빌드된 레이어를 캐시로 사용하면서 효율적인 빌드를 가능하게 한다.
2️⃣ 레이어 검사 가능
docker history 명령을 사용하여 Docker 이미지를 구성하는 레이어를 검사할 수 있다. 이는 이미지가 불필요하게 커지는 이유를 찾을 수 있으며, 민감한 데이터를 포함하는 명령을 사용했는지 확인할 수 있다.
레이어 정리
Docker 이미지는 레이어화된 파일 시스템을 사용하므로 이미지를 효율적으로 저장하고 배포할 수 있다.
이미지를 업데이트할 때는 변경된 부분만 새로운 레이어로 추가하므로 이전 레이어는 그대로 유지된다. 이를 통해 이미지의 용량이 크게 감소하고 더욱 빠른 배포가 가능해진다.

최적화된 도커파일 작성 방법
도커파일을 작성할 때 몇 가지 최적화 기법을 적용하면 더 가볍고 효율적인 이미지를 만들 수 있다.
1️⃣ 레이어 최소화
각 RUN명령어는 새로운 레이어를 생성하기 때문에 여러 개의 명령어를 &&로 연결하여 최소화하는 것이 좋다.
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
2️⃣ 캐싱 활용
이미지 빌드 시 각 명령어마다 캐시가 적용된다.
자주 변경되지 않는 부분을 위쪽에 배치하면 Docker의 캐싱을 효과적으로 사용할 수 있다.
FROM node:18
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD ["node", "app.js"]
COPY package.json . → 이전과 같은 package.json이면 캐시된 결과를 사용한다.
RUN npm install → package.json이 바뀌지 않았다면 캐시된 결과를 사용하면서, npm install을 실행하지 않는다.
3️⃣ 멀티 스테이지 빌드 활용
멀티 스테이지 빌드는 Docker에서 여러 개의 FROM 명령어를 사용하여, 빌드 환경과 실행 환경을 분리하는 방법이다.
FROM golang:1.20 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/myapp .
CMD ["./myapp"]
첫 번째 단계에서는 빌드 환경에 필요한 모든 것을 포함해 작업한 후, 최종 실행 단계에서는 빌드 작업과 관련 없는 파일들을 제외하고, 최소한의 환경만 포함된 이미지로 빌드하여 이미지 크기를 줄일 수 있다.
정리
도커파일은 애플리케이션 실행 환경을 정의하는 중요한 구성 요소이다. 여러 가지 명령어들(FROM, WORKDIR, COPY, RUN, CMD 등)을 사용해 이미지를 만들고, 이를 기반으로 컨테이너를 실행할 수 있다. 도커파일을 잘 작성하면 이미지 크기를 최적화하고 빌드 속도를 개선할 수 있다.
결국, 도커파일 작성은 효율적이고, 재사용 가능한 이미지를 만들기 위한 과정이다. 각 명령어의 특성을 잘 이해하고 사용하면, 더 깔끔하고 빠르게 컨테이너 환경을 구축할 수 있다.