CHHB stroy

Docker Desktop, 설치부터 실무 활용까지 — 내가 처음에 알았으면 좋았을 것들 본문

기타

Docker Desktop, 설치부터 실무 활용까지 — 내가 처음에 알았으면 좋았을 것들

CHHB 2026. 5. 15. 16:54

Docker 처음 접했을 때 솔직히 멘붕이었다. 가상머신이랑 뭐가 다른 건지, 이미지랑 컨테이너가 뭔 차인지, 왜 다들 도커 도커 하는 건지. 근데 한번 제대로 잡고 나니까 이제는 도커 없이 개발하는 게 상상이 안 된다. "내 컴에서는 되는데요?"를 안 듣게 된 것만으로도 충분한 가치가 있다.

오늘은 Docker Desktop 기준으로, 설치부터 실무에서 자주 쓰는 패턴까지 한번에 정리해보려고 한다.


Docker가 뭔데, 왜 써야 하는 건데

한 줄로 말하면, 앱을 실행하는 데 필요한 모든 것을 하나의 패키지로 묶어서 어디서든 똑같이 돌리는 도구다.

예를 들어 PHP + MySQL + Redis 조합의 프로젝트를 한다고 치자. 팀원 A는 Mac이고, B는 Windows고, C는 Ubuntu다. 셋 다 PHP 버전이 다르고, MySQL 설정이 다르고, 뭔가 하나씩 안 된다. 이걸 Docker로 묶어놓으면 세 사람 다 docker compose up 한 방이면 똑같은 환경에서 개발할 수 있다.

가상머신(VM)이랑 뭐가 다르냐는 질문을 많이 받는데, VM은 운영체제 전체를 올리는 거고 Docker는 필요한 프로세스만 격리해서 올린다. 그래서 훨씬 가볍고 빠르다. 내 맥북에서 VM으로 Ubuntu 3개 띄우면 팬이 미쳐 돌아가는데, 컨테이너 10개 띄워도 별로 티가 안 난다.


Docker Desktop 설치

macOS

두 가지 방법이 있다.

# Homebrew로 설치 (내가 선호하는 방법)
brew install --cask docker

# 또는 공식 사이트에서 .dmg 다운로드
# https://www.docker.com/products/docker-desktop/

설치하고 실행하면 상단 메뉴바에 고래 아이콘이 뜬다. 이게 떠 있어야 Docker 명령어가 동작한다. 가끔 터미널에서 docker 쳤는데 안 되면 십중팔구 Docker Desktop이 안 켜져 있는 거다.

Apple Silicon(M1/M2/M3) 맥이면 ARM 버전이 자동으로 설치된다. 근데 가끔 x86 전용 이미지를 돌려야 할 때가 있는데, 이때는 Rosetta 에뮬레이션이 알아서 처리해준다. 느리긴 하지만 동작은 한다.

Windows

1. Docker Desktop 공식 사이트에서 설치 파일 다운로드
2. 설치 진행 — WSL 2 백엔드 사용 옵션 체크 (기본값)
3. 재부팅
4. WSL 2 커널 업데이트가 필요하다고 뜨면 안내대로 설치

Windows는 WSL 2(Windows Subsystem for Linux 2)가 핵심이다. 예전에는 Hyper-V를 썼는데 요즘은 WSL 2가 기본이고 성능도 훨씬 좋다. WSL 2가 없으면 Docker Desktop 설치 과정에서 같이 설치해준다.

⚠️ 주의: Windows Home 에디션도 WSL 2 방식이면 Docker Desktop 사용 가능하다. 예전에는 Pro 이상만 됐는데 그건 Hyper-V 시절 얘기다.

Linux

# Ubuntu 기준
sudo apt-get update
sudo apt-get install ./docker-desktop-<version>-amd64.deb

솔직히 Linux에서는 Docker Desktop 안 쓰고 Docker Engine만 깔아도 된다. Desktop은 GUI가 주된 장점인데, Linux에서 Docker 쓸 정도면 CLI가 더 편한 경우가 많으니까. 근데 요즘 Docker Desktop에 Kubernetes 원클릭 활성화, Extensions 같은 기능이 있어서 쓰는 사람도 꽤 있다.


핵심 개념 정리 — 이것만 알면 된다

이미지 vs 컨테이너

이게 처음에 제일 헷갈리는 건데, 비유하면 이렇다.

  • 이미지 = 붕어빵 틀. 설계도. 실행에 필요한 모든 게 들어있는 스냅샷.
  • 컨테이너 = 그 틀로 찍어낸 붕어빵. 실제로 돌아가는 인스턴스.

