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

sammy·2026년 3월 8일

Dev Knowledge

목록 보기
12/12

"내 컴퓨터에서는 잘 되는데요..."
Docker를 사용하면 이 말을 더 이상 하지 않아도 됩니다.


📌 들어가며

개발자라면 한 번쯤 이런 경험이 있을 겁니다.

  • 로컬에서 잘 돌아가던 코드가 서버에서는 안 돌아감
  • 팀원과 개발 환경이 달라서 버그 재현이 안 됨
  • 새 프로젝트마다 환경 설정에 반나절 소요
  • Node.js 14, 16, 18... 여러 버전을 동시에 써야 하는 상황

이 모든 문제를 해결해주는 것이 바로 Docker입니다.

이 글에서는 Docker가 무엇인지, 왜 필요한지, 그리고 실제로 어떻게 사용하는지까지 정리해보도록 하겠습니다.


🐳 Docker란?

한 줄 정의

Docker는 애플리케이션을 컨테이너라는 격리된 환경에서 실행할 수 있게 해주는 플랫폼입니다.

역사와 배경

📅 2013년: Solomon Hykes가 PyCon에서 Docker 첫 공개
📅 2014년: Docker 1.0 출시
📅 2025년: Docker 28.x 버전, 클라우드 배포의 사실상 표준

Docker는 Go 언어로 개발되었으며, 현재 컨테이너 기반 개발과 배포의 업계 표준으로 자리잡았습니다.

Docker를 쉽게 이해하기: 해운 컨테이너 비유

해운업Docker
물건을 표준 컨테이너에 담음앱을 Docker 컨테이너에 담음
어떤 배에서든 운송 가능어떤 서버에서든 실행 가능
내용물이 뭔지 몰라도 됨어떤 언어/런타임인지 몰라도 됨

🤔 왜 Docker가 필요한가?

기존 방식의 문제점

환경 불일치 문제:

  • 개발/테스트/운영 환경이 다름
  • "내 컴퓨터에서는 되는데..." 현상
  • 환경 설정에 많은 시간 소요

Docker로 해결

Docker의 해결책:

  • 앱 + 환경을 하나의 패키지로 묶음
  • 어디서든 동일하게 실행
  • 환경 설정이 코드로 관리됨

🆚 VM vs 컨테이너

가상머신(VM)의 구조

컨테이너의 구조

비교표

항목가상머신 (VM)컨테이너 (Docker)
시작 시간수 분수 초
용량GB 단위MB 단위
성능오버헤드 있음네이티브에 가까움
격리 수준완전 격리 (별도 OS)프로세스 수준 격리
OS각각 별도 OS 필요호스트 OS 커널 공유
밀도서버당 수십 개서버당 수백~수천 개

언제 뭘 쓸까?

✅ 컨테이너 (Docker) 추천
- 마이크로서비스 아키텍처
- 빠른 배포와 확장이 필요할 때
- 개발 환경 통일
- CI/CD 파이프라인

✅ VM 추천
- 완전히 다른 OS가 필요할 때
- 강력한 격리가 필요할 때
- 레거시 시스템 운영

📦 Docker 핵심 개념

1. 이미지 (Image)

이미지 = 컨테이너를 만들기 위한 설계도 (읽기 전용)

특징:

  • 읽기 전용 (변경 불가)
  • 레이어 구조로 되어 있음
  • 하나의 이미지로 여러 컨테이너 생성 가능

비유:

  • 이미지 = 붕어빵 틀 🐟
  • 컨테이너 = 붕어빵 🥮

2. 컨테이너 (Container)

컨테이너 = 이미지를 실행한 인스턴스

특징:

  • 이미지의 실행 인스턴스
  • 생성, 시작, 중지, 삭제 가능
  • 격리된 환경에서 실행
  • 컨테이너끼리 영향을 주지 않음

3. Dockerfile

Dockerfile = 이미지를 만드는 레시피

# 베이스 이미지 지정
FROM node:18-alpine

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

# 의존성 파일 복사 (캐싱 활용)
COPY package*.json ./

# 의존성 설치
RUN npm install

# 소스 코드 복사
COPY . .

# 포트 노출
EXPOSE 3000

# 실행 명령
CMD ["npm", "start"]

4. Docker Hub

Docker Hub = 이미지 저장소 (GitHub 같은 것)

공식 이미지: nginx, node, python, mysql
커스텀 이미지: username/my-app 형태로 업로드


🛠️ Docker 설치하기

Docker Desktop 설치 (권장)

macOS:

# Homebrew로 설치
brew install --cask docker

# 또는 공식 사이트에서 다운로드
# https://www.docker.com/products/docker-desktop

Windows:

1. Docker Desktop for Windows 다운로드
2. WSL 2 활성화 필요
3. 설치 후 재시작

