Docker 입문 가이드 2026: 컨테이너 완벽 이해하기

Docker 입문 가이드 2026: 컨테이너 완벽 이해하기

Docker를 처음부터 배워보세요. 컨테이너, 이미지, Dockerfile, Docker Compose를 실전 예제로 이해합니다.

2026년 3월 17일6분 소요

2026년에 Docker가 중요한 이유

"내 컴퓨터에서는 됩니다"라는 말은 수십 년간 소프트웨어 개발을 괴롭혀 온 문구입니다. Docker가 이 문제를 해결했습니다. 2026년 현재, Docker와 컨테이너 기반 개발은 선택 사항이 아닙니다 — 실제 프로젝트를 진행하는 모든 개발자에게 기본 역량입니다. AWS, Google Cloud, Fly.io, 베어 메탈 서버 등 어디에 배포하든 컨테이너는 보편적인 배포 단위입니다.

이 가이드는 코딩을 할 줄 알지만 아직 컨테이너에 뛰어들지 않은 개발자를 위해 작성되었습니다. 끝까지 읽으면 핵심 개념을 이해하고, Dockerfile 작성 방법을 익히고, 로컬 개발에 Docker Compose를 편안하게 사용할 수 있게 됩니다.

컨테이너 vs 가상 머신

Docker 이전에 팀들은 가상 머신(VM)을 사용해 환경을 격리했습니다. VM은 커널, 드라이버, 시스템 라이브러리, 그리고 애플리케이션을 포함한 전체 운영체제를 포함합니다. 이것은 작동하지만 무겁습니다: VM이 시작하는 데 몇 분이 걸리고 기가바이트의 메모리를 사용할 수 있습니다.

컨테이너는 다르게 작동합니다. 호스트 운영체제의 커널을 공유하지만 애플리케이션의 파일시스템, 프로세스, 네트워킹을 격리합니다. 결과:

기능가상 머신컨테이너
시작 시간분 단위초 단위
크기GB 단위MB 단위
OS 포함 여부전체 OS라이브러리/의존성만
격리 수준강함강함 (단, 주의사항 있음)
리소스 사용높음낮음
이식성좋음탁월

컨테이너는 VM이 아닙니다 — 파일시스템과 네트워크를 격리된 시각으로 보는 프로세스입니다. 이 덕분에 컨테이너는 대부분의 사용 사례에 충분한 격리를 제공하면서 VM보다 훨씬 가볍고 빠릅니다.

Docker 핵심 개념

이미지 (Image)

Docker 이미지는 애플리케이션 실행에 필요한 모든 것을 담은 읽기 전용 템플릿입니다: 코드, 런타임, 라이브러리, 환경 변수, 설정. 이미지를 레시피나 스냅샷처럼 생각하세요.

이미지는 레이어로 빌드됩니다. 베이스 레이어는 Ubuntu나 Alpine Linux일 수 있습니다. 다음 레이어는 Node.js를 추가합니다. 그 다음은 package.json을 추가하고 의존성을 설치합니다. 최종 레이어는 애플리케이션 코드를 추가합니다.

컨테이너 (Container)

컨테이너는 이미지의 실행 중인 인스턴스입니다. 같은 프로그램을 여러 번 실행할 수 있는 것처럼 같은 이미지에서 많은 컨테이너를 만들 수 있습니다. 컨테이너는 임시적입니다 — 컨테이너를 중지하고 제거하면 내부 데이터는 사라집니다 (볼륨을 사용하지 않는 한).

볼륨 (Volume)

볼륨은 컨테이너 수명 주기를 넘어 데이터를 유지하는 메커니즘입니다. 호스트 머신의 볼륨을 컨테이너에 마운트해 데이터베이스 파일, 업로드된 에셋, 개발 코드를 동기화하세요.

레지스트리 (Registry)

Docker Hub는 Docker 이미지의 기본 공개 레지스트리입니다. Node.js, Python, PostgreSQL, Nginx 등 수천 개의 도구에 대한 공식 이미지를 호스팅합니다. 레지스트리에서 이미지를 가져오고(pull), 자신의 이미지를 팀과 공유하거나 프로덕션에 배포하기 위해 push합니다.

Docker 설치하기

docker.com에서 Docker Desktop을 다운로드하세요. Docker 데몬, CLI, Docker Compose가 포함되어 있습니다. 설치 후 작동 확인:

docker --version
# Docker version 26.x.x

docker run hello-world
# hello-world 이미지를 pull하고 실행

첫 번째 Dockerfile: Node.js 앱

간단한 Express.js 애플리케이션을 위한 Dockerfile을 만들어봅시다. 다음 프로젝트 구조를 가정합니다:

my-app/
  src/
    index.js
  package.json
  package-lock.json
  Dockerfile
  .dockerignore

src/index.js 파일:

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.json({ message: 'Docker에서 안녕하세요!', timestamp: new Date().toISOString() });
});

app.listen(PORT, () => {
  console.log(`서버가 포트 ${PORT}에서 실행 중입니다`);
});

Dockerfile:

# 베이스 이미지로 공식 Node.js 20 LTS 이미지 사용
FROM node:20-alpine

