DevOps·5분 읽기

Docker 컨테이너 입문 가이드: 개념부터 실전까지

Docker의 핵심 개념, Dockerfile 작성법, 이미지 빌드, 컨테이너 관리, Docker Compose 사용법까지 실전 예제와 함께 배우는 Docker 입문 가이드입니다.

공유:

Docker 컨테이너 입문 가이드: 개념부터 실전까지

소프트웨어를 개발하고 배포하는 과정에서 "내 컴퓨터에서는 잘 되는데..."라는 말을 한 번이라도 해본 적이 있다면, Docker가 그 문제를 해결해 줄 수 있습니다. Docker는 현대 소프트웨어 개발에서 빼놓을 수 없는 핵심 도구로, 애플리케이션을 컨테이너라는 독립적인 환경에서 실행할 수 있게 해줍니다. 이 글에서는 Docker의 기본 개념부터 실전 활용법까지 단계별로 알아보겠습니다.

Docker란 무엇인가?

Docker는 컨테이너 기반의 오픈소스 가상화 플랫폼입니다. 기존의 가상 머신(VM)과 달리, Docker 컨테이너는 호스트 운영체제의 커널을 공유하면서 애플리케이션과 그 의존성만을 패키징합니다. 이로 인해 가상 머신보다 훨씬 가볍고 빠르게 실행됩니다.

가상 머신 vs 컨테이너

가상 머신은 하이퍼바이저 위에 게스트 운영체제 전체를 설치하고 그 위에서 애플리케이션을 실행합니다. 반면 컨테이너는 호스트 OS 위에서 Docker 엔진을 통해 격리된 프로세스로 실행됩니다. 이 차이로 인해 컨테이너는 다음과 같은 장점을 가집니다.

  • 빠른 시작 시간: VM은 부팅에 수 분이 걸리지만, 컨테이너는 수 초 내에 시작됩니다.
  • 적은 리소스 사용: OS 전체를 포함하지 않으므로 메모리와 디스크 사용량이 현저히 적습니다.
  • 높은 이식성: 동일한 이미지를 어디서든 동일하게 실행할 수 있습니다.
  • 효율적인 확장: 컨테이너 단위로 빠르게 스케일 아웃이 가능합니다.

Docker의 핵심 개념

Docker를 이해하려면 세 가지 핵심 개념을 알아야 합니다.

1. 이미지(Image)

Docker 이미지는 컨테이너를 생성하기 위한 읽기 전용 템플릿입니다. 운영체제, 런타임, 라이브러리, 애플리케이션 코드 등 실행에 필요한 모든 것이 포함되어 있습니다. 이미지는 레이어(Layer) 구조로 되어 있어, 변경된 부분만 추가 레이어로 저장됩니다. 이 덕분에 디스크 공간을 효율적으로 사용하고, 빌드 캐시를 활용해 빌드 속도를 높일 수 있습니다.

2. 컨테이너(Container)

컨테이너는 이미지를 기반으로 생성된 실행 가능한 인스턴스입니다. 이미지가 클래스라면, 컨테이너는 그 클래스의 인스턴스라고 생각할 수 있습니다. 각 컨테이너는 독립된 파일 시스템, 네트워크, 프로세스 공간을 가지며, 서로 격리되어 실행됩니다.

3. 레지스트리(Registry)

Docker 레지스트리는 이미지를 저장하고 공유하는 저장소입니다. Docker Hub가 가장 대표적인 공개 레지스트리이며, 회사 내부에서 사용하는 프라이빗 레지스트리도 구축할 수 있습니다. AWS ECR, Google Container Registry, GitHub Container Registry 등의 클라우드 서비스도 널리 사용됩니다.

Docker 설치하기

Docker는 Windows, macOS, Linux 모든 플랫폼에서 사용할 수 있습니다. Docker Desktop을 설치하면 Docker 엔진과 함께 Docker Compose, Docker CLI 등 필요한 도구가 모두 포함됩니다.

설치 후 다음 명령어로 정상 설치 여부를 확인합니다.

docker --version
# Docker version 24.0.7, build afdd53b

docker run hello-world
# Hello from Docker! 메시지가 출력되면 성공

Dockerfile 작성법

Dockerfile은 Docker 이미지를 빌드하기 위한 명세서입니다. 각 명령어가 이미지의 레이어를 구성하며, 위에서 아래로 순차적으로 실행됩니다. 다음은 Node.js 애플리케이션을 위한 Dockerfile 예제입니다.

# 1. 베이스 이미지 지정
FROM node:20-alpine

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

# 3. 의존성 파일 복사 (캐시 최적화)
COPY package*.json ./

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

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

# 6. 포트 노출 선언
EXPOSE 3000

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