하나의 이미지로 컨테이너를 여러 개 만들 수 있다. Node.js 이미지 하나로 컨테이너 10개 띄울 수 있는 거다.

# 이미지 다운로드
docker pull nginx

# 이미지로 컨테이너 생성 + 실행
docker run -d -p 8080:80 nginx

# 같은 이미지로 하나 더
docker run -d -p 8081:80 nginx

볼륨 (Volume)

컨테이너는 기본적으로 일회용이다. 컨테이너 지우면 안의 데이터도 날아간다. DB 컨테이너 재시작했는데 데이터가 다 사라져서 멘탈 나간 적이 있다면 (나도 그랬다), 볼륨을 안 붙여서 그런 거다.

# 볼륨 생성
docker volume create mydata

# 볼륨 붙여서 컨테이너 실행
docker run -d \
  -v mydata:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:8

이렇게 하면 컨테이너를 지우고 다시 만들어도 데이터가 유지된다.

네트워크

컨테이너끼리 통신하려면 같은 네트워크에 있어야 한다. docker compose를 쓰면 자동으로 해주지만, 수동으로 할 때는 이렇게 한다.

# 네트워크 생성
docker network create mynet

# 같은 네트워크에 컨테이너 두 개 띄우기
docker run -d --name db --network mynet mysql:8
docker run -d --name app --network mynet node:20

같은 네트워크 안에서는 컨테이너 이름으로 서로 접근할 수 있다. app 컨테이너에서 db:3306으로 접속하면 된다. IP 주소 외울 필요 없다.


Dockerfile — 이미지 만드는 레시피

남이 만든 이미지만 쓸 수는 없다. 내 앱을 이미지로 만들려면 Dockerfile을 작성해야 한다.

기본 구조

# 베이스 이미지 선택
FROM node:20-alpine

# 작업 디렉토리 설정
WORKDIR /app

# 의존성 파일 먼저 복사 (캐시 활용)
COPY package*.json ./

# 의존성 설치
RUN npm ci --production

# 소스코드 복사
COPY . .

# 포트 명시 (문서화 목적, 실제 열리지는 않음)
EXPOSE 3000

# 실행 명령
CMD ["node", "server.js"]

레이어 캐시 — 이거 모르면 빌드가 느리다

Dockerfile의 각 명령어는 하나의 레이어가 된다. 변경된 레이어부터 아래는 전부 다시 빌드된다. 그래서 자주 바뀌는 것은 아래에, 안 바뀌는 것은 위에 두는 게 중요하다.

# ❌ 나쁜 예 — 소스 바꿀 때마다 npm install도 다시 함
COPY . .
RUN npm ci

# ✅ 좋은 예 — package.json 안 바뀌면 npm install 캐시 적중
COPY package*.json ./
RUN npm ci
COPY . .

이 차이를 모르면 소스 한 줄 고칠 때마다 npm ci가 돌아간다. 프로젝트 크면 빌드에 몇 분씩 잡아먹는다.

멀티스테이지 빌드 — 이미지 크기 줄이기

프론트엔드 프로젝트에서 특히 유용하다.

# 1단계: 빌드
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 2단계: 실행 (빌드 결과물만 가져옴)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

빌드에 필요한 Node.js, node_modules 같은 건 최종 이미지에 안 들어간다. 이미지 크기가 1GB에서 50MB로 줄어드는 경우도 있다.


Docker Compose — 여러 컨테이너 한 방에 관리

실무에서는 앱 하나에 컨테이너 여러 개를 쓰는 경우가 대부분이다. 웹서버, DB, 캐시, 큐 워커... 이걸 하나씩 docker run으로 띄우면 미친다. Docker Compose가 이걸 해결해준다.

실전 예제 — 웹 앱 + DB + Redis

# docker-compose.yml (또는 compose.yaml)
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    volumes:
      - .:/app
      - /app/node_modules

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:
# 전부 띄우기
docker compose up -d

# 로그 보기
docker compose logs -f app

# 전부 내리기
docker compose down

# 볼륨까지 삭제 (DB 데이터도 날아감!)
docker compose down -v

depends_on과 healthcheck

depends_on만 쓰면 컨테이너 시작 순서만 보장하지, 실제로 서비스가 준비됐는지는 확인 안 한다. DB 컨테이너가 뜨긴 했는데 아직 커넥션 안 받는 상태에서 앱이 접속 시도하면 에러가 뜬다.

