Docker 기초 — 컨테이너의 개념부터 실전 배포까지

Docker가 해결하는 문제

“내 컴퓨터에서는 되는데요?” — 개발자라면 한 번쯤 들어본 말입니다. 개발 환경과 운영 환경의 차이(OS, 라이브러리 버전, 설정)가 원인입니다. Docker는 애플리케이션과 실행 환경을 컨테이너라는 격리된 패키지로 묶어 이 문제를 해결합니다.

가상머신(VM)과 달리 컨테이너는 OS 커널을 공유하므로 훨씬 가볍습니다.

항목가상머신 (VM)컨테이너 (Docker)
시작 시간수십 초~수 분수 초 이내
메모리GB 단위MB 단위
격리 수준OS 전체 격리프로세스 수준 격리
이미지 크기수 GB수십~수백 MB
이식성하이퍼바이저 의존어디서든 동일하게 실행

핵심 개념 3가지

Docker를 이해하려면 이미지, 컨테이너, 레지스트리 3가지만 알면 됩니다.

  • 이미지(Image): 앱 실행에 필요한 모든 것(코드, 런타임, 라이브러리)을 담은 읽기 전용 템플릿. 설계도에 해당합니다.
  • 컨테이너(Container): 이미지를 기반으로 실행된 인스턴스. 설계도로 만든 실제 건물입니다. 여러 개를 동시에 실행할 수 있습니다.
  • 레지스트리(Registry): 이미지를 저장/공유하는 저장소. Docker Hub가 대표적입니다.

필수 명령어

이미지 관리

Docker Hub에서 이미지를 다운로드하고, 로컬에서 관리하는 명령어입니다.

# 이미지 다운로드 (태그를 생략하면 latest)
docker pull nginx:alpine

# 로컬 이미지 목록 확인
docker images
# REPOSITORY   TAG       IMAGE ID       SIZE
# nginx        alpine    a2573b4e3f72   43MB

# 이미지 삭제
docker rmi nginx:alpine

# 이미지 상세 정보 (레이어, 환경변수 등)
docker inspect nginx:alpine

alpine 태그는 경량 Alpine Linux 기반 이미지입니다. 같은 앱이라도 node:22(약 350MB)보다 node:22-alpine(약 50MB)이 훨씬 작습니다. 프로덕션에서는 가능한 alpine 태그를 사용하세요.

컨테이너 실행과 관리

이미지를 컨테이너로 실행하고, 상태를 관리하는 명령어입니다.

# 백그라운드(-d)로 실행, 이름 지정, 포트 매핑(호스트:컨테이너)
docker run -d --name my-nginx -p 8080:80 nginx:alpine
# → http://localhost:8080 에서 접속 가능

# 실행 중인 컨테이너 목록
docker ps
# CONTAINER ID   IMAGE          STATUS    PORTS                  NAMES
# a1b2c3d4e5f6   nginx:alpine   Up 5s     0.0.0.0:8080->80/tcp   my-nginx

# 모든 컨테이너 (중지된 것 포함)
docker ps -a

# 중지 / 재시작 / 삭제
docker stop my-nginx
docker start my-nginx
docker rm my-nginx          # 중지 상태에서만 삭제 가능
docker rm -f my-nginx       # 강제 삭제 (실행 중이어도)

-p 8080:80에서 앞이 호스트 포트, 뒤가 컨테이너 포트입니다. 실수하기 쉬우니 호스트:컨테이너 순서를 기억하세요.

디버깅 명령어

컨테이너 내부 확인과 로그 조회에 사용합니다.

# 실시간 로그 스트리밍
docker logs -f my-nginx

# 최근 50줄만 확인
docker logs --tail 50 my-nginx

# 컨테이너 내부 쉘 접속
docker exec -it my-nginx sh
# → 컨테이너 안에서 ls, cat 등 명령어 실행 가능
# → exit 으로 빠져나오기

