개발블로그

Dockerfile 본문

STUDY/Docker

Dockerfile

devmel 2026. 8. 26. 16:24
Contents 접기
 

개념

개념

Docker Image를 어떻게 만들 것인지 정의한 설정 파일 

 

Application 실행에 필요한 환경을 코드 형태로 작성해두고, 

Docker은 Dockerfile의 명령을 순서대로 실행하여 Image를 생성함

 

Application의 실행 환경을 코드로 정의하여 동일한 Image를 반복해서 만들 수 있게 하는 것

의의

직접 Server나 Container 내부에 들어가서 PHP, Nginx, Library 등을 하나씩 설치하면 환경을 다시 만들 때 동일한 작업을 반복해야 함. 

=> Dockerfile에 필요한 환경을 정의하면 동일한 환경을 반복해서 생성할 수 있다

 

예시

예를들어 PHP Application을 실행하려면 다음과 같은 환경이 필요할 수 있음

=> PHP, PHP Extension, Application Source Code, 설정 File, Library, 실행 Command

 

이를 Dockerfile에 정의한다

Dockerfile
      ↓
Docker가 명령을 순서대로 실행
      ↓
PHP와 필요한 Extension 설치
      ↓
Source Code 복사
      ↓
Docker Image 생성

 


 

명령어

명령어 의미 옵션 예시
FROM 기본 이미지 [AS name ]   기본 이미지 선택 (어떤 환경에서 시작할 것인가) 

ex)
PHP가 없는 환경에서 모든 것을 처음부터 설치(X)
PHP 8.3 FPM이 준비된 Image를 기반으로 새로운 Image 구성 (O)
  FROM php:8.3-fpm

ex) FROM node:22 AS builder
= node:22 Image를 기반으로 새로운 작업 공간을 하나 만들고 그 공간 이름을 builder라고 하겠다. 
RUN 이미지 빌드 중 명령 실행   RUN apt-get update
COPY 파일 복사
(내 프로젝트 파일을 이미지 안으로 복사 : Host의 파일 => Image 내부로)

- 필요한 파일만 복사할 수도 있음

ex) 현재 Directory가 다음과 같다면
Project
├─ index.php
├─ composer.json
└─ src/
=> COPY ... 실행 후 Image 내부
/var/www/html
├─ index.php
├─ composer.json
└─ src/

• 단, 개발환경에서는 volumn을 더 많이 씀 ⇒ 내 pc 코드 수정이 컨테이너 안에도 바로 반영됨.
--from = builder 
=> 앞 Stage에서 만든 File을 현재 Stage로 가져옴.
COPY . .

- 필요한 파일만 복사
COPY composer.json composer.lock ./
ADD COPY와 비슷하지만 압축 해제나 URL 처리 같은 추가 기능이 있음.
• 일반적인 프로젝트 파일 복사는 보통 COPY를 더 많이 씀
   
WORKDIR 작업 디렉터리 설정
→ 이후 명령어들은 이 위치를 기준으로 실행됨.
  WORKDIR /app
CMD 컨테이너가 시작될 때 기존으로 실행할 명령어
• 기본 실행 명령 
  CMD ["php-fpm"]
ENTRYPOINT CMD와 비슷하게 컨테이너 시작 명령을 정함.
• 반드시 실행되는 시작점

- Container에서 실행할 기본 Program 자체를 고정하는 용도로 사용할 수 있다. 
  ENTRYPOINT ["php"]
EXPOSE 컨테이너가 사용할 포트 표시
• 실제로 포트를 외부에 열어주는 명령이 아님 ⇒ 실제로 내 PC에서 접속하려면 Compose나 docker run에서 ports를 설정해야 함.
  EXPOSE 8080
ENV 환경변수 설정

단, Password, API Key 같은 Secret을 Dockerfile에 직접 넣는 용도로 사용하면 안됨
  ENV APP_ENV=production
ARG Image Build 과정에서 사용할 변수 정의   ARG PHP_VERSION=8.3

 


 

Dockerfile 설계

개념

단순히 Application이 실행되도록 명령어를 나열하는 것이 아니라, 

