PUBLISHED

docker

작성일: 2025.04.17

docker

현대의 개발 환경에서 도커(Docker)는 빠르게 개발하고 배포할 수 있는 핵심 도구로 자리 잡았다. 도커를 사용하면 애플리케이션 실행 환경을 컨테이너라는 독립된 단위로 포장할 수 있으며, 이를 통해 운영체제에 관계없이 동일한 환경에서 애플리케이션을 실행할 수 있다. 이러한 장점 덕분에 개발, 테스트, 배포 전반에 걸쳐 일관성을 유지할 수 있으며, 특히 팀 협업 시 환경 차이로 인한 문제를 크게 줄일 수 있다.

맥에서의 도커 설치

macOS 사용자는 일반적으로 Docker Desktop을 통해 도커 환경을 구성하지만, Docker Desktop은 과도한 리소스 사용, 기업 사용자에 대한 라이선스 비용, 불편한 자동 업데이트, 그리고 파일 시스템 공유 시의 성능 저하 등 여러 가지 단점을 안고 있다. 이러한 문제 때문에 보다 가볍고 유연한 대안으로 Colima가 주목받고 있다.

Colima는 macOS와 Linux에서 도커 호환 환경을 실행할 수 있도록 해주는 경량 가상 머신 관리자이다. 내부적으로는 Lima를 기반으로 하며, Docker CLI와 완벽히 호환된다. GUI 없이 터미널에서만 작동하는 방식이기 때문에 시스템 자원을 적게 사용하며, 개발자가 설정을 세밀하게 조정할 수 있다는 장점이 있다.

untitled
PY
# Docker CLI 설치
brew install docker

# Colima 설치
brew install colima

# Colima 시작 (기본 설정)
colima start

# 리소스를 지정하고 싶을 경우
colima start --cpu 4 --memory 4 --disk 60

# Colima 종료
colima stop

이미지 관리

도커에서 이미지(Image)란 컨테이너 실행에 필요한 파일 시스템과 설정 정보를 포함한 읽기 전용 템플릿이다. 애플리케이션 코드, 라이브러리, 의존성, 환경 변수, 실행 명령어 등이 포함되어 있으며, 컨테이너는 이 이미지를 기반으로 실행된다. 쉽게 말해, 이미지는 컨테이너의 '설계도' 역할을 한다.

Dockerfile을 작성하여 직접 이미지를 생성할 수 있으며, 도커 허브(Docker Hub)와 같은 공개 저장소에서 다른 사람이 만든 이미지를 가져다 쓸 수도 있다. 이를 통해 필요한 환경을 빠르게 구성하거나, 공통된 베이스 이미지를 활용하여 일관된 개발 환경을 유지할 수 있다.

untitled
SH
# 이미지 다운로드
$ docker pull 이미지명[:태그명]

# 다운받은 모든 이미지 조회
$ docker image ls

이미지를 삭제할 때는 몇 가지 사항을 고려해야 한다. 우선 해당 이미지로부터 생성된 컨테이너가 존재하는지 확인해야 하며, 그 컨테이너가 실행 중이라면 이미지를 바로 삭제할 수 없다. 실행 중인 컨테이너가 있다면 먼저 컨테이너를 중지하고 삭제해야 이미지를 제거할 수 있다. 또한, 동일한 이미지를 기반으로 여러 컨테이너가 만들어졌을 수도 있기 때문에, 사용 중인 모든 컨테이너를 확인한 후 삭제를 진행하는 것이 안전하다.

untitled
CSS
# [특정 이미지 삭제]
$ docker image rm [이미지 ID 또는 이미지명]

# [중지된 컨테이너에서 사용하고 있는 이미지 강제 삭제하기]
$ docker image rm -f [이미지 ID 또는 이미지명]

# 컨테이너에서 사용하고 있지 않은 이미지만 전체 삭제
$ docker image rm $(docker images -q)

# 컨테이너에서 사용하고 있는 이미지를 포함해서 전체 이미지 삭제
$ docker image rm -f $(docker images -q)

도커 파일Dockerfile

도커파일(Dockerfile)은 도커 이미지를 자동으로 생성하기 위한 설정 파일로, 이미지 생성에 필요한 명령어들을 단계별로 정의한다. 보통 FROM, RUN, COPY, CMD 등의 명령어를 사용하여 어떤 베이스 이미지에서 시작할지, 어떤 패키지를 설치할지, 어떤 파일을 복사할지, 어떤 명령어를 실행할지를 기술한다.

이렇게 작성된 도커파일은 docker build 명령어를 통해 실제 도커 이미지로 빌드되며, 이를 통해 동일한 이미지를 여러 환경에서 일관되게 재현할 수 있다. 도커파일을 활용하면 수동 설정 없이 코드와 환경 구성을 함께 버전 관리할 수 있어, 배포 자동화와 협업에 매우 유리하다.

untitled
SH
# 도커 파일을 이미지로 빌드
$ docker build -t [이미지명]:[태그명] [도커 파일이 위치한 경로]

# FROM은 베이스 이미지를 생성
$ FROM [이미지명]:[태그명]

