상세 컨텐츠

본문 제목

Docker 입문 완전정리: 이미지 한 번에 이해하기

AI DevOps

by thisnorm 2026. 4. 23. 17:41

본문

Docker를 처음 공부할 때는 용어가 한꺼번에 쏟아져서 흐름이 잘 보이지 않는 경우가 많다. 특히 개념은 이해한 것 같은데 실제 명령어와 연결이 안 되면 금방 막히게 된다.

이 글은 티스토리에서 읽기 편한 흐름으로, 실행 흐름과 핵심 명령어를 중심으로 Docker의 핵심을 한 번에 정리한 글이다.



Dockerfile이란?

Dockerfile은 이미지를 재현 가능하게 만들기 위한 선언 파일이다. 마치 요리 레시피처럼 - 어떤 재료(이미지)를 쓰고, 어떤 순서로 준비하고, 최종적으로 어떤 명령을 실행할지를 한 파일에 적어 둔다. 이 파일만 있으면 누구든 동일한 이미지를 만들 수 있다.

Dockerfile이 필요한 이유

손으로 컨테이너를 수정하고 docker commit 으로 이미지를 저장하는 방식도 있다. 하지만이 방법에는 치명적인 문제가 있다.

재현 불가능

어떤 명령을 어떤 순서로 실행했는지 알 수 없어, 동일 이미지를 다시 만들기 어렵다.

협업 불가능

팀원에게 "이렇게 만들었어요"를 파일로 공유할 수 없고, 구두로 설명해야 한다.

자동화 불가능

CI/CD 파이ㅡ라인에서 자동으로 이미지를 빌드할 수 없다.

💡 *Dockerfile은 이미지 생성 과정을 코드로 문서화한다. Git으로 버전 관리하고 팀이 공유할 수 있다. *



가장 작은 Dockerfile 구조

복잡해 보이지만 기본 축으 네 가지이다. 아래 예시는 Node.js 앱의 최소 Dockerfile이다.



가장 작은 Dockerfile 구조 관련 이미지




build context란 무엇인가

docker build. 를 실행할 때 마지막의.(점)이 바로 빌드 컨텍스트이다. Docker 데몬은이 디렉터리 전체를 빌드에 포함시킨다.

포함되는 것

현재 디렉터리의 모든 파일과 폴더 - 소스 코드, 설정 파일 등

문제가 되는 것

node_modules, env,.git, 빌드 산출물 등 불필요하거나 민감한 파일

💡 *빌드 컨텍스트가 크면 빌드가 느려지고, 민감한 파일이 이미지에 포함될 수 있다. .dockerignore 파일로 제외 목록을 지정한다. *



# .dockerignore 예시
node_modules
.env
.git
*.log




FROM - 시작점 정하기

FROM 은 Dockerfile의 첫 번째 지시어로, 베이스 이미지 를 지정한다. 베이스 이미지는 최종 이미지의 크기, 보안, 안정성에 큰 영향을 준다.



# 공식 Node.js 이미지 (버전 고정)
FROM node:20-slim

# 공식 Python 이미지 (Alpine 기반 - 더 가벼움)
FROM python:3.11-alpine

#절대 피해야 할 방식
FROM node:latest # <- 버전이 언제 바뀔지 모름!



-slim 변형

불필요한 패키지를 제거한 경량 버전. 일반 앱에 권장

-alpine 변형

Alpine Linux 기반. 매우 가볍지만 일부 네이티브 모듈 호환성 주의

-버전 고정

20, 3.11처럼 명확한 버전을 지정해 재현성 확보



latest를 남발하면 안되는 이유

latest 태그는 "가장 최신 버전"을 의미하지만, *언제 무엇으로 바뀔지 예측할 수 없다. * 오늘 빌드와 내일 빌드가 서로 다른 결과를 낼 수 있다.

재현성 파괴

같은 Dockerfile로 빌드해도 시점에 따라 다른 이미지가 나온다.

팀 불일치

팀원마다 다른 버전의 이미지를 사용하게 된다.

CI/CD 장애

자동화 파이프라인에서 예상치 못한 빌드 실패 발생 가능

💡 규칙: Dockerfile에서 베이스 이미지 태그는 항상 node:20-slim 처럼 구체적인 버전으로 고정하기



WORKDIR - 작업 디렉터리 지정

