비전공자도 이해할 수 있는 Docker 입문/실전 강의를 바탕으로 작성했습니다.
실전에서 사용하는 거의 모든 내용을 담았으므로 목차에서 필요한 부분만 보시길 권장드립니다.
📄 목차 바로가기
현업에서 Docker를 왜 많이 사용할까요?
Docker를 쓰는 이유에는 여러 가지 장점이 있지만 가장 핵심적인 장점은 이식성 입니다.
이식성이란?
특정 프로그램을 다른 곳으로 쉽게 옮겨서 설치 및 실행할 수 있는 특성
예를 들어 봅시다.
A의 컴퓨터에 MySQL을 깔고 정상 작동하는 것을 확인했습니다. 그런데 B의 컴퓨터에 MySQL을 깔았더니 에러가 발생했습니다. 왜 이런 상황이 발생할까요?
B의 컴퓨터에 에러가 발생하는 이유는 다양합니다. A와 다른 버전을 설치했거나, 운영체제가 다르거나, B의 컴퓨터에 깔려있는 다른 프로그램과 충돌이 일어났거나와 같은 다양한 이유로 프로그램이 정상적으로 설치되지 않을 수 있습니다. 에러를 해결하기 위해서는 복잡한 과정을 거쳐야 합니다.
이러한 상황을 해결해주는 도구가 Docker 입니다. Docker를 사용하면 명령어 한 줄로 어떤 컴퓨터에든 MySQL을 에러 없이 설치하고 실행할 수 있습니다.
Docker의 장점으로는 이식성 말고도 또 무엇이 있을까요?
컨테이너를 사용하여 각각의 프로그램을 분리된 환경에서 실행 및 관리할 수 있는 툴입니다.
위에서 말한 분리된 환경을 컨테이너라고 부릅니다.
Docker를 통해 하나의 컴퓨터 환경에서 독립적인 컴퓨터 환경을 구성해서 각 환경에 프로그램을 별도로 설치할 수 있습니다.

