본문 바로가기

make

Docker가 뭔지 몰라도 괜찮아 — 개발자가 Docker를 사랑하는 이유, 처음부터 차근차근

반응형

"제 컴퓨터에서는 되는데요?"

개발을 시작한 지 얼마 안 됐을 때, 팀원에게 코드를 넘겨받고 실행해보려다 에러 메시지만 잔뜩 만난 경험, 혹시 있지 않나요?

ModuleNotFoundError: No module named 'xxx'
Error: ENOENT: no such file or directory
...

 

분명히 같은 코드인데, 내 환경에선 실행이 안 됩니다. 상대방은 "저는 되는데요?"라고 하고, 나는 한 시간 넘게 환경 설정을 뒤지고 있습니다. Node.js 버전이 다르거나, Python 버전이 미묘하게 달랐거나, 어떤 라이브러리가 전역으로 설치되어 있거나 없거나. 이 상황이 반복되다 보면 "왜 코드보다 환경 세팅에 더 오래 걸리는 거지?"라는 생각이 자연스럽게 듭니다.

Docker는 바로 이 문제에서 출발합니다. "어떤 환경에서든 똑같이 실행되게 하자"는 단순하지만 강력한 아이디어입니다. 이 글에서는 Docker가 무엇인지, 왜 개발자들이 그렇게 좋아하는지를 초급 개발자 눈높이에서 풀어보겠습니다.


Docker란 무엇인가 — 컨테이너 개념부터 이해하기

Docker를 한 문장으로 정리하면, "애플리케이션과 그 실행 환경을 하나의 패키지로 묶어주는 도구" 입니다.

여기서 핵심 개념이 컨테이너(Container) 입니다. 컨테이너는 코드, 런타임, 라이브러리, 설정 파일 등을 하나로 묶어서 어느 환경에서든 동일하게 실행할 수 있게 해줍니다. 마치 음식 배달을 시킬 때 음식만 오는 게 아니라 용기, 수저, 소스까지 세트로 오는 것과 비슷합니다. 받는 사람 입장에선 따로 준비할 게 없죠.

컨테이너와 자주 비교되는 개념이 가상 머신(VM, Virtual Machine) 입니다.

graph TD
    subgraph VM["가상 머신 구조"]
        HOST_OS1["Host OS"] --> HYPERVISOR["Hypervisor"]
        HYPERVISOR --> GUEST_OS1["Guest OS 1\n(수백 MB ~ GB)"]
        HYPERVISOR --> GUEST_OS2["Guest OS 2\n(수백 MB ~ GB)"]
        GUEST_OS1 --> APP1["App A"]
        GUEST_OS2 --> APP2["App B"]
    end

    subgraph DOCKER["Docker 컨테이너 구조"]
        HOST_OS2["Host OS"] --> DOCKER_ENGINE["Docker Engine"]
        DOCKER_ENGINE --> CONTAINER1["Container 1\n(수십 MB)"]
        DOCKER_ENGINE --> CONTAINER2["Container 2\n(수십 MB)"]
        CONTAINER1 --> APP3["App A"]
        CONTAINER2 --> APP4["App B"]
    end

 

VM은 운영체제(OS) 자체를 통째로 가상화합니다. 그래서 무겁고, 시작하는 데 시간이 걸립니다. 반면 Docker 컨테이너는 Host OS의 커널을 공유하면서 프로세스 수준에서 격리됩니다. 덕분에 훨씬 가볍고, 시작도 빠릅니다. VM이 아파트 건물 전체를 새로 짓는 거라면, 컨테이너는 이미 있는 건물에 독립된 방 하나를 만드는 느낌입니다.

VM과 Docker 비교 다이어그램

 


Dockerfile, 이미지, 컨테이너 — 세 가지 개념의 관계

Docker를 처음 배울 때 용어가 헷갈리는 경우가 많습니다. Dockerfile, 이미지(Image), 컨테이너(Container), 이 세 가지는 어떻게 다를까요?

비유를 하나 들어볼게요.

  • Dockerfile = 레시피 (어떻게 만들지 쓴 문서)
  • 이미지(Image) = 레시피대로 만든 완성된 냉동식품 (실행 가능한 패키지)
  • 컨테이너(Container) = 냉동식품을 전자레인지에 돌려서 실제로 먹고 있는 상태 (실행 중인 인스턴스)

Dockerfile을 작성하면, docker build 명령어로 이미지를 만들고, 그 이미지를 docker run으로 실행하면 컨테이너가 됩니다.

간단한 Node.js 앱을 예로 들면, Dockerfile은 이렇게 생겼습니다:

# 베이스 이미지 지정 (Node.js 18버전 사용)
FROM node:18-alpine

# 컨테이너 안의 작업 디렉토리 설정
WORKDIR /app

# 의존성 파일 먼저 복사 (캐시 효율을 위해)
COPY package*.json ./

# 의존성 설치
RUN npm install

# 나머지 소스 코드 복사
COPY . .