# COPY는 호스트 컴퓨터에 있는 파일을 복사해서 컨테이너로 전달한다.
$ COPY [호스트 컴퓨터에 있는 복사할 파일의 경로] [컨테이너에서 파일이 위치할 경로]
$ COPY app.txt /app.txt
$ COPY my-app /my-app/

# ENTRYPOINT는 컨테이너가 생성되고 최초로 실행할 때 수행되는 명령어
$ ENTRYPOINT ["node", "dist/main.js"]

# RUN은 이미지 생성 과정에서 명령어를 실행시켜야 할 때 사용
$ RUN [명령문]

RUN은 ‘이미지 생성 과정’에서 필요한 명령어를 실행시킬 때 사용하고, ENTRYPOINT는 생성된 이미지를 기반으로 컨테이너를 생성한 직후에 명령어를 실행시킬 때 사용한다.


# WORKDIR으로 작업 디렉터리를 전환하면 그 이후에 등장하는 모든 명령문은 해당 디렉터리를 기준으로 실행
$ WORKDIR [작업 디렉토리로 사용할 절대 경로]

# EXPOSE는 컨테이너 내부에서 어떤 포트에 프로그램이 실행되는 지를 문서화
$ EXPOSE [포트 번호]

컨테이너

컨테이너(Container)란 이미지를 실행한 결과로 만들어지는 독립된 실행 환경이다. 컨테이너는 이미지의 내용을 기반으로 애플리케이션을 실행하며, 격리된 프로세스와 파일 시스템을 가진다. 호스트 운영체제 위에서 직접 실행되기 때문에 가볍고 빠르며, 필요한 리소스만을 사용하여 효율적으로 동작한다. 개발자는 컨테이너를 통해 실제 배포 환경과 유사한 조건에서 애플리케이션을 실행하고 테스트할 수 있다.

도커에서는 이미지를 기반으로 컨테이너를 생성한 뒤 실행하는 과정을 단계별로 나눠서 처리할 수 있다. 예를 들어 docker create 명령어로 컨테이너를 생성하고, docker start 명령어로 실행하는 식이다. 하지만 대부분의 경우에는 docker run 명령어를 통해 생성과 실행을 한 번에 처리하는 방식이 더 편리하다. docker run은 이미지가 로컬에 없을 경우 자동으로 다운로드까지 수행하며, 필요한 설정과 함께 컨테이너를 곧바로 실행해준다. 이 방식은 빠르게 테스트하거나 간단한 서비스를 실행할 때 유용하게 사용된다.

untitled
CSS
# 컨테이너 생성
$ docker create 이미지명[:태그명]

# 컨테이너 실행
$ docker start 컨테이너명[또는 컨테이너 ID]

# 이미지를 바탕으로 컨테이너를 생성한 뒤, 컨테이너 실행
$ docker run 이미지명[:태그명] # 포그라운드 실행
$ docker run -d 이미지명[:태그명] # 백그라운드 실행

# 컨테이너에 이름 붙여서 실행
$ docker run -d --name 컨테이너명 이미지명[:태그명]

# 포트 연결
$ docker run -d -p [호스트 포트]:[컨테이너 포트] 이미지명[:태그명]

untitled
SH
# 모든 컨테이너 조회
$ docker ps -a

# 컨테이너 정상 종료
$ docker stop 컨테이너명[또는 컨테이너 ID]

# 컨테이너 강제 종료
$ docker kill 컨테이너명[또는 컨테이너 ID]

# 컨테이너 삭제
docker rm 컨테이너명[또는 컨테이너 ID]

# 컨테이너 종료 + 삭제
docker rm -f 컨테이너명[또는 컨테이너 ID]

# 중지되어 있는 모든 컨테이너 삭제
$ docker rm $(docker ps -qa)

# 실행되고 있는 모든 컨테이너 삭제
$ docker rm -f $(docker ps -qa)

도커에서 컨테이너를 백그라운드 모드(Detached mode)로 실행하면 터미널에 출력이 보이지 않기 때문에, 애플리케이션의 동작 상태나 로그를 확인하려면 별도의 명령어를 사용해야 한다. 이때 docker logs <컨테이너 ID 또는 이름> 명령어를 통해 해당 컨테이너의 표준 출력(stdout)과 표준 에러(stderr) 로그를 조회할 수 있다. 실시간으로 로그를 확인하고 싶다면 -f 옵션을 함께 사용하여 로그 스트림을 모니터링할 수 있다. 이러한 방식은 서버 애플리케이션의 상태를 점검하거나 디버깅할 때 유용하게 활용된다.

untitled
CSS
# 특정 컨테이너의 모든 로그 조회
$ docker logs [컨테이너 ID 또는 컨테이너명]
 
# 최근 로그 x줄만 조회
$ dokcer logs --tail [로그 끝부터 표시할 줄 수] [컨테이너 ID 또는 컨테이너명]
 
# 기존 로그 조회 + 생성되는 로그를 실시간으로 보고 싶은 경우
$ docker logs -f [컨테이너 ID 또는 컨테이너명]
 
# 기존 로그는 조회하지 않기 + 생성되는 로그를 실시간으로 보고 싶은 경우
$ docker logs --tail 0 -f [컨테이너 ID 또는 컨테이너명]

