개발자를 위한 Docker: 이미지, 컨테이너, Compose
여러분의 컴퓨터에서는 잘 작동하는 코드를 작성했지만, 동료가 실행하려고 하면 누락된 의존성, 버전 충돌, 또는 알 수 없는 오류에 부딪힙니다. Docker는 애플리케이션과 그 환경을 하나의 휴대 가능한 단위로 패키징하여 이 문제를 해결합니다. 이 가이드에서는 Docker의 기본 개념인 이미지, 컨테이너, Compose를 배우고, 간단한 웹 앱을 컨테이너화하는 실용적인 단계를 알아봅니다.
Docker란 무엇인가?
Docker는 컨테이너에서 애플리케이션을 빌드, 실행, 공유하기 위한 플랫폼입니다. 컨테이너는 소프트웨어를 실행하는 데 필요한 모든 것(코드, 런타임, 시스템 도구, 라이브러리, 설정)을 포함하는 경량의 독립 실행형 패키지입니다. 컨테이너는 애플리케이션을 서로 그리고 호스트 시스템으로부터 격리하여 개발, 테스트, 프로덕션 전반에 걸쳐 일관성을 보장합니다.
이미지 vs 컨테이너: 설계도와 실행 인스턴스
이미지는 컨테이너를 정의하는 읽기 전용 템플릿입니다. 객체 지향 프로그래밍의 클래스와 같습니다. 컨테이너는 이미지의 실행 가능한 인스턴스로, 객체와 같습니다. 동일한 이미지에서 여러 컨테이너를 생성할 수 있습니다.
이미지는 지침이 담긴 텍스트 파일인 Dockerfile에서 빌드됩니다. 컨테이너는 docker run으로 시작됩니다.
주요 차이점
| 이미지 | 컨테이너 |
|---|---|
| 읽기 전용 템플릿 | 실행 가능한 인스턴스 |
docker build로 빌드 | docker run으로 시작 |
| 레지스트리(예: Docker Hub)에 저장 | 호스트에서 실행 |
| 계층화된 파일 시스템 | 상단에 쓰기 가능한 계층 |
첫 번째 이미지 빌드하기
간단한 Node.js 앱을 컨테이너화해 보겠습니다. Dockerfile이라는 파일을 만드세요:
# Use an official Node.js runtime as a parent image
FROM node:18-alpine
# Set the working directory
WORKDIR /app
# Copy package.json and install dependencies
COPY package*.json ./
RUN npm install
# Copy the rest of the application
COPY . .
# Expose the port the app runs on
EXPOSE 3000
# Define the command to run the app
CMD ["node", "server.js"]
태그를 지정하여 이미지를 빌드합니다:
docker build -t my-node-app .
-t 플래그는 이미지의 이름과 태그를 지정합니다. 끝의 .는 빌드 컨텍스트(현재 디렉토리)를 지정합니다.
컨테이너 실행하기
이미지에서 컨테이너를 시작합니다:
docker run -p 3000:3000 -d --name my-app my-node-app
-p 3000:3000: 호스트의 포트 3000을 컨테이너의 포트 3000에 매핑합니다.-d: 컨테이너를 분리 모드(백그라운드)로 실행합니다.--name my-app: 쉽게 참조할 수 있도록 컨테이너에 이름을 할당합니다.
docker ps로 실행 중인 컨테이너를 확인하세요. docker stop my-app으로 컨테이너를 중지합니다.
볼륨으로 데이터 관리하기
컨테이너는 일시적이므로 컨테이너가 제거되면 내부 데이터가 손실됩니다. 데이터를 유지하려면 볼륨을 사용하세요. 예를 들어 데이터베이스 데이터를 유지하려면:
docker run -v my-db-data:/var/lib/mysql -d mysql:8
이렇게 하면 컨테이너 재시작 및 제거 후에도 유지되는 명명된 볼륨 my-db-data가 생성됩니다.
Docker Compose로 다중 컨테이너 앱 간소화하기
실제 애플리케이션은 종종 여러 서비스(웹 서버, 데이터베이스, 캐시 등)가 필요합니다. Docker Compose를 사용하면 단일 YAML 파일로 다중 컨테이너 앱을 정의하고 실행할 수 있습니다.
docker-compose.yml을 만드세요:
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=mydb
volumes:
postgres_data:
다음 명령으로 모든 것을 실행합니다:
docker compose up -d
Compose는 웹 이미지를 빌드하고, 데이터베이스를 시작하며, 공유 네트워크에서 이들을 연결합니다. depends_on은 데이터베이스가 웹 서비스보다 먼저 시작되도록 보장합니다.
필수 Docker 명령어
docker ps -a: 모든 컨테이너(중지된 것 포함)를 나열합니다.docker images: 이미지를 나열합니다.docker logs my-app: 컨테이너 로그를 봅니다.docker exec -it my-app sh: 실행 중인 컨테이너 내부에서 셸을 엽니다.docker compose down: Compose 파일에 정의된 컨테이너, 네트워크, 볼륨을 중지하고 제거합니다.
개발을 위한 모범 사례
- .dockerignore 사용: 빌드 컨텍스트에서 파일(예: node_modules)을 제외하여 빌드 속도를 높입니다.
- 다단계 빌드 활용: 프로덕션 이미지를 작게 유지합니다. 예를 들어 한 단계에서 앱을 빌드하고 필요한 아티팩트만 슬림 런타임 이미지에 복사합니다.
- 버전 고정: 재현성을 위해 Dockerfile에서
node:latest대신node:18-alpine과 같이 버전을 고정하세요. - 비루트 사용자로 실행: 보안을 위해 파일을 복사한 후
USER node를 추가하세요.
FAQ
Docker 이미지와 컨테이너의 차이점은 무엇인가요?
이미지는 애플리케이션과 그 환경을 정의하는 읽기 전용 템플릿입니다. 컨테이너는 해당 이미지의 실행 인스턴스입니다. 동일한 이미지에서 여러 컨테이너를 생성할 수 있습니다.
Docker에서 데이터를 유지하려면 어떻게 해야 하나요?
볼륨을 사용하세요. 명명된 볼륨(예: docker run -v my-vol:/data)은 컨테이너의 파일 시스템 외부에 저장되며 컨테이너 재시작 및 제거 후에도 유지됩니다.
Docker Compose는 언제 사용해야 하나요?
애플리케이션이 함께 실행해야 하는 여러 서비스(예: 웹 + 데이터베이스)로 구성된 경우 Compose를 사용하세요. 개발 및 테스트를 위한 오케스트레이션을 단순화합니다.
다음 프로젝트를 컨테이너화할 준비가 되셨나요? 간단한 Dockerfile을 작성하고 첫 컨테이너를 실행해 보세요. 더 많은 개발자 도구를 원하시면 TryQuickToolBox의 JSON Formatter를 확인하여 컨테이너화된 서비스를 테스트할 때 API 응답을 검증해 보세요.