'컨테이너'와 '컨테이너를 포함하고 있는 컴퓨터'를 구분하기 위해 컨테이너를 포함하고 있는 컴퓨터를 '호스트 컴퓨터'라고 부릅니다.
이미지는 컨테이너를 정의하는 읽기 전용 템플릿입니다.
이미지는 프로그램을 실행하는 데 필요한 설치 과정, 설정, 버전 정보 등 프로그램을 실행하는 데 필요한 모든 것을 포함하고 있습니다.
자주 사용하는 명령어만 작성했습니다.
도커의 전체적인 개념 및 흐름을 보고 싶다면 명령어는 나중에 보시고, 밑에 있는 Dockerfile, 볼륨, 컴포즈 부터 보시는 것을 추천드립니다.
이미지를 다운로드 할 때 Dockerhub라는 곳에서 이미지를 다운받습니다.
Dockerhub은 Github처럼 이미지를 저장 및 다운받을 수 있는 저장소 역할을 합니다.
# docker pull 이미지명
$ docker pull nginx # docker pull nginx:latest와 동일하게 작동
특정 버전을 나타내는 이름을 태그명이라고 합니다. 태그명은 Dockerhub에서 확인할 수 있습니다.
# docker pull 이미지명:태그명
$ docker pull nginx:stable-perl
$ docker image ls
ls : list의 약자$ docker image rm [이미지 ID 또는 이미지명]
rm : remove의 약자$ docker image rm -f [이미지 ID 또는 이미지명]
# 컨테이너에서 사용하고 있지 않은 이미지만 전체 삭제
$ docker image rm $(docker images -q)
# 컨테이너에서 사용하고 있는 이미지를 포함해서 전체 이미지 삭제
$ docker image rm -f $(docker images -q)
docker images -q : 시스템에 있는 모든 이미지의 ID를 반환합니다. 여기서 -q 옵션은 quite를 의미하며, 상세 정보 대신에 각 이미지의 고유한 ID만 표시하도록 지시합니다.이미지를 바탕으로 컨테이너를 생성합니다. 다음 명령어는 컨테이너를 생성만 하고 실행시키지는 않습니다.
# docker create 이미지명[:태그명]
$ docker create nginx
$ docker ps -a # 모든 컨테이너 조회
docker pull)받아서 컨테이너를 생성합니다.# docker start 컨테이너명[또는 컨테이너 ID]
$ docker start 컨테이너명[또는 컨테이너 ID]
$ docker ps # 실행중인 컨테이너 조회
# Nginx 컨테이너 중단 후 삭제하기
$ docker ps # 실행 중인 컨테이너 조회
$ docker stop {nginx를 실행시킨 Contnainer ID} # 컨테이너 중단
$ docker rm {nginx를 실행시킨 Contnainer ID} # 컨테이너 삭제
$ docker image rm nginx # Nginx 이미지 삭제
다음 명령어는 이미지를 바탕으로 컨테이너를 생성한 뒤, 컨테이너를 실행까지 시킨킵니다. (처음에 이미지를 바탕으로 컨테이너를 실행시키고 싶을 때, 이 명령어를 자주 사용합니다.)
# docker run 이미지명[:태그명]
$ docker run nginx # 포그라운드에서 실행 (추가적인 명령어 조작을 할 수가 없음)
# Ctrl + C로 종료할 수 있음
docker pull)받아서 실행시킵니다.docker pull 명령어를 활용해야 합니다.# docker run -d 이미지명[:태그명]
$ docker run -d nginx
# Nginx 컨테이너 중단 후 삭제하기
$ docker ps # 실행 중인 컨테이너 조회
$ docker stop {nginx를 실행시킨 Contnainer ID} # 컨테이너 중단
$ docker rm {nginx를 실행시킨 Contnainer ID} # 컨테이너 삭제
$ docker image rm nginx # Nginx 이미지 삭제
포그라운드(foreground) vs 백그라운드(background)
포그라운드는 내가 실행시킨 프로그램의 내용이 화면에서 실행되고 출력되는 상태를 뜻합니다. 그러다보니 포그라운드 상태에서는 다른 프로그램을 조작할 수가 없습니다.
백그라운드는 내가 실행시킨 프로그램이 컴퓨터 내부적으로 실행되는 상태를 의미합니다. 그래서 프로그램이 어떻게 실행되고 있는 지에 대한 정보를 화면에서 확인할 수 없습니다. 이런 특성 때문에 다른 명령어를 추가로 입력할 수도 있고, 새로운 프로그램을 조작할 수도 있습니다.
# docker run -d --name [컨테이너 이름] 이미지명[:태그명]
$ docker run -d --name my-web-server nginx
# Nginx 컨테이너 중단 후 삭제하기
$ docker ps # 실행 중인 컨테이너 조회
$ docker stop {nginx를 실행시킨 Contnainer ID} # 컨테이너 중단
$ docker rm {nginx를 실행시킨 Contnainer ID} # 컨테이너 삭제
$ docker image rm nginx # Nginx 이미지 삭제
# docker run -d -p [호스트 포트]:[컨테이너 포트] 이미지명[:태그명]
$ docker run -d -p 4000:80 nginx