# 컨테이너 리소스 사용량 모니터링
docker stats my-nginx
# CPU %   MEM USAGE   NET I/O   BLOCK I/O
# 0.00%   2.3MiB      1kB/0B    0B/0B

Dockerfile 작성법

Dockerfile은 이미지를 만드는 레시피입니다. Node.js 앱을 예시로 각 명령어를 설명합니다.

# 1. 베이스 이미지 지정 (가능하면 alpine)
FROM node:22-alpine

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

# 3. 의존성 파일만 먼저 복사 → 캐시 최적화 핵심
COPY package.json package-lock.json ./

# 4. 의존성 설치 (devDependencies 제외)
RUN npm ci --omit=dev

# 5. 소스 코드 복사 (3번 이후에 해야 캐시가 유효)
COPY . .

# 6. 컨테이너가 사용할 포트 문서화
EXPOSE 3000

# 7. 실행 명령어
CMD ["node", "server.js"]

3~5번 순서가 중요합니다. package.json이 변경되지 않으면 npm ci 레이어가 캐시되어 빌드 속도가 크게 빨라집니다. 소스 코드만 바뀌면 5번부터만 재실행됩니다.

빌드와 실행:

# 이미지 빌드 (-t: 태그 지정, .: 현재 디렉토리)
docker build -t my-app:1.0 .

# 빌드한 이미지로 실행
docker run -d --name my-app -p 3000:3000 my-app:1.0

.dockerignore 파일로 불필요한 파일을 빌드 컨텍스트에서 제외하세요.

node_modules
.git
.env
dist
*.md

Docker Compose

여러 컨테이너를 한 번에 정의하고 실행하는 도구입니다. 웹 앱 + DB처럼 연관된 서비스를 함께 관리할 때 사용합니다.

# docker-compose.yml (또는 compose.yml)
services:
  web:
    build: .                          # 현재 디렉토리의 Dockerfile로 빌드
    ports:
      - "3000:3000"                   # 호스트:컨테이너 포트 매핑
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:secret@db:5432/mydb
    depends_on:
      - db                            # db가 먼저 시작되도록 의존성 지정
    restart: unless-stopped           # 비정상 종료 시 자동 재시작

  db:
    image: postgres:16-alpine         # 공식 이미지 사용
    volumes:
      - db-data:/var/lib/postgresql/data   # 데이터 영속화
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=mydb

volumes:
  db-data:                            # 이름 있는 볼륨 (컨테이너 삭제해도 유지)

Compose 명령어:

# 모든 서비스 빌드 + 백그라운드 실행
docker compose up -d --build

# 서비스 상태 확인
docker compose ps

# 로그 확인 (특정 서비스만)
docker compose logs -f web

# 모든 서비스 중지 + 컨테이너 삭제
docker compose down

# 볼륨까지 삭제 (DB 데이터 포함 — 주의!)
docker compose down -v

depends_on은 시작 순서만 보장합니다. DB가 “준비 완료”될 때까지 기다리지는 않습니다. 앱에서 DB 연결 재시도 로직을 구현하거나, healthcheck를 설정하세요.

실전 팁

상황명령어설명
디스크 정리docker system prune -a미사용 이미지/컨테이너/네트워크 전부 삭제
빌드 캐시 확인docker builder prune빌드 캐시만 삭제
환경변수 파일docker run --env-file .env.env 파일의 변수를 컨테이너에 주입
볼륨 마운트docker run -v $(pwd):/app호스트 디렉토리를 컨테이너에 마운트 (개발용)
멀티스테이지 빌드FROM ... AS builder빌드 도구를 최종 이미지에서 제외하여 크기 축소

프로덕션에서는 latest 태그 대신 구체적인 버전 태그(예: node:22.14-alpine)를 사용하세요. latest는 언제 바뀔지 모르므로, 동일한 Dockerfile로 빌드해도 다른 결과가 나올 수 있습니다.

이 글이 도움이 되었나요?