# 컨테이너 내부 작업 디렉토리 설정
WORKDIR /app

# 레이어 캐싱 최적화를 위해 패키지 파일 먼저 복사
COPY package*.json ./

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

# 나머지 애플리케이션 소스 복사
COPY src/ ./src/

# 앱이 실행되는 포트 노출
EXPOSE 3000

# 애플리케이션 실행 명령 정의
CMD ["node", "src/index.js"]

.dockerignore 파일 (불필요한 파일이 복사되지 않도록 방지):

node_modules
npm-debug.log
.git
.gitignore
README.md
.env

이미지 빌드 및 실행:

# 이미지 빌드 및 태그 지정
docker build -t my-app:latest .

# 이미지에서 컨테이너 실행
docker run -p 3000:3000 my-app:latest

# http://localhost:3000 방문

-p 3000:3000 플래그는 컴퓨터의 포트 3000을 컨테이너의 포트 3000에 매핑합니다.

로컬 개발을 위한 Docker Compose

docker run 명령으로 개별 컨테이너를 실행하는 것은 금세 번거로워집니다. Docker Compose는 단일 YAML 파일에서 멀티 컨테이너 애플리케이션을 정의할 수 있게 해줍니다.

PostgreSQL 데이터베이스와 Redis를 갖춘 Node.js 앱을 위한 docker-compose.yml:

version: '3.9'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    volumes:
      - ./src:/app/src  # 개발 중 라이브 리로드
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

단 하나의 명령으로 모두 시작:

docker compose up -d        # 분리 모드로 시작
docker compose logs -f app  # 앱 서비스 로그 실시간 확인
docker compose down         # 컨테이너 중지 및 제거
docker compose down -v      # 볼륨도 제거 (데이터 삭제)

필수 Docker 명령어

# 이미지
docker pull node:20-alpine          # 이미지 다운로드
docker images                       # 로컬 이미지 목록
docker rmi my-app:latest            # 이미지 제거
docker image prune                  # 미사용 이미지 제거

# 컨테이너
docker ps                           # 실행 중인 컨테이너 목록
docker ps -a                        # 모든 컨테이너 목록 (중지된 것 포함)
docker stop <container-id>          # 실행 중인 컨테이너 중지
docker rm <container-id>            # 중지된 컨테이너 제거
docker logs <container-id>          # 컨테이너 로그 확인
docker logs -f <container-id>       # 실시간 로그 추적
docker exec -it <container-id> sh   # 컨테이너 내부에서 셸 열기

# 빌드
docker build -t myapp:v1.0 .        # 태그를 붙여 빌드
docker build --no-cache -t myapp .  # 캐시 없이 빌드

# Compose
docker compose up -d                # 서비스를 백그라운드에서 시작
docker compose down                 # 서비스 중지
docker compose ps                   # 서비스 상태 목록
docker compose exec app sh          # 실행 중인 서비스에서 셸 열기

흔한 실수 피하기

1. 컨테이너에서 루트로 실행하기

기본적으로 컨테이너는 루트로 실행됩니다. Dockerfile에 비루트 사용자를 추가하세요:

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

2. .dockerignore 사용 안 하기

.dockerignore 없이는 Docker가 node_modules 디렉토리를 빌드 컨텍스트에 복사해 빌드 속도를 크게 저하시키고 플랫폼 특정 바이너리 문제를 야기할 수 있습니다.

3. 이미지에 시크릿 저장하기

Dockerfile에서 민감한 데이터에 ENV MY_SECRET=value를 절대 사용하지 마세요. 대신 런타임에 시크릿을 전달하세요:

docker run -e DATABASE_URL="$DATABASE_URL" my-app

또는 프로덕션 배포에 Docker 시크릿을 사용하세요.

4. 이미지 레이어 캐싱 무시하기

Dockerfile 명령어를 변경 빈도가 낮은 것에서 높은 것 순으로 정렬하세요. COPY src/ 전에 COPY package*.jsonRUN npm install을 배치해 package.json이 변경되지 않는 한 의존성 설치가 캐시되도록 하세요.

5. 프로덕션에서 latest 태그 사용하기

이미지 버전을 명시적으로 고정하세요. FROM node:20-alpine(FROM node:latest가 아닌)은 재현 가능한 빌드를 보장합니다.

다음 단계

기본기에 익숙해지면 다음을 탐구해보세요:

  • 멀티 스테이지 빌드: 빌드 환경과 런타임 이미지를 분리해 더 작은 프로덕션 이미지 만들기
  • Docker Scout: 이미지의 취약점 스캔
  • Docker Swarm 또는 Kubernetes: 대규모 컨테이너 오케스트레이션
  • GitHub Actions + Docker: CI/CD에서 이미지 빌드 및 푸시 자동화

Docker는 처음에는 복잡하게 느껴지다가 개념이 잡히면 당연하게 느껴지는 도구 중 하나입니다. 앱에 필요한 모든 것을 이식 가능하고 재현 가능한 단위로 패키징한다는 핵심 아이디어는 단순하면서도 강력합니다. 이 가이드의 명령어에 익숙해지는 데 하루를 투자하면, 컨테이너가 제2의 천성처럼 느껴지게 될 것입니다.

관련 글