생산성

Docker 입문 가이드 - 컨테이너 개념부터 Node.js 실전 배포까지

Docker 입문 가이드 - 컨테이너 개념부터 Node.js 실전 배포까지

“내 컴퓨터엔 되는데 서버엔 왜 안 되지?”

개발하다 보면 이 상황, 한 번쯤 겪어봤을 거예요. Node.js 버전이 달라서, 라이브러리가 달라서, OS가 달라서. 이유는 다양한데 결과는 하나예요. 프로덕션 장애.

Docker는 이 문제를 근본적으로 없애줘요. 2026년 기준 전체 소프트웨어 팀의 약 78%가 컨테이너 기반 배포를 쓰는 이유가 바로 이거예요 (CNCF Annual Survey 2025).

이 글을 다 읽으면:

  • 컨테이너와 VM의 차이를 직관적으로 이해하게 돼요
  • Dockerfile을 직접 작성하고 이미지를 빌드할 수 있어요
  • 실제 Node.js 앱을 컨테이너로 묶어서 배포하는 과정을 경험해요
  • 흔히 저지르는 실수들을 미리 피할 수 있어요

Key Takeaways

  • Docker는 앱과 실행 환경을 이미지 하나로 묶어서, 어떤 서버에서 실행해도 결과가 같아요.
  • 컨테이너는 VM과 달리 OS 커널을 공유하기 때문에, 시작 속도가 수 초 이내로 훨씬 빠릅니다.
  • Dockerfile은 이미지 빌드 명세서이고, docker-compose.yml은 여러 컨테이너를 한 번에 관리하는 도구예요.
  • Docker Hub에서 공식 이미지를 바로 가져다 쓸 수 있어서, 개발 환경 구성 시간이 크게 줄어요.
  • 2026년 기준 Docker Engine 최신 안정 버전은 26.x이며, Docker Desktop 4.x와 함께 쓰는 게 표준이에요.

Docker가 지금 필수가 된 이유

Docker는 2013년 DotCloud(현 Docker Inc.)가 공개한 뒤, 처음엔 “신기한 배포 도구” 정도였어요. 그런데 2018년 이후 Kubernetes와 결합되면서 판이 완전히 바뀌었죠.

문제는 이렇게 시작돼요. 개발자 A 노트북은 Node.js 18, 개발자 B는 20, 서버는 16. 라이브러리 버전도 제각각. 이 환경 불일치가 프로덕션 장애의 주요 원인이었거든요.

Docker의 해법은 단순해요.

앱 코드 + 런타임 + 라이브러리 + 설정을 이미지 하나로 묶는다.

그 이미지를 어디서 실행하든 결과가 같아요. 개발 → 테스트 → 프로덕션 환경이 완전히 동일해지는 거예요.

시작 전 알아두면 좋은 것들:

  • 리눅스 기본 명령어 (ls, cd, pwd)
  • 터미널 사용 경험
  • 기본적인 웹 앱 구조 이해 (Node.js나 Python 경험 있으면 충분해요)

컨테이너 vs VM vs 베어메탈

비교 항목Docker 컨테이너가상 머신 (VM)베어메탈 서버
시작 속도1-3초30초~수 분수 분
이미지 크기수십~수백 MB수 GB
OS 공유호스트 커널 공유독립 OS독립 OS
격리 수준프로세스 레벨하이퍼바이저 레벨완전 격리
이식성매우 높음중간낮음
주요 도구Docker, PodmanVMware, VirtualBox

컨테이너는 OS를 통째로 들고 다니지 않아요. 호스트 OS의 커널을 빌려 쓰는 대신, 앱 실행에 필요한 파일만 포함해요. 그래서 VM보다 훨씬 가볍고 빠른 거예요.

VM은 격리가 더 강해서 보안이 중요한 환경엔 여전히 쓰여요. 하지만 빠른 배포와 스케일링이 필요한 현대 서비스엔 컨테이너가 훨씬 잘 맞아요.


단계별 실습: Node.js 앱을 컨테이너로 배포해볼게요

준비물

  • Docker Desktop 4.x (Mac/Windows) 또는 Docker Engine 26.x (Linux)
  • Node.js 20.x (로컬 테스트용)
  • 터미널 (bash 또는 zsh)

설치 확인은 이렇게 해요:

docker --version
# Docker version 26.1.4, build a61e2b4

docker info

Step 1: 샘플 Node.js 앱 만들기

프로젝트 폴더 구조는 이렇게 돼요:

my-docker-app/
├── app.js
├── package.json
└── Dockerfile
// app.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
  const env = process.env.NODE_ENV || 'development';
  res.json({ message: 'Hello from Docker!', environment: env });
});