위 예제처럼 healthcheck + condition: service_healthy 조합을 쓰면, DB가 진짜 준비된 다음에 앱이 뜬다. 이거 안 하면 앱 시작할 때 DB 접속 실패로 크래시 나는 일이 생긴다.


자주 쓰는 Docker 명령어

매일 치는 것들 위주로 정리.

컨테이너 관리

# 실행 중인 컨테이너 목록
docker ps

# 전체 (중지된 것 포함)
docker ps -a

# 컨테이너 중지
docker stop <컨테이너ID 또는 이름>

# 컨테이너 삭제
docker rm <컨테이너ID>

# 중지된 컨테이너 일괄 삭제
docker container prune

# 컨테이너 안에 들어가기 (디버깅할 때 필수)
docker exec -it <컨테이너> /bin/sh
# 또는
docker exec -it <컨테이너> bash

docker exec -it은 정말 많이 쓴다. 컨테이너 안에서 뭐가 어떻게 돌아가는지 직접 확인하고 싶을 때, DB에 직접 접속하고 싶을 때, 로그 파일 확인하고 싶을 때.

이미지 관리

# 로컬 이미지 목록
docker images

# 이미지 삭제
docker rmi <이미지ID>

# 사용 안 하는 이미지 일괄 삭제
docker image prune

# 빌드
docker build -t myapp:latest .

# 태그 붙이기
docker tag myapp:latest myregistry.com/myapp:v1.0

디스크 정리 — 이거 안 하면 디스크 꽉 찬다

Docker 오래 쓰면 이미지, 컨테이너, 볼륨, 빌드 캐시가 쌓여서 디스크를 엄청 먹는다. 나는 한 번은 맥북 용량 50GB가 Docker한테 먹혀있던 적도 있다.

# Docker가 먹고 있는 디스크 확인
docker system df

# 안 쓰는 것 전부 정리 (컨테이너, 네트워크, 이미지, 빌드 캐시)
docker system prune

# 볼륨까지 정리 (주의! 데이터 날아감)
docker system prune --volumes

# 진짜 다 날리기 (핵폭탄 옵션)
docker system prune -a --volumes

⚠️ prune -a는 사용 중이 아닌 모든 이미지를 삭제한다. 다시 pull 받아야 하니까 네트워크 느린 환경에서는 신중하게.


Docker Desktop GUI 활용

CLI만 써도 되지만, Docker Desktop GUI가 은근 편한 것들이 있다.

Containers 탭

실행 중인 컨테이너 목록, 로그, 터미널 접속, 환경 변수 확인을 한 화면에서 할 수 있다. docker logs + docker exec + docker inspect를 GUI로 하는 거라고 보면 된다. 로그를 실시간으로 보면서 필터링하는 건 CLI보다 GUI가 편하다.

Images 탭

로컬 이미지 관리. 이미지 크기 확인하고, 안 쓰는 이미지 정리할 때 좋다. 각 이미지의 레이어 구조도 볼 수 있는데, Dockerfile 최적화할 때 참고가 된다.

Volumes 탭

볼륨 목록이랑 사용량 확인. 어떤 볼륨이 어떤 컨테이너에 붙어있는지 한눈에 보인다.

Dev Environments (개발 환경)

Git 레포 URL 넣으면 자동으로 개발 환경을 만들어주는 기능인데, 솔직히 나는 잘 안 쓴다. docker compose가 더 익숙해서. 근데 팀에 Docker 처음 접하는 사람 있으면 온보딩할 때 괜찮긴 하다.

Kubernetes 원클릭

Settings > Kubernetes > Enable Kubernetes 체크하면 끝. 로컬에서 K8s 환경을 바로 쓸 수 있다. minikube 같은 거 따로 설치 안 해도 된다. K8s 학습하거나 매니페스트 테스트할 때 편하다.


실무 팁 모음

.dockerignore 필수

.gitignore처럼 빌드 컨텍스트에서 제외할 파일을 지정한다. 이거 안 하면 node_modules.git 폴더가 이미지에 들어가서 이미지가 쓸데없이 커진다.

# .dockerignore
node_modules
.git
.env
.env.local
*.log
dist
.DS_Store

환경변수 관리

# docker-compose.yml에서 .env 파일 사용
services:
  app:
    image: myapp
    env_file:
      - .env
# .env
DATABASE_URL=postgresql://user:pass@db:5432/myapp
JWT_SECRET=my-super-secret-key
REDIS_URL=redis://cache:6379