작고, 빠르고, 재현 가능하며, 안전한 Docker Image를 만들도록 Dockerfile의 구조와 명령 순서를 결정하는 것

실행 가능
+
Build가 빠름
+
Image가 불필요하게 크지 않음
+
환경을 재현할 수 있음
+
민감한 정보가 포함되지 않음
+
보안상 불필요한 권한이 없음
+
유지보수하기 쉬움

 

고려할 사항

적절한 Base Image 선택

Base Image는 최종 Image의 크기, 보안, Package 설치 방법 등에 영향을 준다. 

 

고려사항 내용
Image 크기 불필요하게 큰 Image인지
필요한 Library Application에서 필요한 Package를 지원하는지
호환성 사용하는 Runtime / Extension과 문제없는지
관리 편의성 Package 설치와 문제 해결이 쉬운지
보안 오래되거나 지원 종료된 Image인지

 

 

Docker Layer를 고려해 명령 순서 설계 

Docker Image는 Dockerfile의 명령을 실행하면서 여러 Layer로 구성됨 

이전 Build에서 만들어진 Layer를 다시 사용할 수 있는 경우 Cache를 사용

=> 자주 변경되지 않는 작업은 위쪽에, 자주 변경되는 작업은 아래쪽에 배치하는 것이 일반적

 

ex)

잘못된 예)

COPY . .
RUN composer install

Source Code 하나만 수정되어도 COPY .. Layer가 변경됨.

그럼 이후의 RUN composer install도 다시 실행될 가능성이 높음. 

 

보완)

COPY composer.json composer.lock ./
RUN composer install
COPY . .

의존성을 정의하는 File를 먼저 복사함 

Souce Code만 변경되었다면 composer.json이나 composer.lock이 변경되지 않았으므로 기존 Dependency 설치 Layer를 재사용할 수 있음

 

필요한 File만 Image에 포함

.dockerigonre 를 이용하여 필요 없는 파일 제외 

 

장점

  • Build Context 감소
  • Image 크기 감소
  • Build 속도 개선
  • .env 같은 민감한 File이 Image에 들어가는 실수 방지 

 

하나의 RUN에서 불필요한 Layer 줄이기

서로 관련있는 명령은 하나의 RUN으로 묶음 

(명령어 마다 각각 별도의 Layer가 생성되기 때문) 

특히 Package 설치 Cache까지 정리하면 최종 Image에 불필요한 데이터가 남는 것을 줄일 수 있다. 

 

ex)

RUN apt-get update
RUN apt-get install -y git
RUN apt-get install -y unzip

 

위의 명령을 개선

