도커(Docker)의 기본 개념과 사용법

서예림·2025년 10월 31일
post-thumbnail

도커(Docker)란?

도커(Docker)는 리눅스 컨테이너 기술을 기반으로 애플리케이션을 격리된 프로세스 단위(컨테이너)로 관리할 수 있도록 도와주는 플랫폼입니다.

가상 머신(VM)처럼 완전한 운영체제를 복제하지 않고 Host OS의 커널을 공유하기 때문에 훨씬 가볍고 빠릅니다.

도커는 계층화된 파일 시스템(Layered File System)을 사용해 애플리케이션 실행 환경을 이미지(Image) 형태로 정의합니다.
이 이미지를 기반으로 컨테이너를 실행하고, 특정 컨테이너의 상태를 다시 새로운 이미지로 저장할 수도 있습니다.

이렇게 만들어진 이미지는 파일로 보관하거나 Docker Hub 같은 원격 저장소에 업로드하여 쉽게 공유할 수 있습니다.
도커만 설치되어있다면 언제 어디서나 동일한 환경에서 컨테이너를 실행할 수 있습니다.

도커(Docker)를 사용하는 이유?

1. 안정적인 환경 보장
도커 컨테이너는 애플리케이션이 어디서 실행되든 항상 동일한 환경을 제공합니다.
덕분에 개발, 테스트, 프로덕션 환경이 달라서 생기는 환경 차이 문제를 크게 줄일 수 있습니다.

2. 가벼운 리소스 활용
도커는 가상 머신보다 훨씬 가볍습니다. 컨테이너는 Host OS의 커널을 공유하므로 GPU나 메모리 자원을 효율적으로 활용할 수 있고 수십 개의 컨테이너를 동시에 실행해도 부담이 적습니다.

3. 자동화된 배포와 재현성
도커 이미지는 버전 관리가 가능합니다. 한 번 만들어둔 이미지를 어디서든 동일하게 재현할 수 있습니다.
CI/CD 파이프라인과 연동하면 자동 배포도 쉽게 할 수 있습니다.

도커 이미지 (Docker Image)

도커에서 이미지란 애플리케이션을 실행하기 위한 환경(템플릿)이라고 할 수 있습니다. 이 환경은 파일들의 집합입니다.

사용 가능한 이미지 확인:

docker image ls

도커 허브(Docker Hub)

도커 허브는 도커에서 제공하는 이미지 호스팅 서비스입니다. 자주 사용되는 애플리케이션에 대한 공식 이미지를 제공하고 있습니다.

Node.js 버전 확인 방법

1. Docker Hub에서 확인
https://hub.docker.com/_/node 에서 사용 가능한 태그 확인

2. 명령어로 확인

# 현재 사용 중인 Node.js 버전
node --version

# Docker로 특정 버전 확인
docker run --rm node:20.18.3-slim node --version

태그 선택 가이드

# ❌ 비추천: 버전 고정이 없음
FROM node

# ⚠️ 주의: 메이저 버전만 고정
FROM node:20

# ✅ 정확한 버전 + slim
FROM node:20.18.3-slim

# ✅ 프로덕션 용: Alpine (더 작은 크기)
FROM node:20.18.3-alpine

도커 컨테이너 (Docker Container)

컨테이너는 이미지를 기반으로 실제 실행된 프로세스(실행 중인 인스턴스)입니다. 이미지가 설계도라면 컨테이너는 그 설계도로 만들어진 집입니다.
컨테이너를 실행하려면 반드시 이미지가 있어야 합니다.

현재 실행중인 컨테이너 확인:

docker container ls

각 컨테이너는 독립된 공간에서 실행되기 때문에, 다른 컨테이너나 호스트 환경과 충돌하지 않습니다.

Dockerfile로 이미지 빌드하기

Dockerfile은 이미지를 생성하기 위한 설정 파일입니다. 어떤 베이스 이미지를 사용할 지, 어떤 명령어를 실행할 지 정의합니다.

예시: Next.js + Nginx (Multi-stage Build)