WORKDIR 은 이후 모든 RUN, COPY, CMD 명령의 기준 경로 를 설정한다. 디렉터리가 없으면 자동으로 생성된다.



FROM node:20-slim

WORKDIR/app  # 이후 모든 명령은 /app 기준으로 실행
RUN npm instlal  # /app에서 실행됨
CMD ["node", "server.js"]  # /app/server.js를 실행



WORKDIR을 쓰면

  • 명령 경로가 명확해짐
  • 예측 가능한 구조
  • /app, /user/src/app 등이 관례적 경로

WORKDIR 없이 쓰면

  • 파일이 루트(/)에 흩어짐
  • 다른 시스템 파일과 충돌 가능
  • 디버깅이 어려워짐



COPY - 파일 복사

COPY 는 빌드 컨텍스트의 파일을 이미지 안으로 복사한다. 무엇을, 언제 복사하느냐가 캐시 효율에 큰 영향을 준다.



# 형식: COPY<호스트 경로> <이미지 경로>

# 패키지 파일만 먼저 복사 (캐시 최적화)
COPY package.json package-lock.json ./
RUN npm install

# 소스 파일은 그 다음에 복사
COPY src/ ./src/
COPY public/ ./public/



→ 패키지 파일 먼저package.json 만 바뀌지 않으면 npm install 레이어가 캐시에서 재사용 됨→ 소스 파일은 나중에 코드를 자주 수정해도 의존성 설치 단계는 다시 실행되지 않음



RUN - 빌드 시 실행 명령



# Node.js - 의존성 설치
RUN npm ci --only=production

#Python - 패키지 설치
RUN pip install --no-cache-dir-r requirements.txt

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



💡 *팁: 여러 관련 명령은 && 로 연결해 하나의 RUN 으로 합치면 레이어 수가 줄어들고 임시 파일도 함께 정리할 수 있다. *



ENV - 환경 변수 정의

ENV 로 정의한 환경 변수는 빌드 시점과 컨테이너 실행 시점 모두에서 사용된다.



# 올바른 ENV 사용 - 일반 설정값
ENV NODE_ENV=production
ENV PORT=3000
ENV APP_HOME=/app

# 절대 금지 - 민감 정보 하드코딩!
ENV DB_PASSWORD=mysecret123 # <- 이미지에 그대로 남음
ENV API_KEY=sk-abe123  # <- 위험!



ENV에 넣어도 되는 것

NODE_ENV, PORT, 앱 동작 모드 등 비민감 설정값

ENV에 절대 넣으면 안되는 것

DB 비밀번호, API 키, 토큰 - 런타임에 외부에서 주입해야 함



EXPOSE와 CMD

EXPOSE 는 컨테이너가 사용하는 포트를 문서로 표현 합니다. 실제로 포트를 열지는 않는다. 포트를 여는 것은 docker run -p 의 역할이다.



EXPOSE 3000 # 앱이 3000번 포트 사용을 명시
EXPOSE 8080



💡 *EXPOSE는 개발자와 Docker 도구에 "이 컨테이너는이 포트를 씁니다" 라는 힌트를 주는 역할이다. *



Node.js 앱 Dockerfile - 완성 예시

team-board backend(Node.js) 기준의 실제 Dockerfile이다.



# 1. 베이스 이미지 - 버전 고정
FROM node:20-slim

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

# 3. 패키지 파일 먼저 복사 -> 의존성 캐시 활용
COPY package.json package-lock.json ./
RUN npm ci --only=production

# 4. 소스 코드 복사 (자주 바뀌므로 나중에)
COPY src/ ./src/

# 5. 환경 변수 설정
ENV NODE_ENV=production
ENV PORT=3000

# 6. 포트 문서화
EXPOSE 3000

# 7. 컨테이너 시작 명령
CMD ["node", "src/server.js"]




Python 앱 Dockerfile - 완성 예시

Python(Flask/FastAPI) 기반 앱의 기본 Dockerfil이다. Node.js와 구조는 같지만 의존성 설치 방식이 다르다.



# 1. 베이스 이미지
FROM python:3.11-slim

# 2. 작업 디렉터리
WORKDIR /app

# 3. 의존성 파일 먼저 복사 -> pip install 캐시 활용
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# 4. 소스 코드 복사
COPY ..

# 5. 환경 변수
ENV PYTHONUNBUFFERED=1
ENV PORT=8000