.env 파일은 .gitignore에 넣고, .env.example만 커밋하자. 비밀번호가 깃 히스토리에 남으면 골치 아프다.

로그 관리

컨테이너 로그가 무한으로 쌓이면 디스크를 다 먹는다. 운영 환경이면 로그 드라이버를 설정하자.

services:
  app:
    image: myapp
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

이러면 로그 파일이 10MB씩 최대 3개까지만 유지된다.

빌드 속도 올리기

# BuildKit 활성화 (Docker Desktop은 기본 활성화)
# 환경변수로도 강제 가능
# DOCKER_BUILDKIT=1 docker build .

# 캐시 마운트로 패키지 매니저 캐시 재활용
RUN --mount=type=cache,target=/root/.npm \
    npm ci --production

BuildKit의 캐시 마운트를 쓰면 npm cipip install 같은 패키지 설치가 캐시 덕분에 빨라진다.


흔한 트러블슈팅

포트 충돌

Error: Bind for 0.0.0.0:3000 failed: port is already allocated

로컬에서 이미 3000번 포트를 쓰고 있는 거다. 다른 컨테이너가 쓰고 있거나, 로컬에 직접 띄운 앱이 있거나.

# 어떤 프로세스가 포트 쓰는지 확인 (macOS/Linux)
lsof -i :3000

# 다른 포트로 매핑
docker run -p 3001:3000 myapp

권한 문제 (Linux)

Permission denied

Linux에서 Docker 명령어에 sudo를 붙여야 하는 경우. docker 그룹에 사용자를 추가하면 해결된다.

sudo usermod -aG docker $USER
# 로그아웃 후 다시 로그인

컨테이너가 바로 종료됨

# 로그 확인
docker logs <컨테이너ID>

# 대화형으로 띄워서 디버깅
docker run -it myapp /bin/sh

CMD나 ENTRYPOINT가 잘못됐거나, 앱이 크래시 나는 경우가 대부분이다. 로그를 먼저 보자.

Docker Desktop이 느릴 때

macOS에서 Docker Desktop이 CPU나 메모리를 너무 잡아먹으면 Settings > Resources에서 조절할 수 있다.

기본 추천 설정:
- CPUs: 전체 코어의 절반
- Memory: 전체 RAM의 25~50%
- Disk image size: 필요한 만큼 (기본 60GB)

특히 파일 I/O가 느린 문제가 있다면 (macOS에서 흔함), docker-compose.yml에서 볼륨 마운트에 캐시 옵션을 추가해볼 수 있다.

volumes:
  - .:/app:cached

Docker Desktop 라이선스 얘기

이건 한번 짚고 넘어가야 한다. 2021년부터 Docker Desktop은 대기업(직원 250명 이상 또는 연매출 $10M 이상)에서 유료다. 개인, 소규모 기업, 교육, 오픈소스 프로젝트는 무료.

회사에서 쓰는데 라이선스가 걸린다면 대안도 있다.

  • Rancher Desktop — 무료, K8s 통합이 강점
  • Podman Desktop — Red Hat에서 만든 거, Docker CLI 호환
  • Colima — macOS에서 CLI 기반으로 Docker 돌리기

근데 솔직히 Docker Desktop의 편의성을 따라오는 건 아직 없다고 느낀다. 라이선스 문제가 없다면 Docker Desktop이 제일 낫다.


마무리

Docker Desktop은 단순히 Docker를 GUI로 쓰는 것 이상이다. 로컬 개발 환경 통일, Kubernetes 테스트, 빠른 프로토타이핑까지. 한번 익숙해지면 없이 개발하는 게 오히려 불편해진다.

핵심 정리:

  • 이미지는 설계도, 컨테이너는 실행 인스턴스. 이 구분만 확실히 하자
  • Dockerfile은 레이어 캐시를 고려해서 작성하자 (자주 바뀌는 건 아래에)
  • Docker Compose는 실무에서 거의 필수다. 단일 컨테이너만 쓸 일은 별로 없다
  • 볼륨 안 붙이면 데이터 날아간다. DB는 반드시 볼륨 설정하자
  • docker system prune으로 주기적으로 디스크 정리하자
  • .dockerignore랑 멀티스테이지 빌드로 이미지 크기를 줄이자

다음에는 Docker + CI/CD 파이프라인 구성이나, 프로덕션 배포 전략(Blue-Green, Rolling Update) 같은 주제도 다뤄볼 생각이다.