docker run -p 4000:80 라고 명령어를 입력하게 되면, 도커를 실행하는 호스트의 4000번 포트를 컨테이너의 80번 포트로 연결하도록 설정합니다.$ docker ps # 실행 중인 컨테이너들만 조회
$ docker ps -a # 모든 컨테이너 조회(작동 중인 컨테이너 + 작동을 멈춘 컨테이너)
ps : process status의 약자a: all의 약자$ docker stop 컨테이너명[또는 컨테이너 ID]
$ docker kill 컨테이너명[또는 컨테이너 ID]
stop은 시스템 종료 버튼을 통해 정상적으로 컴퓨터를 종료하는 걸 의미하고, kill은 본체 버튼을 눌러 무식하게 종료하는 걸 의미합니다.$ docker rm 컨테이너명[또는 컨테이너 ID] # 중지되어 있는 특정 컨테이너 삭제
$ docker rm -f 컨테이너명[또는 컨테이너 ID] # 실행되고 있는 특정 컨테이너 삭제
$ docker rm $(docker ps -qa) # 중지되어 있는 모든 컨테이너 삭제
$ docker rm **-f** $(docker ps -qa) # 실행되고 있는 모든 컨테이너 삭제
컨테이너를 실행시키고나서 실행시킨 컨테이너가 잘 실행되고 있는 지, 에러가 발생한 건 아닌 지 로그를 확인할 수 있어야 합니다. 디버깅할 때 필수로 확인해야 하는 게 로그입니다. 지금부터 컨테이너에서 발생한 로그는 어떻게 확인하는 지 알아봅시다.
# docker logs [컨테이너 ID 또는 컨테이너명]
$ docker run -d nginx
$ docker logs [nginx가 실행되고 있는 컨테이너 ID]
# dokcer logs --tail [로그 끝부터 표시할 줄 수] [컨테이너 ID 또는 컨테이너명]
$ dokcer logs --tail 10 [컨테이너 ID 또는 컨테이너명]
# docker logs -f [컨테이너 ID 또는 컨테이너명]
# Nginx의 컨테이너에 실시간으로 쌓이는 로그 확인하기
$ docker run -d -p 80:80 nginx
$ docker logs -f
f : follow의 약어$ docker logs **--tail 0 -f** [컨테이너 ID 또는 컨테이너명]
# docker exec -it 컨테이너명[또는 컨테이너 ID] bash
$ docker run -d nginx
$ docker exec -it [Nginx가 실행되고 있는 컨테이너 ID] bash
$ ls # 컨테이너 내부 파일 조회
$ cd /etc/nginx
$ cat nginx.conf
Ctrl + D 또는 exit을 입력하면 됩니다.bash : 쉘(Shell)의 일종-it : -it옵션을 사용해야 명령어를 입력하고 결과를 확인할 수 있습니다. -it옵션을 적지 않으면 명령어를 1번만 실행시키고 종료되어 버립니다. 즉, -it 옵션을 적어야 계속해서 명령어를 입력할 수 있습니다.
Docker 볼륨이란 도커 컨테이너에서 데이터를 영속적으로 저장하기 위한 방법입니다.
볼륨(Volume)은 컨테이너 자체의 저장 공간을 사용하지 않고, 호스트 자체의 저장 공간을 공유해서 사용하는 형태입니다.

$ docker run -v [호스트의 디렉토리 절대경로]:[컨테이너의 디렉토리 절대경로] [이미지명]:[태그명]
⚠️ 볼륨을 사용할 때는 주의해야할 점이 있습니다.
호스트의 디렉토리 절대 경로에 디렉토리가 존재하지 않을 경우, 호스트의 디렉터리 절대 경로에 디렉터리를 새로 만들고 컨테이너의 디렉터리에 있는 파일들을 호스트의 디렉터리로 복사해옵니다.

호스트의 디렉토리 절대 경로에 디렉토리가 이미 존재할 경우, 호스트의 디렉터리가 컨테이너의 디렉터리를 덮어씌웁니다.

$ cd /Users/yereumi/Documents/Develop
$ mkdir docker-mysql # MySQL 데이터를 저장하고 싶은 폴더 만들기
# docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -v {호스트의 절대경로}/mysql_data:/var/lib/mysql -d mysql
$ docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -v /Users/yereumi/Documents/Develop/docker-mysql/mysql_data:/var/lib/mysql -d mysql