app.listen(PORT, () => {
  console.log(`서버가 포트 ${PORT}에서 실행 중이에요`);
});
{
  "name": "my-docker-app",
  "version": "1.0.0",
  "main": "app.js",
  "scripts": { "start": "node app.js" },
  "dependencies": { "express": "^4.18.2" }
}

Step 2: Dockerfile 작성하기

Dockerfile은 이미지 빌드 명세서예요. 요리 레시피랑 비슷해요.

FROM node:20-alpine
WORKDIR /app

# package.json만 먼저 복사 — 레이어 캐시를 잘 쓰기 위해서예요
COPY package.json .
RUN npm install --omit=dev

COPY . .
EXPOSE 3000
CMD ["node", "app.js"]

package.json을 소스 코드보다 먼저 복사하는 게 핵심이에요. Docker는 레이어 단위로 캐시를 저장해요. 소스 코드가 바뀌어도 의존성이 그대로면 npm install을 다시 실행하지 않아요. 빌드 시간이 확 줄어요.


Step 3: 이미지 빌드 & 컨테이너 실행

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

# 컨테이너 실행 (-d: 백그라운드, -p: 포트 연결)
docker run -d -p 8080:3000 --name my-app my-docker-app:1.0

# 실행 중인 컨테이너 확인
docker ps

브라우저에서 http://localhost:8080을 열면 JSON 응답이 바로 나와요.

docker logs my-app    # 로그 확인
docker stop my-app    # 중지
docker rm my-app      # 삭제

Step 4: docker-compose로 멀티 컨테이너 관리하기

실제 앱엔 DB도 있고 Redis도 있어요. docker-compose.yml로 한 번에 띄워요.

version: '3.9'

services:
  app:
    build: .
    ports:
      - "8080:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=db
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
docker compose up -d      # 모든 서비스 시작
docker compose ps         # 상태 확인
docker compose down       # 중지 및 삭제

실전에서 꼭 챙겨야 할 것들

.dockerignore는 필수예요

node_modules    # 이미지 안에서 새로 설치해요
.git
*.log
.env            # 보안상 절대 포함하지 마세요
README.md

node_modules를 이미지에 포함시키는 실수를 자주 해요. 이미지 크기가 수백 MB씩 커지고, OS가 다르면 네이티브 모듈 문제도 생겨요.

레이어 낭비 줄이는 법

# 비효율적
RUN apt-get update
RUN apt-get install -y curl

# 효율적
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

흔히 저지르는 실수 3가지

Pitfall 1: latest 태그만 쓰기 my-app:latest만 쓰면 어떤 버전인지 추적이 안 돼요. my-app:1.2.3 또는 my-app:git-abc1234 같은 버전 태그를 붙이세요.

Pitfall 2: root 사용자로 실행 컨테이너 안에서 root로 앱을 실행하면 보안 위험이 커요. Dockerfile에 USER node를 추가하는 게 기본이에요.

Pitfall 3: .env 파일 이미지에 포함 환경 변수 파일이 이미지에 들어가면 Docker Hub에 올렸을 때 비밀번호가 그대로 노출돼요. .dockerignore.env를 반드시 추가하세요.

프로덕션 배포 체크리스트

  • .dockerignore 파일 작성 완료
  • 비루트 사용자(USER) 설정
  • 환경 변수로 민감 정보 분리
  • 이미지 태그에 버전 명시
  • restart: unless-stopped 설정
  • 볼륨으로 데이터 영속성 확보

이제 뭘 해볼까요?

여기까지 이해했다면, 실무의 80%는 준비된 거예요.

핵심만 다시 정리하면:

  • 컨테이너 = 앱 + 환경을 하나로 묶은 것
  • Dockerfile = 이미지 만드는 레시피
  • docker-compose = 여러 컨테이너를 한 번에 관리하는 도구
  • .dockerignore와 레이어 캐싱은 습관처럼 챙기세요

지금 당장 터미널 열고 docker run hello-world를 쳐보세요. 그 다음엔 본인 프로젝트에 Dockerfile을 붙여보는 거예요. 막히는 부분이 생기면 댓글로 남겨줘요.

다음 단계는 멀티스테이지 빌드로 이미지 최적화Kubernetes 입문을 추천해요.

참고자료

  1. 도커(Docker) 입문편 컨테이너 기초부터 서버 배포까지 - 44BITS
  2. [Docker] 도커 컨테이너 완벽 가이드: 개념부터 명령어, 실전 활용까지
  3. Docker와 Kubernetes 입문 가이드: 컨테이너 기술의 모든 것 | SpacePlanning Blog

Photo by Mohammad Rahmani on Unsplash