Docker는 프로그램을 컨테이너라는 작은 방에 넣어 어느 컴퓨터에서나 똑같이 돌리게 해 주는 도구다. 이 글은 Docker를 쓰면서 자주 치는 명령을 빠르게 찾아보려고 정리한 것이다. 명령마다 표 한 줄과 예제 한 덩어리가 있고, 예제 뒤에는 줄마다 무엇을 하는지 적었다.
공식 문서: https://docs.docker.com/
이 글의 명령은 터미널에 친다
이 글의 명령은 전부 터미널에 글자로 쳐서 실행한다. 그러려면 먼저 컴퓨터에 Docker가 깔려 있어야 한다. Windows와 macOS는 Docker Desktop을 깔고, 리눅스는 Docker Engine을 직접 깐다. Docker Engine은 아래에서 설명할 Docker 데몬과 docker 명령을 합친 Docker의 몸통이고, Docker Desktop은 그 Engine에 Compose 같은 도구를 더 얹은 묶음이다. 운영체제마다 다른 점은 맨 끝 플랫폼별 차이에 모아 두었다.
글에 나오는 표기 몇 가지를 먼저 읽는 법을 적어 둔다.
<image>처럼 꺾쇠로 감싼 자리는 내가 바꿔 넣는 자리다. 꺾쇠까지 치지 않는다.docker run <image>는docker run nginx처럼 친다#으로 시작하는 줄은 주석이다. 자바의//와 같다. 터미널에서 명령을 받아 실행해 주는 프로그램인 셸이 그 줄을 읽지 않고 넘어간다- 줄 끝의
\는 「다음 줄에 이어진다」는 뜻이다. 긴 명령을 여러 줄로 나눠 적을 때 쓴다. 셸은 여러 줄을 한 줄로 이어 붙여 실행한다 $(pwd)는 「지금 있는 폴더의 경로」로 바뀐다. 셸이pwd명령을 먼저 실행하고 그 결과 글자를 그 자리에 끼워 넣는다.은 지금 폴더,./logs/는 지금 폴더 안의logs폴더다./var/log처럼/로 시작하는 경로는 리눅스에서 맨 꼭대기부터 적은 경로다
Docker 명령은 클라이언트가 데몬에게 부탁하는 모양이다
docker 명령을 칠 때 실제로 일하는 쪽은 따로 있다. 아래 그림이 그 흐름이다.
그림의 네 칸은 이렇다.
- Docker 클라이언트 — 내가 터미널에 치는
docker명령 그 자체다. 스스로 일하지 않고 데몬에게 부탁만 보낸다 - Docker 데몬 — 뒤에서 늘 돌며 부탁을 받아 실제로 일하는 프로그램(
dockerd)이다. 클라이언트와 둘로 나뉘어 있어서, 데몬이 꺼져 있으면docker명령은 연결 오류만 낸다 - 레지스트리 — 이미지를 올려 두고 내려받는 인터넷 저장소다. 누구나 쓰는 공개 레지스트리가 Docker Hub이고, 이미지 이름에 다른 주소를 안 적으면 Docker는 Docker Hub에서 찾는다
- 컨테이너 — 이미지를 실제로 실행한 것이다
이미지와 컨테이너는 자바의 클래스와 객체 관계와 비슷하다. 이미지는 고칠 수 없는 설계도이고, docker run 은 그 설계도로 컨테이너를 하나 찍어 실행한다. 같은 이미지로 컨테이너를 여러 개 띄울 수 있고, 컨테이너를 지워도 이미지는 남는다.
그림 셋째 칸의 docker ps/logs/exec 는 세 명령을 묶어 적은 것이다. 각각 아래 컨테이너 실행과 컨테이너 접근 절에서 다룬다.
컨테이너는 run으로 띄우고 stop으로 멈추고 rm으로 지운다
컨테이너에는 상태가 셋 있다. 실행 중, 중지됨, 지워짐이다. 멈춘(stop) 컨테이너는 지워진 게 아니라 그대로 남아 있어서 start 로 다시 켤 수 있고, docker ps -a 목록에도 보인다. 완전히 없애려면 rm 으로 따로 지운다.
| 명령어 | 설명 | 예제 |
|---|---|---|
docker run <image> |
새 컨테이너 실행 | docker run nginx |
docker run -d <image> |
백그라운드로 실행 (detached) | docker run -d nginx |
docker run -it <image> |
대화형 모드로 실행 | docker run -it ubuntu bash |
docker run --name <name> |
컨테이너 이름 지정 | docker run --name my-nginx nginx |
docker run -p <host>:<container> |
포트 매핑 | docker run -p 8080:80 nginx |
docker run --rm |
종료 시 자동 삭제 | docker run --rm ubuntu |
docker ps |
실행 중인 컨테이너 목록 | docker ps |
docker ps -a |
모든 컨테이너 목록 (중지 포함) | docker ps -a |
docker start <container> |
중지된 컨테이너 시작 | docker start my-nginx |
docker stop <container> |
실행 중인 컨테이너 중지 | docker stop my-nginx |
docker restart <container> |
컨테이너 재시작 | docker restart my-nginx |
docker rm <container> |
컨테이너 삭제 | docker rm my-nginx |
docker rm -f <container> |
실행 중인 컨테이너 강제 삭제 | docker rm -f my-nginx |
표의 옵션 중 처음 보면 막히는 것만 풀어 둔다.
nginx,ubuntu는 이미지 이름이다. nginx는 웹 페이지를 돌려주는 서버 프로그램이고, ubuntu는 우분투를 담은 이미지다. 리눅스는 한 가지가 아니라, 리눅스 커널에 기본 프로그램을 묶어 내놓은 여러 판(배포판)이 있고 우분투가 그중 하나다. 예제에서는 둘 다 Docker Hub에서 내려받는다-d(detached) — 컨테이너를 뒤에서 돌리고 터미널은 바로 돌려준다. 이 옵션이 없으면 컨테이너가 끝날 때까지 터미널이 그 컨테이너에 붙잡혀 있다-it—-i와-t를 붙여 쓴 것이다.-i는 내가 치는 글자가 컨테이너로 들어가게 입력을 열어 두고,-t는 가짜 터미널 화면(pty)을 붙인다. 둘을 같이 써야 컨테이너 안에서 명령을 치고 결과를 보는 「대화」가 된다.ubuntu bash는 ubuntu 이미지로 컨테이너를 띄우면서 그 안에서 셸인bash를 실행하라는 뜻이다-p 8080:80(포트 매핑) — 콜론 앞이 내 컴퓨터(호스트)의 포트, 뒤가 컨테이너 안의 포트다. nginx는 컨테이너 안에서 80번 포트로 요청을 받는다. 그런데 컨테이너 안의 포트는 바깥에서 바로 안 보인다.-p 8080:80은 「내 컴퓨터 8080번으로 오는 요청을 컨테이너 80번으로 넘겨라」라는 뜻이고, 그래서 브라우저에서 내 컴퓨터를 가리키는 주소 localhost의 8080번, 곧localhost:8080으로 들어가면 nginx가 대답한다--rm— 컨테이너는 끝나도 저절로 지워지지 않고 「중지됨」으로 남는다.--rm을 붙이면 끝나는 순간 지워진다
웹 서버를 띄웠다가 지우는 예제
# Nginx 웹 서버를 8080 포트로 실행
docker run -d -p 8080:80 --name web nginx
# 실행 중인 컨테이너 확인
docker ps
# 컨테이너 중지
docker stop web
# 컨테이너 삭제
docker rm web
# 한 번에 실행, 작업 후 자동 삭제
docker run --rm -it ubuntu bash
docker run -d -p 8080:80 --name web nginx— nginx 이미지로 컨테이너를 하나 띄운다. 뒤에서 돌리고(-d), 내 컴퓨터 8080번을 컨테이너 80번에 잇고(-p), 이름을web으로 붙인다(--name). 이름을 붙여 두면 뒤의 명령에서web으로 부를 수 있다docker ps— 지금 돌고 있는 컨테이너 목록을 본다.web이 보이면 잘 뜬 것이다docker stop web—web을 멈춘다. 아직 지워지지 않아서docker ps -a에는 남아 있다docker rm web— 멈춘web을 지운다docker run --rm -it ubuntu bash— ubuntu 컨테이너를 띄우고 그 안의 bash에 바로 들어간다. 화면에 컨테이너 안의 셸이 「명령을 치라」고 기다리는 줄(프롬프트)이 뜨고, 거기서 리눅스 명령을 쳐 볼 수 있다.exit를 치면 bash가 끝난다. 컨테이너는 처음 실행한 그 프로그램(bash)이 끝나면 같이 끝나고,--rm이라 끝나는 순간 지워진다
실행 중인 컨테이너의 로그를 보고 안에 들어간다
컨테이너가 뒤에서(-d) 돌면 화면에 아무것도 안 보인다. 컨테이너 안의 프로그램이 찍는 로그를 보거나, 안에 들어가 명령을 쳐 봐야 무슨 일이 있는지 안다.
| 명령어 | 설명 | 예제 |
|---|---|---|
docker logs <container> |
컨테이너 로그 조회 | docker logs web |
docker logs -f <container> |
로그 실시간 추적 (follow) | docker logs -f web |
docker logs -n <num> <container> |
마지막 N줄만 조회 | docker logs -n 100 web |
docker exec <container> <cmd> |
실행 중인 컨테이너에서 명령 실행 | docker exec web ls /app |
docker exec -it <container> bash |
컨테이너에 쉘 접속 | docker exec -it web bash |
docker attach <container> |
컨테이너의 표준 입출력에 연결 | docker attach web |
docker cp <src> <container>:<dest> |
파일 복사 (호스트→컨테이너) | docker cp file.txt web:/app/ |
docker cp <container>:<src> <dest> |
파일 복사 (컨테이너→호스트) | docker cp web:/app/log.txt . |
-n은--tail과 같은 옵션이다. 로그 끝에서부터 몇 줄을 볼지 정한다exec와attach는 다르다.exec는 컨테이너 안에서 새 프로그램을 하나 더 실행한다(ls,bash등).attach는 새 프로그램을 띄우지 않고, 컨테이너가 처음부터 돌리던 프로그램의 표준 입출력에 내 터미널을 잇는다. 그래서 안을 둘러보고 싶을 때는 보통exec -it ... bash를 쓴다ls는 폴더 안의 파일 목록을 보는 리눅스 명령이다.ls /app은 컨테이너 안/app폴더를 본다docker cp는컨테이너이름:경로로 컨테이너 쪽을 적는다.web:/app/은 「web컨테이너 안의/app/폴더」다. 콜론이 없는 쪽이 내 컴퓨터의 경로이고, 맨 끝의.은 내 컴퓨터의 지금 폴더다
로그를 보고 파일을 주고받는 예제
# 실행 중인 컨테이너 로그 실시간 확인
docker logs -f web
# 컨테이너 안에서 bash 셸 실행
docker exec -it web bash
# 컨테이너 내부 파일 확인
docker exec web ls -la /var/log
# 호스트에서 컨테이너로 파일 복사
docker cp config.json web:/etc/app/
# 컨테이너에서 호스트로 파일 복사
docker cp web:/var/log/app.log ./logs/
docker logs -f web—web의 로그를 찍고, 새 로그가 생기면 계속 이어서 찍는다.docker exec -it web bash—web안에서 bash를 새로 띄워 들어간다. 나올 때는exit를 친다docker exec web ls -la /var/log— 들어가지 않고 명령 하나만 실행해 결과를 받는다.-l은 크기·날짜까지 길게 보여 주고,-a는.으로 시작하는 숨은 파일까지 보여 준다./var/log는 리눅스에서 로그 파일을 두는 폴더다docker cp config.json web:/etc/app/— 내 지금 폴더의config.json을web안의/etc/app/폴더로 복사한다. 파일 이름은 예시다docker cp web:/var/log/app.log ./logs/— 반대로web안의app.log를 내 지금 폴더의logs폴더로 가져온다
이미지는 pull로 받고 build로 만들고 push로 올린다
이미지가 어디서 오고 어디로 가는지를 그림으로 먼저 본다. 이 블로그는 Mermaid라는 도구로 글자를 그림으로 바꿔 그리는데, 그림의 [*] 는 Mermaid 상태 그림에서 시작·끝을 나타내는 표시다.
- Registry 는 인터넷의 레지스트리, Local 은 내 컴퓨터에 내려받아 둔 이미지들이다. 「로컬」은 「내 컴퓨터」라는 뜻이다
docker commit은 돌고 있는 컨테이너에서 바뀐 내용을 새 이미지로 굳힌다. 그래서 그림에서 Container → Local 화살표가 있다. 이미지를 만드는 다른 길은 Dockerfile이다. 아래 예제는 Dockerfile 쪽을 쓴다
| 명령어 | 설명 | 예제 |
|---|---|---|
docker images |
로컬 이미지 목록 조회 | docker images |
docker pull <image> |
이미지 다운로드 | docker pull nginx:latest |
docker push <image> |
이미지 업로드 | docker push myuser/myapp:1.0 |
docker build -t <tag> <path> |
Dockerfile로 이미지 빌드 | docker build -t myapp:1.0 . |
docker tag <image> <tag> |
이미지에 태그 추가 | docker tag myapp:1.0 myapp:latest |
docker commit <container> <image> |
컨테이너를 새 이미지로 저장 | docker commit web myweb:1.0 |
docker rmi <image> |
이미지 삭제 | docker rmi nginx |
docker rmi -f <image> |
이미지 강제 삭제 | docker rmi -f nginx |
docker image prune |
댕글링 이미지 삭제 | docker image prune |
docker image prune -a |
어느 컨테이너도 안 쓰는 이미지 전부 삭제 | docker image prune -a |
docker history <image> |
이미지 레이어 히스토리 조회 | docker history nginx |
docker inspect <image> |
이미지 상세 정보 조회 | docker inspect nginx |
- 콜론 뒤는 태그다.
nginx:latest의latest,myapp:1.0의1.0처럼 버전을 가르는 꼬리표이고, 안 적으면latest로 친다.docker tag는 이미지는 그대로 두고 이름표만 하나 더 붙인다 - 슬래시 앞은 누구의 이미지인지다.
myuser/myapp의myuser는 레지스트리의 계정 이름 자리다. 안 적으면 Docker Hub의 공식 이미지로 친다 docker build -t myapp:1.0 .은 Dockerfile을 읽어 이미지를 만들고(빌드)myapp:1.0이라는 이름표를 붙인다. 끝의.은 지금 폴더를 재료로 쓰라는 뜻이고, 따로 정하지 않으면 그 폴더의Dockerfile파일을 읽는다prune이 지우는 범위는-a로 갈린다. 옵션 없이 치면 태그도 없고 쓰는 컨테이너도 없는 댕글링 이미지만 지운다.-a를 붙이면 태그가 있어도 어느 컨테이너도 안 쓰는 이미지를 전부 지운다- 레이어 는 이미지를 이루는 층이다. Dockerfile 한 줄이 층 하나가 되고,
docker history가 그 층들을 보여 준다
이미지를 받고 만들어 올리는 예제
아래 예제의 docker build 를 따라 하려면 지금 폴더에 Dockerfile 이 있어야 한다. 가장 짧은 모양은 이렇다. nginx 공식 이미지 설명에 나오는 예를 그대로 옮긴 것이다.
FROM nginx
COPY static-html-directory /usr/share/nginx/html
FROM nginx— nginx 이미지를 출발점(바탕 이미지)으로 삼는다COPY static-html-directory /usr/share/nginx/html— 지금 폴더의static-html-directory폴더를 이미지 안의/usr/share/nginx/html로 복사한다. nginx가 웹 페이지 파일을 읽는 곳이라, 이 이미지로 컨테이너를 띄우면 내 HTML을 내준다
# 이미지 다운로드
docker pull node:18-alpine
# Dockerfile로 이미지 빌드
docker build -t myapp:1.0 .
# 이미지에 추가 태그 지정
docker tag myapp:1.0 myapp:latest
docker tag myapp:1.0 myuser/myapp:1.0
# Docker Hub에 업로드
docker push myuser/myapp:1.0
# 사용하지 않는 이미지 정리
docker image prune -a
docker pull node:18-alpine— Node.js 이미지를 받는다. 태그18-alpine은 「Node.js 18 버전을 Alpine 리눅스 위에 얹은 것」이다. Alpine은 이미지 크기가 5MB 남짓인 작은 리눅스다docker build -t myapp:1.0 .— 지금 폴더의 Dockerfile로myapp:1.0이미지를 만든다docker tag myapp:1.0 myapp:latest— 같은 이미지에latest이름표를 하나 더 붙인다docker tag myapp:1.0 myuser/myapp:1.0— 이번에는 앞에 계정 이름을 붙인다. Docker Hub에 올리려면 이름이계정이름/이미지이름모양이어야 내 계정 아래로 올라간다.myuser는 내 계정 이름으로 바꿔 넣는 자리다docker push myuser/myapp:1.0— 그 이미지를 Docker Hub에 올린다. 올리기 전에docker login으로 로그인해 두어야 한다docker image prune -a— 어느 컨테이너도 안 쓰는 이미지를 전부 지운다
볼륨에 두어야 컨테이너를 지워도 데이터가 남는다
컨테이너 안에 쓴 파일은 그 컨테이너를 지우면 같이 사라진다. DB처럼 데이터를 지켜야 하는 컨테이너는 그래서 데이터를 컨테이너 바깥에 둔다. 이렇게 컨테이너가 사라져도 데이터가 남는 성질을 영속성이라고 부르고, 그 바깥 자리가 볼륨이다. 바깥에 두는 방법은 셋이다.
Docker 관리 영역] Bind[Bind Mount
호스트 특정 경로] Tmpfs[Tmpfs Mount
메모리 임시 저장] end Container[컨테이너] Volume -.->|docker volume| Container Bind -.->|직접 마운트| Container Tmpfs -.->|메모리| Container Host -->|관리| Volume Host -->|직접 연결| Bind style Volume fill:#2d3748,stroke:#48bb78,stroke-width:2px,color:#e2e8f0 style Bind fill:#2d3748,stroke:#ecc94b,stroke-width:2px,color:#e2e8f0 style Tmpfs fill:#2d3748,stroke:#4299e1,stroke-width:2px,color:#e2e8f0
- Volume — Docker가 직접 관리하는 저장 공간이다. 리눅스에서는
/var/lib/docker/volumes/아래에 실제 파일이 놓인다. 데이터를 오래 지킬 때 쓴다 - Bind Mount — 내 컴퓨터에 이미 있는 폴더 하나를 컨테이너 안 경로에 그대로 겹쳐 보이게 한다(이걸 「마운트」라고 부른다). 내가 고치는 소스 폴더를 컨테이너에 보여 줄 때 쓴다
- Tmpfs Mount — 디스크가 아니라 메모리에 둔다. 컨테이너가 멈추면 사라지므로 남기면 안 되는 임시 파일에만 쓴다
| 명령어 | 설명 | 예제 |
|---|---|---|
docker volume ls |
볼륨 목록 조회 | docker volume ls |
docker volume create <name> |
볼륨 생성 | docker volume create mydata |
docker volume inspect <name> |
볼륨 상세 정보 | docker volume inspect mydata |
docker volume rm <name> |
볼륨 삭제 | docker volume rm mydata |
docker volume prune |
쓰지 않는 이름 없는 볼륨 삭제 (-a 면 이름 있는 것까지) |
docker volume prune |
docker run -v <volume>:<path> |
볼륨 마운트 | docker run -v mydata:/data nginx |
docker run -v <host>:<container> |
바인드 마운트 | docker run -v /host/path:/container/path nginx |
docker run --mount |
상세한 마운트 옵션 | docker run --mount source=mydata,target=/data nginx |
-v하나로 볼륨과 바인드 마운트를 둘 다 쓴다. 콜론 앞에 이름(mydata)을 적으면 볼륨이고, 내 컴퓨터의 경로(/host/path)를 적으면 바인드 마운트다. 콜론 뒤는 둘 다 컨테이너 안의 경로다--mount는 같은 일을이름=값으로 풀어 적는다. 더 길지만 무엇을 어디에 붙이는지가 글자로 드러나고,-v로 못 적는 옵션까지 다 적을 수 있다- 이름 없는 볼륨은 Docker가 알아서 만든 볼륨이다. 예를 들어 postgres 이미지는
/var/lib/postgresql/data를 볼륨 자리로 정해 두어서, 거기에 아무 볼륨도 안 붙이고 띄우면 Docker가 이름 없는 볼륨을 새로 만들어 붙인다. 이름이 없으니 다음 컨테이너가 다시 찾아 쓰지 못하고 남기만 한다 docker volume prune은 옵션 없이 치면 이름 없는 볼륨만 지운다.docker volume create로 이름을 붙여 만든 볼륨은-a를 붙여야 지워진다. 내 컴퓨터의 Docker 클라이언트 28.1.1에서docker volume prune --help로 확인한 동작이다
볼륨과 바인드 마운트를 붙이는 예제
# 볼륨 생성
docker volume create postgres-data
# 볼륨을 사용하는 컨테이너 실행
docker run -d \
--name db \
-v postgres-data:/var/lib/postgresql/data \
postgres:15
# 호스트 디렉토리를 바인드 마운트
docker run -d \
--name web \
-v /home/user/app:/usr/share/nginx/html:ro \
nginx
# 읽기 전용(ro) 마운트
docker run -v $(pwd):/app:ro myapp
# 볼륨 목록 확인
docker volume ls
# 사용하지 않는 볼륨 정리
docker volume prune
docker volume create postgres-data—postgres-data라는 이름의 볼륨을 만든다docker run -d \ ... postgres:15— 네 줄이\로 이어진 명령 하나다. PostgreSQL DB 15 버전 이미지로db컨테이너를 띄우고, 볼륨postgres-data를 컨테이너 안/var/lib/postgresql/data에 붙인다. 그 경로는 postgres 이미지가 DB 파일을 쓰는 곳이라, 거기에 볼륨을 붙여야 컨테이너를 지워도 DB가 남는다. 실제로 이 이미지는POSTGRES_PASSWORD라는 환경변수가 없으면 뜨지 않으므로, 이대로 띄우려면-e POSTGRES_PASSWORD=...를 더해야 한다(개발 환경 구성 예제 참고)docker run -d \ ... nginx— 내 컴퓨터의/home/user/app폴더를 nginx가 웹 페이지 파일을 읽는/usr/share/nginx/html에 겹쳐 붙인다. 내 폴더의 HTML 파일을 고치면 nginx가 바로 그 파일을 내준다. 끝의:ro는 읽기 전용(read-only)이라는 뜻이라, 컨테이너 쪽에서는 그 폴더에 쓸 수 없다docker run -v $(pwd):/app:ro myapp— 지금 폴더를myapp컨테이너의/app에 읽기 전용으로 붙인다.myapp은 앞 절에서 빌드한 예시 이미지다docker volume ls— 볼륨 목록을 본다docker volume prune— 쓰지 않는 이름 없는 볼륨을 지운다. 위에서 만든postgres-data는 이름이 있어서 이 명령으로는 안 지워진다
같은 네트워크에 둔 컨테이너끼리 이름으로 통신한다
웹 서버 컨테이너가 DB 컨테이너에 접속하려면 둘이 이어져 있어야 한다. 그 길이 네트워크다. 네트워크는 종류(드라이버)를 골라 만든다.
기본 네트워크] Host[Host
호스트 네트워크 공유] None[None
네트워크 없음] Custom[Custom Bridge
사용자 정의] end C1[컨테이너 1] C2[컨테이너 2] C3[컨테이너 3] Bridge --> C1 Bridge --> C2 Custom --> C3 style Bridge fill:#2d3748,stroke:#4299e1,stroke-width:2px,color:#e2e8f0 style Custom fill:#2d3748,stroke:#48bb78,stroke-width:2px,color:#e2e8f0 style Host fill:#2d3748,stroke:#ecc94b,stroke-width:2px,color:#e2e8f0 style None fill:#2d3748,stroke:#f56565,stroke-width:2px,color:#e2e8f0
- Bridge — 네트워크를 따로 안 고르면 컨테이너가 들어가는 기본 네트워크다. 그림의 컨테이너 1, 2가 여기 있다
- Custom Bridge — 내가
docker network create로 만든 bridge 네트워크다. 그림의 컨테이너 3이 여기 있다. 기본 Bridge와 달리 여기서는 컨테이너가 서로를 이름으로 찾는다. 그래서 컨테이너끼리 통신시킬 때는 네트워크를 직접 만든다 - Host — 컨테이너가 내 컴퓨터의 네트워크를 그대로 같이 쓴다. Bridge에 있는 컨테이너는 내 컴퓨터와 네트워크가 분리돼 있어서
-p로 포트를 이어야 바깥에서 닿는데, Host는 그 분리가 없다. 그래서 Host에서는-p가 아예 무시된다 - None — 네트워크를 아예 끊는다
그림에서 Host와 None에 이어진 컨테이너가 없는 것은 이 두 종류를 쓰는 예제가 이 글에 없어서다.
| 명령어 | 설명 | 예제 |
|---|---|---|
docker network ls |
네트워크 목록 조회 | docker network ls |
docker network create <name> |
네트워크 생성 | docker network create mynet |
docker network inspect <name> |
네트워크 상세 정보 | docker network inspect mynet |
docker network rm <name> |
네트워크 삭제 | docker network rm mynet |
docker network connect <net> <container> |
컨테이너를 네트워크에 연결 | docker network connect mynet web |
docker network disconnect <net> <container> |
네트워크에서 컨테이너 분리 | docker network disconnect mynet web |
docker run --network <name> |
특정 네트워크로 실행 | docker run --network mynet nginx |
네트워크를 만들고 컨테이너를 잇는 예제
# 사용자 정의 브리지 네트워크 생성
docker network create --driver bridge myapp-net
# 네트워크에 연결된 컨테이너 실행
docker run -d --name db --network myapp-net -e POSTGRES_PASSWORD=secret postgres
docker run -d --name web --network myapp-net nginx
# 같은 네트워크 내에서 컨테이너 이름으로 통신 가능
docker exec web ping db
# 네트워크 없이 띄운 컨테이너 하나 (기본 Bridge에 들어간다)
docker run -d --name redis redis
# 기존 컨테이너를 네트워크에 연결
docker network connect myapp-net redis
# 네트워크 정보 확인
docker network inspect myapp-net
# 네트워크에서 컨테이너 분리
docker network disconnect myapp-net redis
docker network create --driver bridge myapp-net— bridge 종류로myapp-net네트워크를 만든다.--driver는 종류를 고르는 옵션이고, 안 적어도 bridge가 기본이다docker run -d --name db --network myapp-net -e POSTGRES_PASSWORD=secret postgres—db컨테이너를myapp-net에 넣어 띄운다.-e는 컨테이너에 환경변수를 넣는 옵션이고, postgres 이미지는 비밀번호POSTGRES_PASSWORD가 없으면 뜨지 않아서 넣었다docker run -d --name web --network myapp-net nginx—web도 같은 네트워크에 넣는다docker exec web ping db—web안에서 ping으로db라는 이름에 신호를 보낸다. 대답이 오면web이db를 이름만으로 찾아 닿았다는 뜻이다. 둘이 내가 만든 네트워크에 같이 있어서 이름이 통한다. 다만 nginx 이미지 안에ping명령이 들어 있는지는 확인하지 않았다. 없으면 「명령을 찾을 수 없다」는 오류가 나고, 그때는 아래docker network inspect myapp-net으로 두 컨테이너가 같은 네트워크에 붙어 있는지까지만 확인할 수 있다docker run -d --name redis redis— 네트워크를 안 고르고redis이미지로 컨테이너를 하나 띄운다. 기본 Bridge에 들어간다. 다음 줄에서 「이미 떠 있는 컨테이너를 나중에 네트워크에 잇는」 모습을 보이려고 띄운 것이고, redis가 무슨 프로그램인지는 이 예제에서 중요하지 않다docker network connect myapp-net redis— 이미 떠 있는redis를myapp-net에도 잇는다docker network inspect myapp-net— 네트워크에 어떤 컨테이너가 붙어 있는지 등 상세 정보를 본다docker network disconnect myapp-net redis—redis를myapp-net에서 뗀다
Docker Compose는 컨테이너 여러 개를 파일 하나로 띄운다
앞 절까지는 컨테이너를 하나씩 명령으로 띄웠다. 웹 페이지를 내주는 웹 서버, 데이터를 주고받는 창구(API)를 맡는 API 서버, DB를 매번 따로 띄우고 네트워크와 볼륨까지 손으로 만들면 명령이 길어지고 빠뜨리기 쉽다. Docker Compose는 이것들을 docker-compose.yml 이라는 YAML 파일 하나에 적어 두고 docker compose up 한 번으로 띄운다. Compose에서는 컨테이너 하나하나를 서비스라고 부른다. 서비스는 「이 이미지로, 이 설정으로 컨테이너를 띄운다」는 정의이고, 띄운 결과가 컨테이너다.
그림의 Docker Engine은 처음 절에서 말한 그 Engine이다. 데몬과 docker 명령을 합친 이름이지만 실제 일은 그 안의 데몬이 한다. Compose도 결국 데몬에게 부탁해서 일을 시킨다.
| 명령어 | 설명 | 예제 |
|---|---|---|
docker compose up |
서비스 시작 | docker compose up |
docker compose up -d |
백그라운드로 서비스 시작 | docker compose up -d |
docker compose down |
서비스 중지 및 삭제 | docker compose down |
docker compose ps |
서비스 상태 확인 | docker compose ps |
docker compose logs |
서비스 로그 조회 | docker compose logs |
docker compose logs -f |
로그 실시간 추적 | docker compose logs -f |
docker compose exec <service> |
서비스에서 명령 실행 | docker compose exec web bash |
docker compose build |
이미지 빌드 | docker compose build |
docker compose pull |
이미지 다운로드 | docker compose pull |
docker compose restart |
서비스 재시작 | docker compose restart |
docker compose stop |
서비스 중지 (컨테이너 유지) | docker compose stop |
docker compose start |
중지된 서비스 시작 | docker compose start |
down 과 stop 은 남기는 것이 다르다. stop 은 컨테이너를 멈추기만 하고 지우지 않아서 start 로 다시 켤 수 있다. down 은 컨테이너와 Compose가 만든 네트워크를 지운다. 볼륨은 down 만으로는 안 지워지고, down -v 를 쳐야 파일의 volumes 에 적힌 이름 있는 볼륨까지 지워진다.
웹 서버 · API · DB를 한 파일에 적는 예제
# docker-compose.yml 예제
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
networks:
- frontend
depends_on:
- api
api:
build: ./api
environment:
- DATABASE_URL=postgresql://db:5432/mydb
networks:
- frontend
- backend
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=mydb
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- backend
volumes:
postgres-data:
networks:
frontend:
backend:
YAML은 들여쓰기로 「무엇 안에 무엇이 있는지」를 나타낸다. 한 칸 더 들어간 줄은 바로 위 줄에 속한다. - 로 시작하는 줄은 목록의 한 칸이다.
version: '3.8'— 파일 형식의 버전을 적던 자리다. 지금 Compose는 이 값을 쓰지 않고 「더 이상 쓰지 않는 항목」이라는 경고만 낸다. 빼도 된다services:— 아래에 띄울 서비스를 적는다.web,api,db는 내가 정한 이름이다. 이 이름이 곧 같은 네트워크 안에서 서로를 찾는 이름이 된다web의image: nginx:alpine— Alpine 리눅스 위에 얹은 nginx 이미지로 띄운다ports: - "8080:80"—docker run -p 8080:80과 같다. 내 컴퓨터 8080번을 컨테이너 80번에 잇는다volumes: - ./html:/usr/share/nginx/html— 콜론 앞이 경로(./html, 이 파일 옆의html폴더)이므로 바인드 마운트다. 내html폴더를 nginx가 읽는 폴더에 겹친다networks: - frontend—web을frontend네트워크에 넣는다depends_on: - api—api를 먼저 띄우고 나서web을 띄운다. 띄우는 순서를 정하는 항목이다api의build: ./api— 이미 있는 이미지를 받는image대신,./api폴더 안의 Dockerfile로 이미지를 직접 빌드해 띄운다. 그 폴더에 Dockerfile이 없으면 오류가 난다environment: - DATABASE_URL=postgresql://db:5432/mydb—api컨테이너에 환경변수를 넣는다. 값은 「postgresql 방식으로,db라는 이름의 컴퓨터의 5432번 포트에 있는mydbDB」라는 뜻의 주소다. 5432는 PostgreSQL이 기본으로 요청을 받는 포트이고,db는 아래db서비스의 이름이다. 이 값을 읽는 쪽은api안의 프로그램이다networks: - frontend - backend—api는 두 네트워크에 다 들어간다. 그래서web과도,db와도 닿는다db의POSTGRES_PASSWORD=secret— postgres 이미지가 읽는 값으로, DB 관리자 비밀번호가 된다. 이 값이 없으면 postgres 컨테이너가 뜨지 않는다db의POSTGRES_DB=mydb— 처음 뜰 때mydb라는 DB를 만든다. 위api의 주소가 가리키는mydb가 이것이다. 이 줄이 없으면 postgres 이미지는 사용자 이름(기본postgres)과 같은 이름으로 DB를 만들어서,mydb는 생기지 않는다volumes: - postgres-data:/var/lib/postgresql/data— 콜론 앞이 이름이므로 볼륨이다. DB 파일을 볼륨에 둔다networks: - backend—db는backend에만 있다.web은backend에 없어서db에 바로 닿지 않는다. 웹 서버가 DB를 직접 건드리지 못하게 가른 것이다- 맨 바깥
volumes: postgres-data:— 이름 있는 볼륨postgres-data를 쓰겠다고 선언하는 자리다. 값이 비어 있으면 기본 설정으로 만든다 - 맨 바깥
networks: frontend: backend:— 네트워크 둘을 쓰겠다고 선언한다. 값이 비어 있으면 기본 설정으로 만든다
# 모든 서비스 시작
docker compose up -d
# 특정 서비스만 시작
docker compose up -d web
# 로그 확인
docker compose logs -f api
# 서비스 상태 확인
docker compose ps
# 모든 서비스 중지 및 삭제
docker compose down
# 볼륨까지 함께 삭제
docker compose down -v
docker compose up -d— 지금 폴더의docker-compose.yml을 읽어 세 서비스를 전부 뒤에서 띄운다docker compose up -d web—web만 띄우라고 했지만,web이depends_on으로 기대는api와,api가 기대는db도 아직 안 떠 있으면 같이 띄운다docker compose logs -f api—api서비스의 로그를 계속 이어서 본다docker compose ps— 서비스마다 컨테이너가 떠 있는지 본다docker compose down— 컨테이너와 네트워크를 지운다. 볼륨postgres-data는 남는다docker compose down -v— 볼륨까지 지운다. DB 데이터가 사라진다
디스크를 차지한 찌꺼기는 system prune으로 한 번에 지운다
| 명령어 | 설명 | 예제 |
|---|---|---|
docker system df |
디스크 사용량 조회 | docker system df |
docker system prune |
사용하지 않는 리소스 정리 | docker system prune |
docker system prune -a |
모든 사용하지 않는 리소스 정리 | docker system prune -a |
docker info |
Docker 시스템 정보 | docker info |
docker version |
Docker 버전 정보 | docker version |
docker stats |
컨테이너 리소스 사용량 실시간 확인 | docker stats |
docker top <container> |
컨테이너 프로세스 확인 | docker top web |
docker system prune 이 지우는 「사용하지 않는 리소스」는 넷이다. 멈춘 컨테이너 전부, 어느 컨테이너도 안 쓰는 네트워크, 댕글링 이미지, 빌드 캐시다. Docker는 이미지를 빌드할 때 층마다 결과를 남겨 두고, 다음 빌드에서 Dockerfile 중 바뀌지 않은 층은 다시 만들지 않는다. 그 남겨 둔 결과가 빌드 캐시라, 빌드를 거듭할수록 쌓인다. -a 를 붙이면 댕글링이 아니어도 어느 컨테이너도 안 쓰는 이미지까지 지운다. 볼륨은 기본으로 안 지운다. --volumes 를 붙여야 이름 없는 볼륨까지 지운다.
docker stats 의 리소스는 CPU와 메모리 같은 컴퓨터 자원이고, docker top 의 프로세스는 컨테이너 안에서 돌고 있는 프로그램 하나하나다.
디스크와 자원을 살피는 예제
# 디스크 사용량 확인
docker system df
# 중지된 컨테이너, 사용하지 않는 네트워크 등 정리
docker system prune
# 사용하지 않는 이미지도 함께 정리
docker system prune -a
# 모든 컨테이너의 리소스 사용량 실시간 모니터링
docker stats
# Docker 정보 확인
docker info
docker system df— 이미지·컨테이너·볼륨이 디스크를 얼마나 쓰는지 본다docker system prune— 위에 적은 넷(멈춘 컨테이너, 안 쓰는 네트워크, 댕글링 이미지, 빌드 캐시)을 지운다docker system prune -a— 거기에 어느 컨테이너도 안 쓰는 이미지까지 지운다docker stats— 돌고 있는 컨테이너마다 CPU·메모리를 얼마나 쓰는지 계속 갱신해 보여 준다.docker info— 데몬 쪽 정보(컨테이너 수, 저장 방식 등)를 본다
개발 DB · 로그 · 정리처럼 자주 치는 명령 조합
1. 개발 환경 구성
# Node.js 개발 환경
docker run -it --rm \
-v $(pwd):/app \
-w /app \
-p 3000:3000 \
node:18-alpine \
sh
# 데이터베이스 개발용 실행
docker run -d \
--name dev-db \
-e POSTGRES_PASSWORD=dev \
-e POSTGRES_DB=myapp \
-p 5432:5432 \
-v postgres-dev:/var/lib/postgresql/data \
postgres:15
첫 명령은 내 컴퓨터에 Node.js를 깔지 않고 컨테이너 안에서 Node.js 코드를 돌려 보는 모양이다.
docker run -it --rm \— 대화형으로 띄우고, 끝나면 지운다-v $(pwd):/app \— 지금 폴더(내 소스)를 컨테이너의/app에 바인드 마운트한다-w /app \— 컨테이너 안에서 일을 시작할 폴더(작업 폴더)를/app으로 정한다. 들어가자마자 내 소스 폴더에 서 있게 된다-p 3000:3000 \— 내 컴퓨터 3000번을 컨테이너 3000번에 잇는다. 컨테이너 안에서 띄울 Node.js 앱이 3000번을 쓴다고 가정한 예시 번호다node:18-alpine \— 쓸 이미지다sh— 컨테이너 안에서 실행할 셸이다. 앞 예제의bash대신sh라는 셸을 띄운다. Alpine 이미지에bash가 들어 있는지는 확인하지 않았다
둘째 명령은 개발용 PostgreSQL을 하나 띄운다.
--name dev-db— 이름을dev-db로 붙인다-e POSTGRES_PASSWORD=dev— postgres 이미지에 꼭 필요한 관리자 비밀번호를 환경변수로 넣는다-e POSTGRES_DB=myapp— 처음 뜰 때 만들 DB 이름을myapp으로 정한다-p 5432:5432— PostgreSQL이 기본으로 요청을 받는 5432번을 내 컴퓨터 5432번에 이어, 내 컴퓨터의 프로그램이localhost:5432로 DB에 접속하게 한다-v postgres-dev:/var/lib/postgresql/data— DB 파일을 볼륨postgres-dev에 둔다. 볼륨이 없으면 Docker가 이 이름으로 만든다postgres:15— PostgreSQL 15 이미지다
2. 로그 및 디버깅
# 최근 100줄의 로그 확인
docker logs --tail 100 my-container
# 타임스탬프와 함께 로그 실시간 추적
docker logs -f --timestamps my-container
# 컨테이너 리소스 사용량 확인
docker stats my-container
# 컨테이너 내부 프로세스 확인
docker top my-container
docker logs --tail 100 my-container— 마지막 100줄만 본다. 앞 절의-n 100과 같다.my-container는 컨테이너 이름을 바꿔 넣는 자리다docker logs -f --timestamps my-container— 줄마다 그 로그가 찍힌 시각(타임스탬프)을 앞에 붙여 계속 이어서 본다docker stats my-container— 그 컨테이너 하나의 CPU·메모리 사용량만 본다docker top my-container— 그 컨테이너 안에서 돌고 있는 프로세스 목록을 본다
3. 정리 및 최적화
# 중지된 모든 컨테이너 삭제
docker container prune
# 사용하지 않는 모든 이미지 삭제
docker image prune -a
# 사용하지 않는 볼륨 삭제
docker volume prune
# 한 번에 모두 정리
docker system prune -a --volumes
docker container prune— 멈춘 컨테이너를 전부 지운다. 앞의 표에는 없던 명령이다docker image prune -a— 어느 컨테이너도 안 쓰는 이미지를 전부 지운다docker volume prune— 쓰지 않는 이름 없는 볼륨을 지운다. 이름 있는 볼륨까지 지우려면-a를 붙인다docker system prune -a --volumes— 위 셋을 한 번에 한다. 단--volumes도 이름 없는 볼륨만 지운다
팁 — 별칭 · 자동완성 · 자주 쓰는 run 옵션
별칭(Alias) 설정으로 생산성 향상
# ~/.bashrc 또는 ~/.zshrc에 추가
alias dps='docker ps'
alias dpsa='docker ps -a'
alias di='docker images'
alias dex='docker exec -it'
alias dlog='docker logs -f'
alias dcp='docker compose'
별칭은 긴 명령에 짧은 이름을 붙이는 셸 기능이다. alias 짧은이름='원래 명령' 모양으로 적는다. ~ 는 내 홈 폴더이고, ~/.bashrc 와 ~/.zshrc 는 각각 bash와 zsh 셸이 시작할 때 읽는 설정 파일이다. 여기에 적어 두어야 터미널을 새로 열어도 별칭이 남는다.
alias dps='docker ps'— 이제dps만 치면docker ps가 실행된다alias dex='docker exec -it'—dex web bash라고 치면docker exec -it web bash가 된다. 뒤에 적은 말은 원래 명령 뒤에 그대로 붙는다- 나머지 넷도 같은 모양이다
이 문법은 bash · zsh 셸의 것이라 Windows의 PowerShell에서는 그대로 안 된다.
컨테이너 이름으로 자동완성
# Bash completion 설치
sudo apt-get install bash-completion
# Docker completion 활성화
sudo curl -L https://raw.githubusercontent.com/docker/cli/master/contrib/completion/bash/docker -o /etc/bash_completion.d/docker
자동완성은 명령이나 컨테이너 이름을 앞 몇 글자만 치고 Tab 키를 누르면 나머지를 채워 주는 기능이다.
sudo apt-get install bash-completion— 우분투에서 apt-get으로 bash 자동완성 기능을 설치한다. sudo는 이 명령을 관리자(root) 권한으로 실행한다sudo curl -L <주소> -o /etc/bash_completion.d/docker— curl로 Docker용 자동완성 스크립트를 내려받는다.-L은 주소가 다른 곳으로 넘겨 주면 따라가라는 옵션이고,-o는 받은 내용을 화면 대신 뒤에 적은 파일에 저장하라는 옵션이다. bash 자동완성이 읽는 폴더(/etc/bash_completion.d/)에 넣는다
자주 사용하는 docker run 옵션 조합
# 개발용: 현재 디렉토리 마운트, 자동 삭제, 대화형
docker run -it --rm -v $(pwd):/app -w /app node:18-alpine sh
# 서비스용: 백그라운드, 재시작 정책, 포트 매핑
docker run -d --restart unless-stopped -p 8080:80 nginx
- 첫 줄 — 개발 환경 구성의 Node.js 명령을 한 줄로 적은 것이다
- 둘째 줄 —
--restart는 컨테이너가 끝났을 때 다시 켤지 정하는 재시작 정책이다. 기본값은no(다시 안 켬)다.unless-stopped는 늘 다시 켜되, 내가 직접 멈춘 컨테이너는 Docker 데몬이 다시 시작해도 켜지 않는다
주의 — 데이터를 잃는 명령과 자주 나는 실패
docker rm -f: 실행 중인 컨테이너를 강제 삭제하면 데이터 손실 가능. 컨테이너를 지우면 볼륨에 두지 않은 컨테이너 안의 파일은 사라진다(볼륨 참고)docker system prune -a: 사용하지 않는 모든 이미지가 삭제되므로, 그 이미지가 다시 필요하면 다시 내려받거나 다시 빌드하느라 시간이 든다- 볼륨 삭제:
docker compose down -v는 데이터를 영구적으로 삭제하므로 주의 - 포트 충돌:
-p 8080:80의 8080처럼 내 컴퓨터 쪽 포트를 다른 프로그램이 이미 쓰고 있으면 컨테이너가 뜨지 못한다 - 메모리 제한: 실제 사용자가 쓰는 운영 환경에서는
--memory옵션으로 메모리 제한 설정 권장.docker run --memory 512m nginx처럼 숫자 뒤에 단위(b,k,m,g)를 붙여 적고, 6MB보다 작게는 못 정한다
Windows · Linux · macOS는 설치하는 것이 다르다
Windows
- Docker Desktop 사용 필요
- WSL 2 백엔드 권장. WSL 2는 Windows 안에서 리눅스를 돌리는 기능이고, 「WSL 2 백엔드」는 Docker Desktop이 Docker 데몬을 그 WSL 2 위에서 돌리는 방식이다
- 볼륨 경로:
/c/Users/...형식 사용. Git Bash에서 Windows 폴더C:\Users를 가리키는 모양이다(내 PC의 Git Bash에서cd /c/Users가C:/Users로 가는 것을 확인했다). Docker가 바인드 마운트 경로로 이 모양을 받는지는 데몬이 꺼져 있어 실행해 보지 못했다
Linux
- Docker Engine 직접 설치. Engine은 데몬과
docker명령을 합친 것이라, 둘을 리눅스에 바로 깐다 - sudo 없이 사용하려면 docker 그룹에 사용자 추가:
sudo usermod -aG docker $USER. 리눅스에서docker명령은 데몬이 열어 둔 연결 통로(Unix 소켓)로 부탁을 보내는데, 이 통로의 주인이 root라서 다른 사용자는 원래 sudo를 붙여야 닿는다. 그룹은 여러 사용자를 한 이름으로 묶은 것이고(uid · gid의 gid가 그룹 번호다), 데몬은 그 통로를docker그룹에 든 사용자도 쓸 수 있게 열어 둔다. 그래서 내 계정을docker그룹에 넣으면 sudo 없이 닿는다.usermod는 사용자 계정 설정을 바꾸는 명령이고,-aG docker는 기존 그룹은 그대로 두고docker그룹을 더하라는 뜻이다.$USER는 지금 로그인한 사용자 이름이 든 환경변수다
macOS
- Docker Desktop 사용
- 파일 공유 성능: Bind mount보다 볼륨 사용 권장. 왜 macOS에서 성능 차이가 나는지는 이 글에서 다루지 않았다
관련 문서
자신만의 철학을 만들어가는 중입니다.
댓글남기기