# 앱이 사용할 포트 명시
EXPOSE 3000

# 컨테이너 실행 시 실행될 명령어
CMD ["node", "index.js"]

 

이 파일 하나가 있으면, 팀원 누구든 docker builddocker run 명령어 두 개로 동일한 환경에서 앱을 실행할 수 있습니다. Python 버전 맞추거나, 라이브러리 따로 설치할 필요가 없습니다.

Docker 빌드 흐름 안내


실제 개발 환경에서 Docker를 쓰면 어떻게 달라질까

Docker가 이론적으로는 멋지게 들리는데, 실제로 어떤 상황에서 체감이 되는지 궁금하실 수 있습니다.

상황 1: 새 팀원 온보딩

기존에는 새 팀원이 합류하면 README를 보면서 Node 버전 맞추고, 데이터베이스 설치하고, 환경 변수 설정하는 데 반나절이 걸리는 경우가 흔합니다. Docker Compose를 사용하면, 아래처럼 명령어 하나로 앱 서버와 데이터베이스가 함께 뜹니다.

# docker-compose.yml 예시
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://user:password@db:5432/mydb
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
docker compose up

 

이 명령어 하나면 앱 서버와 PostgreSQL 데이터베이스가 함께 실행됩니다. 새 팀원 입장에선 DB를 따로 설치할 필요가 없습니다.

상황 2: "내 컴퓨터에서는 됐는데" 문제 해결

로컬에서 개발한 코드가 서버에 배포되면 갑자기 안 되는 상황, 개발자라면 한 번쯤 겪어봤을 겁니다. Docker를 쓰면 개발 환경과 배포 환경이 동일한 이미지를 기반으로 실행되기 때문에 이런 불일치가 크게 줄어듭니다. 로컬에서 잘 돌아가던 컨테이너가 서버에서도 그대로 실행됩니다.

상황 3: 여러 프로젝트 환경 충돌 방지

Python 2가 필요한 레거시 프로젝트와 Python 3.11이 필요한 신규 프로젝트를 동시에 작업해야 할 때, 기존엔 가상환경을 여러 개 관리하거나 pyenv 같은 도구를 써야 했습니다. Docker를 쓰면 각 프로젝트가 완전히 격리된 컨테이너에서 실행되기 때문에, 어떤 버전이든 충돌 없이 공존할 수 있습니다.


Docker를 처음 써볼 때 알아두면 좋은 것들

처음 Docker를 접하면 막연하게 느껴질 수 있어서, 실제로 해보면서 "아, 이래서 쓰는구나" 싶었던 순서를 공유해봅니다.

1. Docker Desktop 설치부터
로컬에서 Docker를 쓰려면 Docker Desktop을 설치하는 것이 가장 쉬운 출발점입니다. 설치하면 docker CLI 명령어를 바로 쓸 수 있습니다.

2. 공식 이미지를 먼저 당겨보기
직접 Dockerfile을 짜기 전에, Docker Hub에 있는 공식 이미지를 실행해보는 것도 좋은 연습입니다.

# nginx 웹서버를 로컬 8080 포트로 실행해보기
docker run -p 8080:80 nginx

 

이 명령어 하나로 nginx 서버가 뜹니다. 브라우저에서 localhost:8080에 접속하면 nginx 기본 페이지가 보입니다. 설치도 없이요.

3. 컨테이너 안을 직접 들여다보기

# 실행 중인 컨테이너 안으로 들어가기
docker exec -it [컨테이너ID] /bin/sh

컨테이너를 마치 별도의 리눅스 환경처럼 들어가서 파일도 보고 명령어도 실행해볼 수 있습니다. 이렇게 하다 보면 컨테이너가 "가상 컴퓨터 비슷한 것"이라는 감이 오기 시작합니다.

도커 데스크탑 컨테이너 화면

 


마무리 — Docker, 배워두면 어디서든 쓰인다

정리해보면 이렇습니다.

개념 한 줄 요약
컨테이너 코드 + 실행 환경을 하나로 묶은 격리된 프로세스
Dockerfile 이미지를 만드는 레시피 파일
이미지 빌드된 실행 패키지 (배포 단위)
Docker Compose 여러 컨테이너를 한 번에 정의하고 실행하는 도구

 

Docker가 처음엔 "그냥 환경 맞추는 도구" 정도로 보일 수 있습니다. 그런데 쓰다 보면 협업, 배포, 테스트 자동화 등 개발의 거의 모든 영역에 연결된다는 걸 느끼게 됩니다. 특히 CI/CD 파이프라인이나 쿠버네티스(Kubernetes) 같은 컨테이너 오케스트레이션 도구로 가는 기초이기도 합니다.

"일단 Docker run 한 번 해봤다"는 경험이 쌓이다 보면, 어느 순간 팀 프로젝트에서 Dockerfile을 자연스럽게 작성하고 있는 자신을 발견하게 될 겁니다. 처음엔 어렵게 느껴져도 직접 손을 움직여보는 게 제일 빠른 길이었습니다.


반응형