Linux (Ubuntu):

# 패키지 업데이트
sudo apt-get update

# 필요 패키지 설치
sudo apt-get install ca-certificates curl gnupg

# Docker GPG 키 추가
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Docker 설치
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

설치 확인

# Docker 버전 확인
docker --version
# Docker version 28.x.x

# Docker 정상 동작 확인
docker run hello-world

🚀 Docker 기본 명령어

컨테이너 생명주기

이미지 관련 명령어

# 이미지 검색
docker search nginx

# 이미지 다운로드 (pull)
docker pull nginx
docker pull nginx:1.25  # 특정 버전

# 이미지 목록 확인
docker images

# 이미지 삭제
docker rmi nginx

# 이미지 빌드 (Dockerfile 필요)
docker build -t my-app:1.0 .

컨테이너 관련 명령어

# 컨테이너 실행
docker run nginx

# 백그라운드 실행 (-d)
docker run -d nginx

# 이름 지정 + 포트 매핑 (-p)
docker run -d --name my-nginx -p 8080:80 nginx

# 컨테이너 목록 (실행 중)
docker ps

# 컨테이너 목록 (전체)
docker ps -a

# 컨테이너 중지
docker stop my-nginx

# 컨테이너 시작
docker start my-nginx

# 컨테이너 재시작
docker restart my-nginx

# 컨테이너 삭제
docker rm my-nginx

# 컨테이너 강제 삭제 (실행 중이어도)
docker rm -f my-nginx

컨테이너 내부 접근

# 컨테이너 내부 셸 접속
docker exec -it my-nginx /bin/bash

# 컨테이너 로그 확인
docker logs my-nginx

# 실시간 로그 확인
docker logs -f my-nginx

# 컨테이너 상세 정보
docker inspect my-nginx

자주 쓰는 옵션 정리

옵션설명예시
-d백그라운드 실행docker run -d nginx
-p포트 매핑-p 8080:80 (호스트:컨테이너)
--name컨테이너 이름--name my-app
-e환경 변수-e NODE_ENV=production
-v볼륨 마운트-v /host/path:/container/path
--rm종료 시 자동 삭제docker run --rm nginx
-it인터랙티브 + TTYdocker exec -it container bash

📝 Dockerfile 작성하기

기본 구조

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

# 2. 메타데이터
LABEL maintainer="your@email.com"
LABEL version="1.0"

# 3. 환경 변수
ENV NODE_ENV=production

# 4. 작업 디렉토리
WORKDIR /app

# 5. 파일 복사
COPY package*.json ./
RUN npm ci --only=production
COPY . .

# 6. 포트 노출
EXPOSE 3000

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

주요 명령어

명령어설명예시
FROM베이스 이미지FROM node:18-alpine
WORKDIR작업 디렉토리WORKDIR /app
COPY파일 복사COPY . .
ADD파일 복사 (압축 해제 가능)ADD app.tar.gz /app
RUN빌드 시 명령 실행RUN npm install
ENV환경 변수 설정ENV NODE_ENV=production
EXPOSE포트 문서화EXPOSE 3000
CMD컨테이너 시작 시 실행CMD ["npm", "start"]
ENTRYPOINT컨테이너 진입점ENTRYPOINT ["node"]

실전 예제: Node.js 앱

프로젝트 구조:

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

Dockerfile:

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

# Stage 2: 실행
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

EXPOSE 3000
USER node
CMD ["node", "dist/index.js"]

.dockerignore:

node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
README.md

빌드 및 실행:

# 이미지 빌드
docker build -t my-app:1.0 .

# 컨테이너 실행
docker run -d -p 3000:3000 --name my-app my-app:1.0

# 확인
curl http://localhost:3000

🎼 Docker Compose

왜 필요한가?

실제 애플리케이션은 보통 여러 서비스로 구성됩니다:

이런 경우 각 컨테이너를 개별로 실행하면 번거롭습니다.

# 이렇게 하나하나 실행하면 힘듦...
docker run -d --name db postgres
docker run -d --name redis redis
docker run -d --name app --link db --link redis my-app
docker run -d --name nginx --link app nginx

Docker Compose로 해결

docker-compose.yml:

version: '3.8'

services:
  # 웹 애플리케이션
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    restart: unless-stopped

  # 데이터베이스
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

  # 캐시
  redis:
    image: redis:7-alpine
    restart: unless-stopped

# 볼륨 정의
volumes:
  postgres_data:

Docker Compose 명령어

# 모든 서비스 시작
docker compose up

# 백그라운드 실행
docker compose up -d

# 서비스 중지
docker compose down

# 볼륨까지 삭제
docker compose down -v

# 로그 확인
docker compose logs

# 특정 서비스 로그
docker compose logs app

# 서비스 재시작
docker compose restart app

# 실행 중인 서비스 확인
docker compose ps

