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, Podman | VMware, 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 입문을 추천해요.
- 공식 문서: docs.docker.com
- CNCF 컨테이너 리포트: cncf.io/reports
참고자료
- 도커(Docker) 입문편 컨테이너 기초부터 서버 배포까지 - 44BITS
- [Docker] 도커 컨테이너 완벽 가이드: 개념부터 명령어, 실전 활용까지
- Docker와 Kubernetes 입문 가이드: 컨테이너 기술의 모든 것 | SpacePlanning Blog
Photo by Mohammad Rahmani on Unsplash