Dockerfile 주요 명령어 설명

  • FROM: 베이스 이미지를 지정합니다. 모든 Dockerfile은 FROM으로 시작해야 합니다. alpine 태그는 경량 리눅스 배포판을 사용하여 이미지 크기를 줄입니다.
  • WORKDIR: 이후 명령어가 실행될 작업 디렉토리를 설정합니다. 디렉토리가 없으면 자동으로 생성됩니다.
  • COPY: 호스트의 파일을 이미지 내부로 복사합니다. package*.json을 먼저 복사하는 이유는 빌드 캐시를 활용하기 위해서입니다.
  • RUN: 이미지 빌드 시점에 명령어를 실행합니다. 패키지 설치, 빌드 등에 사용합니다.
  • EXPOSE: 컨테이너가 사용하는 포트를 문서화합니다. 실제 포트 매핑은 docker run-p 옵션으로 합니다.
  • CMD: 컨테이너 시작 시 실행할 기본 명령어를 지정합니다. Dockerfile에서 하나만 사용할 수 있습니다.

Dockerfile 최적화 팁

이미지 크기를 줄이고 빌드 속도를 높이려면 다음 사항을 고려하세요.

  1. 경량 베이스 이미지 사용: node:20-alpine처럼 alpine 기반 이미지를 사용합니다.
  2. 멀티스테이지 빌드 활용: 빌드 환경과 실행 환경을 분리합니다.
  3. 레이어 캐시 최적화: 변경이 적은 레이어를 먼저 배치합니다.
  4. .dockerignore 활용: node_modules, .git 등 불필요한 파일을 제외합니다.

다음은 멀티스테이지 빌드 예제입니다.

# 빌드 스테이지
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 실행 스테이지
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

이미지 빌드와 관리

Dockerfile을 작성했다면 이미지를 빌드하고 관리하는 방법을 알아보겠습니다.

# 이미지 빌드 (-t 옵션으로 태그 지정)
docker build -t my-app:1.0 .

# 빌드된 이미지 목록 확인
docker images

# 이미지 태그 추가
docker tag my-app:1.0 my-app:latest

# 이미지 삭제
docker rmi my-app:1.0

# 사용하지 않는 이미지 정리
docker image prune

이미지를 레지스트리에 푸시하려면 먼저 로그인한 후 푸시 명령어를 실행합니다.

# Docker Hub 로그인
docker login

# 이미지에 레지스트리 경로 태그 추가
docker tag my-app:1.0 username/my-app:1.0

# 이미지 푸시
docker push username/my-app:1.0

컨테이너 실행과 관리

이미지가 준비되었으면 컨테이너를 실행하고 관리하는 방법을 살펴보겠습니다.

# 컨테이너 실행 (포그라운드)
docker run -p 3000:3000 my-app:1.0

백그라운드 실행 (-d 옵션)

docker run -d -p 3000:3000 --name my-container my-app:1.0

환경변수 전달

docker run -d -p 3000:3000 -e NODE_ENV=production my-app:1.0

볼륨 마운트 (데이터 영속성)

docker run -d -p 5432:5432 -v pgdata:/var/lib/postgresql/data postgres:16

실행 중인 컨테이너 확인

docker ps

모든 컨테이너 확인 (중지된 것 포함)

docker ps -a

컨테이너 로그 확인

docker logs my-container docker logs -f my-container # 실시간 로그

실행 중인 컨테이너에 접속

docker exec -it my-container /bin/sh

컨테이너 중지 및 삭제

docker stop my-container docker rm my-container


### 볼륨(Volume) 관리

컨테이너는 기본적으로 상태가 없습니다(stateless). 컨테이너를 삭제하면 내부 데이터도 함께 사라집니다. 데이터를 영속적으로 보관하려면 볼륨을 사용해야 합니다.

볼륨 생성

docker volume create my-data

볼륨 목록 확인

docker volume ls

볼륨을 사용하여 컨테이너 실행

docker run -d -v my-data:/app/data my-app:1.0

볼륨 삭제

docker volume rm my-data


## Docker Compose 기본 사용법

실제 애플리케이션은 여러 서비스(웹 서버, 데이터베이스, 캐시 등)로 구성됩니다. Docker Compose를 사용하면 여러 컨테이너를 하나의 YAML 파일로 정의하고 동시에 관리할 수 있습니다.

다음은 Next.js 앱과 PostgreSQL 데이터베이스, Redis 캐시를 함께 구성하는 `docker-compose.yml` 예제입니다.

version: '3.8'

services: web: build: context: . dockerfile: Dockerfile ports: - "3000:3000" environment: - DATABASE_URL=postgresql://user:password@db:5432/myapp - REDIS_URL=redis://cache:6379 depends_on: - db - cache restart: unless-stopped