💾 볼륨과 데이터 관리

왜 볼륨이 필요한가?

볼륨 종류

볼륨 사용법

# Named Volume 생성
docker volume create my-data

# 볼륨으로 컨테이너 실행
docker run -v my-data:/app/data my-app

# Bind Mount (호스트 경로 마운트)
docker run -v $(pwd)/data:/app/data my-app

# 볼륨 목록
docker volume ls

# 볼륨 삭제
docker volume rm my-data

# 사용하지 않는 볼륨 정리
docker volume prune

실전 예제: DB 데이터 유지

# docker-compose.yml
services:
  db:
    image: postgres:15
    volumes:
      - postgres_data:/var/lib/postgresql/data  # 데이터 영구 저장
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql  # 초기화 스크립트
    environment:
      POSTGRES_PASSWORD: secret

volumes:
  postgres_data:  # Named Volume 정의

🌐 Docker 네트워크

네트워크 종류

타입설명용도
bridge기본 네트워크, 컨테이너 간 통신일반적인 경우
host호스트 네트워크 직접 사용성능 최적화
none네트워크 없음격리된 컨테이너
overlay여러 호스트 간 네트워크Docker Swarm

컨테이너 간 통신

같은 네트워크 안에서는 컨테이너 이름으로 통신 가능!

# 네트워크 생성
docker network create my-network

# 네트워크에 연결하며 컨테이너 실행
docker run -d --name db --network my-network postgres
docker run -d --name app --network my-network my-app

# 네트워크 목록
docker network ls

# 네트워크 상세 정보
docker network inspect my-network

🏆 Docker 베스트 프랙티스

1. 작은 베이스 이미지 사용

# ❌ 나쁜 예: 불필요하게 큰 이미지
FROM ubuntu:latest  # ~29MB

# ✅ 좋은 예: 작은 이미지
FROM alpine:latest  # ~3MB
FROM node:18-alpine  # Node.js + Alpine
FROM python:3.11-slim  # 슬림 버전

2. 멀티 스테이지 빌드

# ✅ 빌드 환경과 실행 환경 분리
# Stage 1: 빌드 (큰 이미지 OK)
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: 실행 (작은 이미지)
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

3. 레이어 캐싱 활용

# ❌ 나쁜 예: 코드 변경 시마다 npm install
COPY . .
RUN npm install

# ✅ 좋은 예: package.json 변경 시에만 npm install
COPY package*.json ./
RUN npm install
COPY . .

4. .dockerignore 활용

# .dockerignore
node_modules
.git
.gitignore
*.md
Dockerfile
docker-compose.yml
.env
.DS_Store
coverage
.nyc_output

5. 보안 베스트 프랙티스

# ✅ root가 아닌 사용자로 실행
FROM node:18-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# ✅ 특정 버전 태그 사용 (latest 피하기)
FROM node:18.19.0-alpine3.19

# ✅ 불필요한 패키지 설치 안 함
RUN npm ci --only=production

# ✅ 민감 정보는 환경 변수로
ENV DATABASE_URL=${DATABASE_URL}

6. 이미지 크기 비교

최적화 단계이미지 크기
FROM node:18~1GB
FROM node:18-slim~200MB
FROM node:18-alpine~120MB
멀티 스테이지 + alpine~80MB
distroless~50MB

🔧 실전 예제: 풀스택 앱 컨테이너화

프로젝트 구조

my-fullstack-app/
├── frontend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── docker-compose.yml
└── .env

Backend Dockerfile

# backend/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder --chown=app:app /app/dist ./dist
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
COPY --chown=app:app package*.json ./
USER app
EXPOSE 4000
CMD ["node", "dist/index.js"]

Frontend Dockerfile

# frontend/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

docker-compose.yml

version: '3.8'

services:
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend
    restart: unless-stopped

  backend:
    build: ./backend
    ports:
      - "4000:4000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      - db
      - redis
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    restart: unless-stopped

volumes:
  postgres_data:

실행

# 개발 환경 실행
docker compose up -d

# 로그 확인
docker compose logs -f

# 프로덕션 빌드
docker compose -f docker-compose.prod.yml up -d --build

📝 정리

핵심 요약

필수 명령어 요약

작업명령어
이미지 다운로드docker pull nginx
이미지 빌드docker build -t my-app .
컨테이너 실행docker run -d -p 8080:80 nginx
컨테이너 목록docker ps
컨테이너 중지docker stop <name>
컨테이너 로그docker logs <name>
컨테이너 접속docker exec -it <name> bash
Compose 실행docker compose up -d
Compose 중지docker compose down

📚 참고 자료

공식 문서

학습 자료

관련 블로그 글

profile
누군가에게 도움을 주기 위한 개발자로 성장하고 싶습니다.

0개의 댓글