주의) mysql_data 디렉토리를 미리 만들어 놓으면 안됩니다. 그래야 처음 이미지를 실행시킬 때 mysql 내부에 있는 /var/lib/mysql 파일들을 호스트 컴퓨터로 공유받을 수 있습니다. mysql_data 디렉토리를 미리 만들어놓을 경우, 기존 컨테이너의 /var/lib/mysql 파일들을 전부 삭제한 뒤에 mysql_data로 덮어씌워 버립니다.
DB에 관련된 데이터가 저장되는 곳이 /var/lib/mysql 인지는 Dockerhub MySQL의 공식 문서에 나와있습니다.
Dockerfile이란 Docker 이미지를 만들게 해주는 파일입니다.
# FROM - 베이스 이미지를 생성하는 역할
FROM [이미지명]
FROM [이미지명]:[태그명]
# COPY - 호스트 컴퓨터에 있는 파일을 복사해서 컨테이너로 전달
COPY [호스트 컴퓨터에 있는 복사할 파일의 경로] [컨테이너에서 파일이 위치할 경로]
# ENTRYPOINT - 컨테이너가 생성되고 최초로 실행할 때 수행되는 명령어
ENTRYPOINT [명령문...]
# RUN - 이미지 생성 과정에서 명령어를 실행시켜야 할 때 사용
RUN [명령문]
# WORKDIR - 작업 디렉토리를 지정
WORKDIR [작업 디렉토리로 사용할 절대 경로]
# EXPOSE - 컨테이너 내부에서 사용 중인 포트를 문서화하기
EXPOSE [포트 번호]
FROM openjdk:17-jdk
COPY build/libs/*SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
$ ./gradlew clean build
$ docker build -t hello-server .
$ docker image ls

$ docker run -d -p 8080:8080 hello-server
$ docker ps


Docker Compose란 여러 개의 Docker 컨테이너들을 하나의 서비스로 정의하고 구성해 하나의 묶음으로 관리할 수 있게 도와주는 툴입니다.
여러 개의 컨테이너로 이루어진 복잡한 애플리케이션을 한 번에 관리할 수 있게 해줍니다. 여러 컨테이너를 하나의 환경에서 실행하고 관리하는 데 도움이 됩니다.
이전에 MySQL 이미지를 컨테이너로 실행시킬 때 아래와 같은 명령어를 실행시켰습니다.
$ docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 –v /Users/jaeseong/Documents/Develop/docker-mysql/mysql_data:/var/lib/mysql -d mysql
Docker Compose를 사용하면 위와 같이 컨테이너를 실행시킬 때마다 복잡한 명령어를 입력하지 않고, 단순히 docker compose up 명령어만 실행시키면 됩니다.
services:
websever:
container_name: webserver
image: nginx
ports:
- 80:80
$ docker compose up # 포그라운드에서 실행
$ docker compose up -d # 백그라운드에서 실행
# compose.yml에 정의된 컨테이너 중 실행 중인 컨테이너만 보여줌
$ docker compose ps
# compose.yml에 정의된 모든 컨테이너를 보여줌
$ docker compose ps -a
# compose.yml에 정의된 모든 컨테이너의 로그를 모아서 출력
$ docker compose logs
$ docker compose up --build # 포그라운드에서 실행
$ docker compose up --build -d # 백그라운드에서 실행
$ docker compose pull
$ docker compose down
지금까지의 예제를 보면 Docker CLI로 작성할 수 있는 명령어는 전부 compose.yml 파일로 옮길 수 있습니다. 반대로 compose.yml에 작성한 모든 값은 Docker CLI로 나타낼 수 있습니다. 이를 편하게 변환해주는 사이트가 존재합니다.
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: pwd1234
driver-class-name: com.mysql.cj.jdbc.Driver
FROM openjdk:17-jdk
COPY build/libs/*SNAPSHOT.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
services:
my-server:
build: .
ports:
- 8080:8080
# my-db의 컨테이너가 생성되고 healthy 하다고 판단 될 때, 해당 컨테이너를 생성한다.
depends_on:
my-db:
condition: service_healthy
my-db:
image: mysql
environment:
MYSQL_ROOT_PASSWORD: pwd1234
MYSQL_DATABASE: mydb # MySQL 최초 실행 시 mydb라는 데이터베이스를 생성해준다.
volumes:
- ./mysql_data:/var/lib/mysql
ports:
- 3306:3306
healthcheck:
test: [ "CMD", "mysqladmin", "ping" ] # MySQL이 healthy 한 지 판단할 수 있는 명령어
interval: 5s # 5초 간격으로 체크
retries: 10 # 10번까지 재시도
$ ./gradlew clean build
$ docker compose up -d **--build**
$ docker compose ps
$ docker ps
$ docker logs [Container ID]
위에처럼 작성하면 Spring Boot 컨테이너에서 에러가 발생합니다.
localhost:3306은 spring-boot 컨테이너 내부의 3306 포트를 가르키기 때문에 spring-boot 컨테이너 밖에 있는 mysql 컨테이너와 연결되지 못합니다.

따라서 application.yml을 다음과 같이 작성해야 합니다.
spring:
datasource:
url: jdbc:mysql://my-db:3306/mydb # localhost가 아니라 mysql의 컨테이너 이름인 my-db로 수정
username: root
password: pwd1234
driver-class-name: com.mysql.cj.jdbc.Driver

그러면 my-db인 컨테이너를 인지하고 Spring Boot와 MySQL을 연결해줍니다.
다음 글은 도커를 활용하여 AWS EC2에 배포하는 내용을 가져오겠습니다.
오늘부터 제 전공책으로 삼겠습니다.... 최고의 블로그.