# Build Stage
FROM node:20.18.3-slim AS build-stage
WORKDIR /app

# pnpm 설치
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile

# 소스코드 복사 및 빌드
COPY . .
RUN pnpm build

# Production Stage
FROM nginx:alpine AS production-stage

# 빌드된 파일 복사
COPY --from=build-stage /app/dist /usr/share/nginx/html

# Nginx 설정 복사
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

프레임워크 별 경로 차이 (Next.js / React / Vue)

  • React / Vue: npm run build 결과물이 /dist 또는 /build 폴더로 생성 -> /usr/share/nginx/html로 복사
  • Next.js: next start 실행 필요 -> Nginx가 아닌 Node 서버에서 직접 실행

따라서 Next.js인 경우는 다음처럼 구성할 수 있습니다.

예시:

# Build Stage
FROM node:18.18.0-slim as build-stage
WORKDIR /app
COPY . .
RUN npm install --legacy-peer-deps
RUN npm install -g pnpm
RUN npx next build --no-lint

# Production Stage
FROM node:18.18.0-slim as production-stage
WORKDIR /app
COPY --from=build-stage /app /app
EXPOSE 3000
CMD ["pnpm", "start"]

Docker Compose로 개발 환경 구성

여러 개의 컨테이너를 함께 관리하려면 Docker Compose를 사용합니다.

예시: docker-compose.yml

version: "3.8"
services:
  frontend:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
    environment:
      - NODE_ENV=development
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./dist:/usr/share/nginx/html

유용한 Docker Compose 명령어:

# 모든 서비스 시작
docker-compose up

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

# 실시간 로그 확인
docker-compose logs -f frontend

# 모든 서비스 정지 및 컨테이너 삭제
docker-compose down

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

Nginx 설정 최적화

SPA(Single Page Application) 지원

events {
    worker_connections 1024;
}

http {
    # MIME 타입 설정 추가 (중요!)
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    server {
        listen 80;
        server_name localhost;
        root /usr/share/nginx/html;
        index index.html;

        # Vue Router의 History Mode를 위한 설정
        location / {
            try_files $uri $uri/ /index.html;
        }

        # 정적 파일에 대한 MIME 타입 명시적 설정
        location ~* \.js$ {
            add_header Content-Type application/javascript;
            expires 1y;
            add_header Cache-Control "public, immutable";
            access_log off;
        }

        location ~* \.css$ {
            add_header Content-Type text/css;
            expires 1y;
            add_header Cache-Control "public, immutable";
            access_log off;
        }

        # 기타 정적 파일 캐싱
        location ~* \.(png|jpg|jpeg|gif|ico|woff|woff2|ttf|svg|eot)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
            access_log off;
        }

        # API 프록시 (CORS 해결)
        location /api/ {
            proxy_pass http://backend:8000/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # gzip 압축
        gzip on;
        gzip_vary on;
        gzip_types 
            text/plain 
            text/css 
            application/json 
            application/javascript 
            text/xml 
            application/xml 
            application/xml+rss 
            text/javascript;

        # 로그 설정
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
    }
}

Next.js를 위한 리버스 프록시 설정