RUN apt-get update \
    && apt-get install -y \
        git \
        unzip \
    && rm -rf /var/lib/apt/lists/*

 

 

Build 단계와 실행 단계를 구분

Build 단계 : Application을 실행할 수 있는 결과물을 만드는 데 필요한 환경

실행(Runtime) 단계 : 만들어진 결과물을 실제로 실행하는 데 필요한 환경

 

ex) React + TypeScript 프로젝트

[dockerfile]

# Node 환경 하나 만들기 : node:22 Image를 기반으로 새로운 작업 공간 하나 만들고, 그 이름을 builder라고 하기

FROM node:22 AS builder 

 

 

# 거기에 Source 코드를 만든다, 그리고 현재 프로젝트 파일을 builder  Stage에 넣음 
WORKDIR /app
COPY . .

 

# npm install
RUN npm install

 

# npm run build를 해서 dist를 만들기
RUN npm run build

 


# 이제 Nginx 환경을 새로 하나 만들기
FROM nginx:alpine

 

# 앞에서 만든 dist만 Nginx 환경으로 가져오고 마지막 Nginx 환경을 최종 Docker Image로 사용 
COPY --from=builder /app/dist /usr/share/nginx/html

---------------------------------------------------------------------------------------------------

 

builder Stage

┌─────────────────┐
│ Node.js                               │
│ npm                                     │
│ Vite                                      │
│ Source                                 │
│                                              │
│ dist ────────────────┐
└─────────────────┘   │
                                                    │ COPY
                                                    ↓
                                         Runtime Stage

              ┌─────────────────┐
              │ Nginx                                  │
              │                                            │
              │ dist                                      │
              └─────────────────┘

 

 

Dependency 버전을 명확하게 관리

명확한 Version을 지정해서 환경 재현성을 높이기

 

ex)

FROM php:latest 

=> latest는 시간이 지나면서 실제 Image가 변경될 수 있다. 

 

FROM php:8.3-fpm

 

Build 시점과 Container 실행 시점을 구분 

RUN => Image를 Build할 때 실행 됨

CMD => Container가 실행될 때 실행 됨

 

따라서 해당 작업이 Image를 만드는 데 필요한 작업인지, Container가 시작될 때 필요한 작업인지를 구분해야 함.

 

Application 설정을 Image에 직접 고정하지 않기 

민감한 값은 Image에 포함하지 않는 것이 좋음. 

대신 실행 시 환경변수 등을 통해 전달함 

 

Dockerfile
→ Application을 실행할 수 있는 공통 환경

Environment Variable
→ 실행 환경별 설정

으로 분리하는 것이 좋음. 

 

하나의 Image를 여러 환경에서 사용할 수 있음.

 

가능하면 Root가 아닌 User로 실행

보안을 고려한다면 Application 실행에 필요한 권한만 가진 User를 사용하는 것이 좋음. 

 

Container의 Main Process를 명확하게 설정 

 

[ Main Process ]

Container가 시작될 때 가장 중심이 되어 실행되는 Process
=> Container 안에 여러 Process가 존재할 수는 있지만, 그중 Container의 생명주기를 대표하는 중심 Process가 존재

- 리눅스 기준으로는 보통 이 Process가 PID 1이 됨 

ex) CMD ["php-fpm"]
=> Container가 실행될 때 php-fpm이 Main Process 역할을 함. 

 

[ Container 실행 상태 ]

 

Container는 하나의 Main Process를 기준으로 실행 상태가 관리됨

Container 시작
   ↓
Main Process 실행
   ↓
Main Process 실행 중
   ↓
Container 실행 중
   ↓
Main Process 종료
   ↓
Container 종료

 

- Main Process가 종료되면 Container도 종료됨
 ex) CMD ["echo", "hello"] 
    => Container 시작 → echo hello 실행 → hello 출력 → echo 종료 → Container 종료

 

- Web Server를 Main Process로 사용하는 이유
  => Server Process (ex PHP-FPM, Node.js Server, Nginx)는 실행된 후 Request를 기다리면서 계속 동작함.

  ex) CMD ["php-fpm"]

    => Container 시작 → php-fpm 실행 → Request 대기 → 계속 실행 → Container 유지 

 

 

 

=> Dockerfile에서는 Container가 시작될 때 실행할 핵심 Process를 명확하게 지정해야 함. 

 

 

 

[ 고려사항 ]

  • Container가 시작되면 무슨 Process를 실행할 것인가?
  • 그 Process가 계속 실행되는가?
  • Docker의 종료 Signal을 제대로 받을 수 있는가?
  • Main Process가 종료되었을 때 Container도 정상적으로 종료되는가? 

[ 핵심 ]

  • 실제 Application Server를 Main Process로 실행
  • Server는 Background가 아니라 Foreground에서 실행
    => 일반적인 Server 환경에서는 Program을 Background로 실행하기도 함.
         하지만 Container에서는 Main Process가 Background로 빠지고 원래 Process가 종료되면, 
          Container가 종료될 수 있음. 
    ex) CMD ["nginx", "-g", "daemon off;"]
     : daemon off;는 Nginx를 Background로 보내지 않고 Foreground에서 실행
  • Docker의 종료 Sinal을 Applciation이 직접 받을 수 있도록 구성  
    : docker stop → SIGTERM → Main Process → 정리 작업 실행 → Process 종료 → Container 종료 

    - CMD는 Exec Form을 사용하는 것이 좋음 (Shell Form 지양) 
      ex) CMD ["node", "server.js"]  => Node Process가 직접 Main Process가 됨
      ex)  CMD node server.js => Shell이 중간에 존재함. 이 경우 Docker의 종료 Signal이 Application
             에 직접 전달되지 않을 수 있음