보통의 개발 흐름?
개발용 컴퓨터에서 코드를 작성하고 실행 → 서버용 컴퓨터에서 빌드와 배포
하지만 이 과정이 항상 순탄한 것은 아님.
환경 충돌 문제
한 컴퓨터에서 여러 프로젝트를 동시에 진행하게 되었을 때, 각 프로젝트가 서로 다른 버전의 프레임워크를 사용한다면 충돌이나 오류가 발생할 가능성이 높음.
A 프로젝트: Node.js 14, Python 3.8
B 프로젝트: Node.js 18, Python 3.10
환경 일치성 문제
"내 컴퓨터에서는 잘 됐는데..." 여러 개발자가 함께하는 복잡한 서비스일수록 서버 환경을 개발 환경과 동일하게 맞추는 것이 중요함. 하지만 프로젝트가 크고 복잡할수록 이 작업은 쉽지 않으며, 작은 설정 하나만 잘못되어도 서버가 제대로 동작하지 않는 오류가 발생할 가능성이 높음.
가상 환경 (Virtual Machine)
내 OS 안에 또 다른 OS를 설치해, 각 프로젝트를 서로 다른 환경에서 실행하는 방식으로, 환경을 완전히 분리해 충돌을 발생할 가능성이 줄어든다는 장점이 있음. 하지만 이 방법의 경우 각각의 가상 환경이 컴퓨터 자원을 나누어 사용하기 때문에 리소스 낭비가 심하고, 시작 시간도 오래 걸릴뿐더러, 운영체제까지 모두 포함하므로 용량이 크고 무거움.
Docker(도커)는 이러한 문제들을 컨테이너 기술로 해결
Docker 이미지는 특정 실행 환경을 그대로 저장해 둔 설계도이자 완제품. 마치 급속된 완제품을 클라우드에 보관해 두는 것과 같음
이미지를 실행한 결과물(인스턴스)가 컨테이너로, 실제로 애플리케이션이 동작하는 격리된 공간과 같음.
예를 들어, 아래는 Ubuntu 이미지를 기반으로 한 컨테이너를 실행하는 코드
docker run -it ubuntu:latest /bin/bash
# 여기서 `-it` 옵션은
# i: 컨테이너와 상호작용 가능 (Interactive)
# t: 터미널 환경 제공 (TTY)
즉, 위 명령을 실행하면 Ubuntu라는 작은 가상 환경 속에 직접 들어가 터미널을 사용할 수 있음. 이 환경 안에서는 다른 컨테이너나 호스트 OS에 영향을 주지 않고 독립적으로 작업 가능.
Dockerfile은 나만의 이미지를 만들기 위한 미시적 설계도.
보통 공식 이미지(node.js, python 등)를 기반으로 시작해, 여기에 프로젝트에 필요한 라이브러리, 설정, 파일 등을 추가해 커스텀 이미지를 만들 수 있음.
# Node.js 프로젝트용 Dockerfile
# 1. Node.js 공식 이미지를 기반으로 시작
FROM node:20-alpine
# 2. 컨테이너 안에서 작업할 디렉터리 지정
WORKDIR /app
# 3. package.json과 package-lock.json만 먼저 복사
COPY package*.json ./
# 4. 의존성 설치
RUN npm install
# 5. 나머지 프로젝트 파일 복사
COPY . .
# 6. 앱이 사용할 포트 개방
EXPOSE 3000
# 7. 컨테이너 시작 시 실행할 명령
CMD ["npm", "start"]
- `FROM`: 기반이 되는 이미지 지정
- `WORKDIR`: 컨테이너 내부 작업 디렉터리
- `COPY`/`RUN`: 파일 복사, 명령 실행
- `EXPOSE`: 외부에서 접근할 포트
- `CMD`: 컨테이너 실행 시 기본으로 수행할 명령
이렇게 만든 Dockerfile로 이미지를 빌드하면, 이미지를 갖고 있는 누구나 동일 환경에서 프로젝트를 실행할 수 있음.
여러 컨테이너로 구성된 애플리케이션을 한 번에 실행하고 관리할 수 있는 거시적 설계도. 예를 들어, 웹 서비스 하나를 운영하려면
이 세 가지를 각각 실행하고 서로 연결하는 것은 꽤 복잡함.
Docker Compose는 이 모든 설정을 하나의 설정 파일로 정의해 한 명령어로 실행 및 종료할 수 있게 해 줌.
# docker-compose.yml
version: "3.9"
services:
db:
image: mysql:8.0
container_name: my-db
environment:
MYSQL_ROOT_PASSWORD: xxxxxx
MYSQL_DATABASE: xxxxxx
MYSQL_USER: xxxxxx
MYSQL_PASSWORD: xxxxxx
ports:
- "3306:3306"
volumes:
- dbdata:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -prootpass || exit 1"]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped
web:
build:
context: ./web
dockerfile: Dockerfile
container_name: my-web
environment:
NODE_ENV: production
DB_HOST: db
DB_USER: appuser
DB_PASSWORD: apppass
DB_NAME: myapp
DB_PORT: "3306"
ports:
- "3000:3000"
depends_on:
- db
restart: unless-stopped
volumes:
dbdata:
dockerfile, Docker compose 보안에 민감한 정보는 따로 관리되는 파일에 넣거나 하는 식으로 안전하게 관리하는 것이 중요
Docker Volume과 COPY 차이
- COPY
- 이미지 빌드 시점에, 호스트 파일을 이미지 내부로 복사
- 복사된 내용은 변하지 않는 정적 데이터
- 프로젝트 소스, 설정 파일 등을 이미지에 포함
- Volume
- 컨테이너 실행 시점에 호스트와 컨테이너의 폴더를 연결
- 연결된 폴더는 실시간으로 동기화, 빌드할 필요 X
- DB 데이터, 개발 중인 소스 코드가 이에 해당
=> 개발 단계에서는 Volume, 배포 단계에서는 COPY를 주로 사용
어떤 OS(Windows, macOS, Linux)에서 실행하더라도 Docker 컨테이너는 리눅스 환경에서 동작. Docker 엔진이 바로 컨테이너를 실행하는 Linux 외에 Windows나 macOS에서는 내부적으로 작은 가상머신을 띄워 그 안에서 컨테이너를 실행함. 이렇게 하면 운영체제가 달라져도 동일한 리눅스 커널을 사용하므로, 컨테이너 내부 환경이 항상 동일함.
이미지 관련 명령어
# 이미지 검색
docker search ubuntu
# 이미지 다운로드
docker pull ubuntu:latest
# 로컬 이미지 목록 확인
docker images
# 이미지 삭제
docker rmi ubuntu:latest
컨테이너 관련 명령어
# 컨테이너 실행 (새로 생성)
docker run -it --name my-container ubuntu /bin/bash
# 백그라운드에서 컨테이너 실행
docker run -d --name web-server nginx
# 실행 중인 컨테이너 목록
docker ps
# 모든 컨테이너 목록 (정지 포함)
docker ps -a
# 컨테이너 시작 / 정지 / 재시작
docker start my-container
docker stop my-container
docker restart my-container
# 실행 중인 컨테이너에 접속
docker exec -it my-container /bin/bash
# 컨테이너 로그 확인
docker logs my-container
# 컨테이너 삭제
docker rm my-container
빌드 및 관리 명령어
# Dockerfile로 이미지 빌드
docker build -t my-app:latest .
# 컨테이너와 호스트 간 파일 복사
docker cp my-file.txt my-container:/app/
# Docker 시스템 정보
docker info
# 사용하지 않는 리소스 정리
docker system prune
# Docker Compose 실행
docker compose up -d
# Docker Compose 정지
docker compose down
자주 사용하는 옵션
-d (detach): 백그라운드에서 실행
-p (port): 포트 포워딩
예: -p 8080:80 → 호스트 8080 → 컨테이너 80
-v (volume): 볼륨 마운트
예: -v ./data:/app/data
--name: 컨테이너에 이름 지정
--rm: 컨테이너 종료 시 자동 삭제
Docker는 복잡한 환경 설정 문제를 해결하고,
팀원들이 같은 환경에서 작업할 수 있게 하며,
배포와 확장 과정을 훨씬 단순하고 빠르게 만들어 줍니다.