server {
    listen 80;
    server_name localhost;

    # Next.js API 요청과 페이지 요청을 처리
    location / {
        proxy_pass http://nextjs:3000; # Next.js 서버로 요청 전달
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    # 정적 파일 요청 처리 (선택사항)
    location /_next/static/ {
        proxy_pass http://nextjs:3000;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 에러 페이지 처리
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}

.dockerignore의 중요성

.dockerignore 파일은 이미지 빌드 시 제외할 파일/폴더를 지정합니다.
.git, node_modules, dist 등을 제외하면 빌드 속도가 크게 향상됩니다.

예시:

# 의존성 (컨테이너에서 새로 설치)
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# 빌드 결과물 (컨테이너에서 새로 빌드)
dist
build
.next
out

# 개발 도구
.git
.gitignore
.vscode
.idea

# 로그 및 캐시
logs
*.log
.cache
.temp
.DS_Store

# 환경 설정 (보안상 제외)
.env.local
.env.*.local

# 테스트
coverage
.nyc_output

실제 사용 방안

로컬 개발 시 CORS 문제 해결

개발할 때 종종 마주치는 문제가 CORS(Cross-Origin Resource Sharing)에러 입니다. 외부 API나 테스트 서버를 사용할 때 자주 발생합니다.

Docker + Nginx로 해결:

docker-compose.yml

version: "3.8"
services:
  nginx:
    image: nginx:stable-alpine
    volumes:
      - ./local-nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "80:80"
    # Linux 사용자의 경우 호스트 네트워크 모드 고려
    # network_mode: "host"  # Linux에서만 사용

nginx.conf

events { }

http {
    # upstream: Next.js 개발 서버 정의
    upstream frontend {
        # 호스트 머신의 개발 서버 연결
        # macOS/Windows: host.docker.internal 사용
        # Linux: --network=host 모드 또는 172.17.0.1 사용
        server host.docker.internal:3000;
    }

    server {
        listen 80;
        server_name 127.0.0.1 localhost;

        # API 요청 프록시 (CORS 우회)
        location /api/ {
            proxy_pass          http://test.api.com:7777/api/;
            proxy_set_header    Host test.api.com;
            proxy_set_header    X-Real-IP $remote_addr;
            proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header    X-Forwarded-Proto $scheme;

            # 쿠키 도메인 재작성 (필요시)
            proxy_cookie_domain test.api.com localhost;
            
            # CORS 헤더 추가 (백업용)
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
            add_header Access-Control-Allow-Headers "Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With";
        }

        # 🖥️ 프론트엔드 요청 프록시
        location / {
            proxy_pass          http://frontend;
            proxy_http_version  1.1;
            proxy_set_header    Upgrade $http_upgrade;
            proxy_set_header    Connection 'upgrade';
            proxy_set_header    Host $host;
            proxy_set_header    X-Real-IP $remote_addr;
            proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header    X-Forwarded-Proto $scheme;
        }
    }
}

사용 방법:

# 1. Next.js 개발 서버 시작 (호스트에서)
npm run dev  # localhost:3000에서 실행

# 2. Nginx 프록시 시작 (Docker로)
docker-compose up -d

# 3. 브라우저에서 접속
# http://localhost (포트 80)

프론트엔드 코드에서:

// CORS 에러 없이 API 호출 가능
const response = await fetch('/api/users')  // localhost:80/api/users
// 실제로는 test.api.com:7777/api/users로 프록시됨

5개의 댓글

comment-user-thumbnail
2025년 10월 31일

Docker같은 CI/CD가 매번 구성할때 찾아보고 다시 구성할때 찾아보게 되는데, 그 때마다 이 글을 다시 읽으면 될 것 같이 잘 정리된 글이네요..!!! 좋은 글 감사합니다!!!

답글 달기
comment-user-thumbnail
2025년 10월 31일

저도 예전에 도커에 관한 글을 해당 벨로그에 썼는데요..! 다시 읽어봐도 참 어려운게 도커인거 같아요 ㅋㅋㅋㅋㅋ나중에... 강의함 해주시지요오..

답글 달기
comment-user-thumbnail
2025년 11월 1일

좋은건 알겠는데 잘 쓰기가 어려운 도커 ㅠㅠ 공부를 많이 하신게 느껴지고 이렇게 한눈에 보니까 넘좋네요!

답글 달기
comment-user-thumbnail
2025년 11월 4일

도커 직접 사용해본적이 없는데 이렇게 훑어볼 수 있으니 좋네요..! 사용하는 이유까지 있어서 이해하는데 더 도움이 된 것 같아용!!

답글 달기
comment-user-thumbnail
2025년 11월 5일

이번에 도커 환경 확인해봐야 할 일이 생겼는데, Nginx 설정 예시 확인할 수 있어서 좋네요! 좋은 정보 감사합니다!!

답글 달기