# 6. 포트 문서화
EXPOSE 8000

# 7. 실행 명령
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]




Docker build 명령 사용법

Dockerfile을 작성했다면 docker build 로 이미지를 만든다.



# 기본 형식
docker build -t<이미지명>:<태그><빌드 컨텍스트 경로>

# 예시: 현재 디렉터리를 컨텍스트로 team-board 이미지 빌드
docker build -t team-board-backend;latest.

# 태그를 붙여 버전 관리
docker build -t team-board-backend:v1.0 .

# Dockerfile이 기본 위치가 아닐 때
docker build -f docker/Dockerfile.prod -t team-board-backend:prod .



-t

이미지에 이름과 태그를 붙인다. 나중에 실행 관리가 편해짐

.

현재 디렉터리를 빌드 컨텍스트로 지정

-f

Dockerfile 경로를 직정 지정. 멀티 환경 구성 시 유용



빌드 후 확인하기

빌드가 완료된 후에는 이미지 목로과 실제 실행으로 결과를 확인한다.



# 1. 이미지 목록 확인
docker images
# 출력 예시:
# REPOSITORY           TAG      IMAGE ID      SIZE
# team-board-backend   latest   a1b2c3d4e5f6  312MB

# 2. 컨테이너로 실행해 확인
docker run -p 3000:3000 team-board-backend:latest

# 3. 이미지 상세 정보 확인
docker inspect team-board-backend:latest



💡 이미지가 만들어졌다면 docker run 으로 실제 앱이 정상 동작하는지까지 꼭 확인한다. 빌드 성공 \neq 앱 동작 성공이다.



캐시가 작동하는 원리

Docker는 빌드 시 각 레이어의 내용이 이전과 동일하게 캐시된 결과를 재사용 한다. 레이어가 변경되면 그 이후 모든 레이어가 다시 빌드된다.



FROM node:20-slim   # 캐시 HIT (변경 없음)
WORKDIR /app   # 캐시 HIT
COPY paackage*.json ./   # 캐시 HIT (package.json 그대로)
RUN npm install   # 캐시 HIT <- 핵심!
COPY src/ ./src/   # 캐시 MISS (코드 수정됨)
CMD ["node", "src/server.js"]   # 캐시 MISS (이전 레이어 변경)



💡 위 예시에서 소스 코드를 수정해도 npm install 은 캐시에서 재사용된다. 빌드 시간이 수 분에서 수 초로 줄어든다.



캐시가 깨지는 순간

앞 단계에서 변경이 감지되면 이후 모든 레이어의 캐시가 무효화된다.

파일 내용 변경

COPY 대상 파일이 수정되면 해당 레이어부터 캐시 무효화

지시어 내용 변경

RUN 명령이나 ENV 값이 바뀌면 해당 레이어부터 무효화

베이스 이미지 업데이트

FORM의 이미지가 업데이트되면 전체 레이어 재빌드

💡 *핵심 규칙: 자주 변경되는 파일은 아래쪽에, 거의 변경되지 않는 의존성 설치는 위쪽에 배치한다. *



멀티스테이지 빌드

멀티스테이지 빌드는 하나의 Dockerfile 안에서 여러 단계의 이미지 를 사용하는 방식이다. 빌드 도구는 첫 번째 스테이지에서만 사용하고, 최종 이미지에는 실행에 필요한 결과물만 담는다.

단순 Dockerfile의 한계

빌드 후 최종 이미지에 남는 것

  • Node.js 전체 런타임
  • npm, npx, yarn 등 패키지 관리자
  • Typescript 컴파일러 (tsc)
  • webpack, babel 등 빌드 툴
  • devDependencies 패키지 전체
  • 빌드 캐시와 임시 파일

→ 이미지 크기: 800MB ~ 1GB+

실행에 실제로 필요한 것

  • Node.js 런타임 (또는 정적 파일 서버)
  • 빌드된 JS 파일 (dist/)
  • production 의존성만

→ 이상적인 이미지 크기: 100~200MB

💡 *멀티스테이지 빌드를 쓰면 불필요한 모든 것을 제거하고 실행에 필요한 것만 남길 수 있다. *



멀티스테이지 빌드 구조

멀티스테이지 빌드는 하나의 Dockerfile에서 FROM 을 여러 번 사용한다. 각 FROM 이 새로운 스테이지를 시작한다.