db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: myapp volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432"

cache: image: redis:7-alpine ports: - "6379:6379"

volumes: pgdata:


### Docker Compose 주요 명령어

모든 서비스 시작 (백그라운드)

docker compose up -d

서비스 상태 확인

docker compose ps

특정 서비스 로그 확인

docker compose logs web docker compose logs -f db # 실시간

모든 서비스 중지

docker compose down

볼륨까지 함께 삭제

docker compose down -v

이미지 재빌드 후 시작

docker compose up -d --build

특정 서비스만 재시작

docker compose restart web


## Docker 네트워크

Docker는 기본적으로 브릿지 네트워크를 생성하여 컨테이너 간 통신을 가능하게 합니다. Docker Compose를 사용하면 자동으로 프로젝트별 네트워크가 생성되어, 같은 Compose 파일 내의 서비스들은 서비스 이름으로 서로 통신할 수 있습니다.

네트워크 목록 확인

docker network ls

커스텀 네트워크 생성

docker network create my-network

네트워크에 연결하여 컨테이너 실행

docker run -d --network my-network --name api my-api:1.0 docker run -d --network my-network --name web my-web:1.0


커스텀 네트워크를 사용하면 컨테이너 이름으로 DNS 해석이 가능하여, IP 주소 대신 이름으로 통신할 수 있습니다. 예를 들어 `web` 컨테이너에서 `api` 컨테이너로 `http://api:8080`과 같이 접근할 수 있습니다.

## 실전 팁과 베스트 프랙티스

Docker를 실무에서 효과적으로 사용하기 위한 베스트 프랙티스를 정리합니다.

### 보안 관련

- **루트 사용자로 실행하지 마세요**: Dockerfile에서 `USER` 명령어로 비루트 사용자를 지정합니다.
- **최소 권한 원칙**: 필요한 패키지와 도구만 설치합니다.
- **시크릿 관리**: 환경변수나 Docker Secrets를 사용하고, Dockerfile에 비밀번호를 하드코딩하지 마세요.
- **이미지 스캔**: `docker scout` 또는 Trivy 같은 도구로 취약점을 검사합니다.

### 성능 관련

- **.dockerignore 파일을 반드시 작성하세요**: 빌드 컨텍스트 크기를 줄여 빌드 속도를 높입니다.
- **레이어 순서를 최적화하세요**: 변경이 적은 명령어를 위에 배치하여 캐시 히트율을 높입니다.
- **멀티스테이지 빌드를 활용하세요**: 최종 이미지에 빌드 도구를 포함하지 않습니다.

### 운영 관련

- **헬스체크를 설정하세요**: `HEALTHCHECK` 명령어로 컨테이너 상태를 모니터링합니다.
- **로그를 표준 출력으로 보내세요**: 파일이 아닌 stdout/stderr로 로그를 출력하여 `docker logs`로 확인할 수 있게 합니다.
- **재시작 정책을 설정하세요**: `restart: unless-stopped`로 예기치 않은 종료 시 자동 재시작되게 합니다.

헬스체크 예제

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1


## .dockerignore 파일 예제

빌드 컨텍스트에서 불필요한 파일을 제외하는 `.dockerignore` 파일 예제입니다.

node_modules .git .gitignore *.md .env .env.local dist .next coverage .vscode .idea


## 마무리

Docker는 현대 소프트웨어 개발과 배포의 핵심 도구입니다. 이 글에서 다룬 내용을 정리하면 다음과 같습니다.

1. **Docker의 개념**: 컨테이너 기반 가상화로 일관된 실행 환경을 제공합니다.
2. **Dockerfile**: 이미지를 빌드하기 위한 명세서를 작성하는 방법을 배웠습니다.
3. **이미지와 컨테이너 관리**: 빌드, 실행, 모니터링, 정리하는 명령어를 익혔습니다.
4. **Docker Compose**: 멀티 컨테이너 애플리케이션을 정의하고 관리하는 방법을 알아보았습니다.
5. **베스트 프랙티스**: 보안, 성능, 운영 측면에서 실전 팁을 정리했습니다.

Docker를 처음 접했다면 간단한 프로젝트부터 컨테이너화해보는 것을 추천합니다. 개발 환경을 Docker Compose로 구성하면 팀원 간 환경 차이로 인한 문제를 줄이고, 새로운 팀원의 온보딩 시간도 크게 단축할 수 있습니다. 더 나아가 Kubernetes와 같은 오케스트레이션 도구로 확장하면 대규모 서비스 운영에도 Docker가 강력한 기반이 됩니다.
#Docker#컨테이너#DevOps#배포