도커에서는 실행 중인 컨테이너 내부에 직접 접속하여 셸 명령어를 실행할 수 있다. 일반적으로 docker exec -it <컨테이너 ID 또는 이름> /bin/bash 또는 /bin/sh 명령어를 사용하며, 이를 통해 컨테이너 내부의 파일 시스템을 확인하거나 실행 중인 프로세스를 점검할 수 있다. 특히 디버깅이나 설정 확인이 필요할 때 유용하게 쓰이며, 컨테이너가 격리된 환경에서 동작하기 때문에 실제 배포 환경과 유사한 조건에서 문제를 분석할 수 있다.

untitled
PY
# 컨테이너 내부에 bash 셸로 접속
$ docker exec -it [컨테이너 ID 또는 컨테이너명] /bin/bash

# bash가 없을 때 sh 셸로 접속하기
$ docker exec -it [컨테이너 ID 또는 컨테이너명] /bin/sh

도커 볼륨

도커 볼륨(Volume)은 컨테이너와 호스트 간에 데이터를 안전하고 효율적으로 공유하기 위한 저장소이다. 컨테이너 내부의 파일 시스템은 기본적으로 휘발성이기 때문에, 컨테이너가 삭제되면 데이터도 함께 사라진다. 하지만 볼륨을 사용하면 컨테이너가 삭제되어도 데이터가 유지되어 중요한 데이터 보존이나 로그 저장 등에 매우 유용하다. 또한, 볼륨은 호스트와 독립적으로 관리되며 여러 컨테이너 간에 데이터를 공유할 수도 있다.

untitled
PY
# 볼륨 생성
$ docker volume create [볼륨명]

# 생성한 볼륨을 컨테이너에 마운트하여 실행
$ docker run -v [볼륨명]:[컨테이너의 디렉토리 절대경로] [이미지명]:[태그명]

# 일반 디렉토리도 docker volume으로 연결 가능(바인드 마운트)
$ docker run -v [호스트의 디렉토리 절대경로]:[컨테이너의 디렉토리 절대경로] [이미지명]:[태그명]

도커 컴포즈

도커 컴포즈(Docker Compose)는 여러 개의 컨테이너로 구성된 애플리케이션을 하나의 설정 파일로 정의하고, 이를 한 번에 실행·관리할 수 있도록 도와주는 도구이다. 웹 서버, 애플리케이션 서버, 데이터베이스처럼 서로 의존 관계를 가지는 서비스들을 개별적으로 docker run 명령어로 관리하는 것은 번거롭고 실수의 여지가 많다. 도커 컴포즈는 이러한 문제를 해결하기 위해 등장했다.

도커 컴포즈는 docker-compose.yml 또는 compose.yml 파일을 사용하며, 이 파일 안에 각 서비스의 이미지, 포트 매핑, 볼륨, 환경 변수, 네트워크 설정 등을 선언적으로 작성한다. 이를 통해 전체 애플리케이션 구성을 코드로 관리할 수 있고, 동일한 환경을 누구나 쉽게 재현할 수 있다. 특히 로컬 개발 환경과 CI 환경, 테스트 환경을 통일하는 데 매우 효과적이다.

도커 컴포즈를 사용하면 여러 컨테이너를 하나의 프로젝트 단위로 다룰 수 있으며, up, down 같은 단순한 명령어로 전체 서비스를 한 번에 기동하거나 종료할 수 있다. 이로 인해 개발자는 개별 컨테이너의 생명주기 관리보다는 애플리케이션 자체에 집중할 수 있다.

untitled
SH
version: "3.9"

services:
  app:
    image: node:18
    container_name: my-app
    ports:
      - "3000:3000"
    volumes:
      - .:/app
    working_dir: /app
    command: npm run dev

services 아래에 각각의 컨테이너를 정의하며, 서비스 이름은 컨테이너 간 통신 시 호스트 이름으로도 사용된다. image 대신 build 옵션을 사용하면 Dockerfile을 기반으로 이미지를 직접 빌드할 수도 있다.

웹 애플리케이션과 데이터베이스를 함께 실행하는 예시는 다음과 같다.

untitled
TS
version: "3.9"

services:
  server:
    build: .
    container_name: api-server
    ports:
      - "4000:4000"
    depends_on:
      - db

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

volumes:
  db-data:

depends_on 옵션을 사용하면 서비스 간의 의존 관계를 명시할 수 있으며, 데이터베이스와 같이 상태를 유지해야 하는 서비스에는 볼륨을 함께 사용하는 것이 일반적이다.

도커 컴포즈는 단순히 여러 컨테이너를 묶는 도구를 넘어, 애플리케이션 아키텍처를 명확하게 문서화하는 역할도 한다. 서비스 간 관계와 실행 환경이 설정 파일로 명시되기 때문에, 프로젝트를 처음 접하는 개발자도 전체 구조를 빠르게 이해할 수 있다. 이러한 특성 덕분에 도커 컴포즈는 로컬 개발 환경 구성부터 간단한 배포 자동화까지 폭넓게 활용되고 있다.