# ==== 스테이지 1: 빌드 =====
FROM node:20-slim AS builder # AS로 스테이지에 이름 부여

WORKDIR /app
COPY package*.json ./
RUN npm ci # devDependenies 포함 설치
COPY ..
RUN npm run build # TypeScript 컴파일 등

# ===== 스테이지 2: 실행 =====
FROM node:20-alpine AS runtime # 새 스테이지 시작 (깨끗한 이미지)

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production # production 의존성만

COPY --from=builder /app/dist ./dist # <- 빌드 결과만 가져옴!

ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/server.js"]




COPY --from 이해하기

COPY --from=<스테이지명> 은 다른 스테이지에서 파일을 가져오는 핵심 지시어이다.

가져오는 것

빌드된 결과물 (dist/, 정적 파일 등) - 실행에 꼭 필요한 것만

남겨두는 것

빌드 도구, devDependencies, 소스 맵, 임시 파일 - 모두 builder 스테이지에만 존재

결과

최종 이미지는 runtime 스테이지만 포함 - builder 레이어는 포함되지 않음



# 스테이지 이름 대신 인덱스 번호 사용도 가능 (이름 권장)
COPY --from=0 /app/dist ./dist # 첫 번째 스테이지에서
COPY --form=builder /app/dist ./dist # 이름으로 (더 명확)




멀티스테이지 빌드 효과

이미지 크기 감소

빌드 도구, devDependencies 제거로 최대 70% 감소

공격 표면 감소

불필요한 패키지 제거로 보안 취약점 노출 최소화

Dockerfile 파일

빌드, 실행 구성이 하나의 파일에서 관리 됨

💡 *실제 사례: Node.js + TypeScript 앱의 경우 단순 Dockerfile 1.2GB → 멀티스테이지 180MB 수준으로 감소하는 경우가 흔하다. *



항상 멀티스테이지가 필요한가?

멀티스테이지 없어도 되는 경우

  • 빌드 단계가 없는 순수 Python 스크립트
  • 매우 간단한 내부 도구나 프로토타입
  • 이미지 크기와 보안이 크게 중요하지 않은 개발 환경

멀티스테이지가 강력히 권장되는 경우

  • TypeScript, Go, Java 등 컴파일이 필요한 언어
  • webpack, vite 등 프론트엔드 빌드 포함
  • 운영 환경 배포용 이미지
  • 보안이 중요한 서비스
  • CI/CD 파이프라인에서 자동 빌드

💡 *실무에서는 거의 배부분의 경우 멀티스테이지를 쓴다. 처음부터 습관화하는 것이 좋다. *



AI가 잘하는 것 VS 자주 놓치는 것

AI가 잘하는 부분

  • 기본 구조 초안 빠르게 생성
  • 레이어 순서 제안
  • 특정 런타임의 관례적 패턴 적용
  • 빌드 오류 원인 설명
  • 멀티스테이지로 리팩터링

AI가 자주 놓치는 부분

  • latest 태그 사용 (버전 고정 미흡)
  • 시크릿 정보를 ENV에 넣는 예시
  • 불필요한 파일 포함 (.env, node_modules)
  • 프로젝트 특수 보안 요구사항
  • 최신 버전 정보 (학습 데이터 기준 시점)



AI 결과물 검증 포인트 4가지

베이스 이미지 버전 고정

FROM node:latest 가 있으면 FROM node:20-slim 으로 수정. 재현성의 기본.

시크릿 하드코딩 여부

ENV에 DB 비밀번호, API 키, 토큰이 들어가 있으면 즉시 제거, 런타임 주입으로 교체

레이어 순서 최적화

소스 복사가 의존성 설치 전에 있으면 순서 교체. 캐시 효율 확인.

불필요한 파일, 패키지

최종 이미지에 빌드 도구, devDependencies, 테스트 파일이 남아 있는지 확인.



마무리

Docker는 처음 보면 명령어가 많아 보여도, 실제로는 이미지와 컨테이너의 차이를 이해하고 실행 흐름을 몇 번 반복해 보면 빠르게 익숙해진다.

처음에는 모든 옵션을 외우기보다, Docker를 직접 띄워 보고 상태를 확인하고 로그를 읽는 흐름까지 연결해서 익히는 것이 훨씬 중요하다.

반응형

관련글 더보기