🚨 필독 사안 🚨
적고 보니, Docker의 VOLUME, NETWORK 이해가 없으면 막상 하기 어려운 이미지 실습인 것 같습니다. 일단 이미지, 컨테이너를 이해하시고, VOLUME, NETWORK 본 이후 다시 돌아오는 것을 권장합니다.
- Docker 공부할 때는 어려웠고, 왜 쓰는 건지 1도 감도 안 잡혔는데, 막상 써보면 못 빠져 나온다.
- 이미지는 불변성이다. 클래스라고 생각하면 된다.
- 개발 환경을 Docker로 구축하고 컨테이너 내에서 작업한다면 VOLUME 연결은 꼭 하자. 안 그러면 야근한다.
- 멀티 스테이징, alpine 버전 사용하기는 꼭 고려하자.
- 포맷하면 개발 환경 Docker로 바꿔야지.
네이버 부스트 캠프에서 만든 최종 프로젝트 🎋Denamu의 개발 환경을 도커로 만들고자 했다. 이럴 경우 프로젝트 환경 구축을 위해 수행하던 것들(Git Pull, 의존성 설치, MySQL 설치, Redis 설치 등등... )이 딸깍 한 번으로 모든 환경 구축이 갖춰지도록 할 수 있다.
새로운 개발자가 팀에 참여하거나, 우리 중 누군가의 개발 환경이 바뀌었을 때 빠르게 구축할 수 있을 것이라 생각했다.
네이버 부스트 캠프에서 만든 최종 프로젝트 🎋Denamu를 운영하는 것을 목표로 했다만, 클라우드 비용 한계로 언젠가 서비스가 종료될 것이라 판단했다. 서비스가 운영되지 않을 경우 최소한 Docker로 만들어둔다면, 더 가치있는 포트폴리오가 될 것이라 생각했다. 최소한 돌려볼 수는 있으니까!
서버의 수평 확장 시, Docker를 사용한다면 환경 구축이 빠르게 되어 수평 확장 하기에도 간편할 것이라 생각했다.
기존 방식에는 개발 서버를 두지 않고 프론트엔드 개발자분들이 API를 직접 메인 서버 경로에 요청을 하는 방식이었다. 하지만, 운영의 입장이 된 만큼 메인 서버 경로에는 최대한 어떠한 짓을 해서는 안 되어야 사용자 경험 개선을 할 수 있을 것 같았다.
프론트엔드 개발자 입장에서는 본인 개발에 크게 필요 없는 서비스에 구축된 DB에 대해 깊은 이해가 필요해진다. (백엔드는 원래 익숙하잖아. 한잔해)
이미지를 통해 컨테이너를 만들 수 있다. 지금 당장에 이미지를 쉽게 이해하려면, 이미지는 하나의 클래스라 생각하면 되고, 컨테이너는 객체라고 생각하면 편하다. 나중에 달라지겠지만
이미지가 필요한 이유
대충 이미지가 어떤 것을 하는지 감이 슬슬 올 것이다.
그럼 이미지가 왜 필요한지에 대해 프로젝트를 통해 함께 확인해보자.
어떤 프로젝트에서 API 서버, Redis, MySQL을 이용해서 서비스를 구축했다고 가정한다.
Docker를 사용하지 않았을 때의 프로젝트 환경 구축 방식을 보자.

Git Pull로 원격 Repository를 로컬에 가져온다.
JS 프로젝트라면 의존성도 설치해준다. npm install
팀 외부로 유출되면 안 되는 환경 변수 (ex. 운영 DB IP 주소, 아이디, 비밀번호 등...)를 팀원끼리 공유하는 어떤 특정한 공간(ex. Slack, Discord, Notion 등...)에서 받고, 없을 경우 팀원에게 요청해서 환경 변수를 삽입한다. (민감한 환경 변수 파일은 업로드 되면 안 되고, .gitignore 처리를 해놓기 때문에 새로운 환경에서 레포지토리를 통해 받기에는 어렵다.
누군가에겐 서비스를 동작시키게 하는 용도 외에는 필요하지 않을 수 있는 Redis-Server를 설치한다.
Redis에 필요한 설정이 있다면 운영 환경(Redis 백업)과 동일하게 설정한다.
누군가에겐 서비스를 동작시키게 하는 용도 외에는 필요하지 않을 수 있는 MySQL-Server를 설치한다.
MySQL에 필요한 설정(DB 생성, DB 계정 생성 등)이 있다면 운영 환경과 동일하게 설정한다.
프로젝트 빌드를 하고 프로젝트 실행하는 명령어를 실행해야 한다.
$ npm run build
$ npm start
위 방식일 경우 문제점은
프론트엔드에서 백엔드 서버를 동작하는 방식을 알아야 로컬에서 개발용 서버를 열 수 있다.
프론트엔드가 개발을 위해 로컬에서 개발용 서버를 열고자 할 때, 서버와 연관된 DB나 각종 서비스들을 설치하고 셋팅해야 할 필요가 있다. ex) DB 만들기, DB 계정 만들기, 접속 권한 설정 등등...
새로운 환경에 프로젝트 환경을 구축하려면 위 일련의 과정을 전부 수행해야 한다.
포트폴리오로 제출해도, '제 컴퓨터에서는(만) 되는데요?'라는 말이 나올 수도 있다.
일련의 과정 중 오류가 발생할 수 있는 가능성이 높다. 설치에서도 그렇고, 환경 설정에서도 그렇고, 발생할 수 있는 가능성이 상당히 높다.
DB 서버 계속 백그라운드로 돌아가면 사양만 먹기에 상당히 찝찝하다.
이런 문제들이 있을 것이다. 특히 3번이 가장 걸리지 않을까 싶다.
그러면 5번의 러시안 룰렛을 또 시작한다. 어디서 오류 났게~?
환경 설정하는 것 보다 개발이 더 재밌으니 환경 설정을 최소화할 필요성이 있다.
우선 환경을 모두 동일하게 해야 한다. 운영체제에 따라 발생할 수도 있고 안 할수도 있는 오류들이 있다 보니 Linux 기반으로 돌아가는 Docker를 사용한다면 Linux 환경으로 통합되기에 어디서든 돌릴 수 있다.
그렇다면 프로젝트 이미지, MySQL 이미지, Redis 이미지를 각각 받고, 각각 컨테이너를 찍어낸 후에 돌리면?

1. 프로젝트 환경을 내려 받는다.
2. 컨테이너 실행한다.
3. DB 환경을 내려 받는다.
4. 컨테이너 실행한다.
5. Redis 환경을 내려 받는다.
6. 컨테이너 실행한다.
끝이다.
위에서 Docker를 사용하지 않았을 때의 주절 주절한 과정을 간단하게 처리할 수 있다.
심지어 프로젝트 이미지, MySQL 이미지, Redis 이미지를 이용해서 컨테이너를 2개씩 만들어내면 프로젝트 환경을 2개나 만들 수 있다. (뻘짓이긴 하겠지만 럭키비키☆
그럼 위에서 Docker를 사용하지 않았을 때 문제점을 보자.
1. 프론트엔드에서 백엔드 서버를 동작하는 방식을 알아야 로컬에서 개발용 서버를 열 수 있다.
-> 딸깍 한 번으로 로컬에서 개발용 서버를 열 수 있다.
2. 프론트엔드가 개발을 위해 로컬에서 개발용 서버를 열고자 할 때, 서버와 연관된 DB나 각종 서비스들을 설치하고 셋팅해야 할 필요가 있다. ex) DB 만들기, DB 계정 만들기, 접속 권한 설정 등등...
-> 환경 구축하는 것 또한 이미지로 만들어 둔다면, 딸깍 한 번으로 DB 설치부터 설정까지 가능하다. 추상화가 가능하다.
3. 새로운 환경에 프로젝트 환경을 구축하려면 위 일련의 과정을 전부 수행해야 한다.
-> 이미지들 들고 와서 돌리면 끝난다. 대신 Docker를 설치해야 하겠지만, 3개 설치할 거 하나로 줄면 꿀? 그리고 예시가 3개(API 서버, MySQL, Redis)지, 실제 서비스는 몇 개를 설치해야 할지 모른다.
4. 포트폴리오로 제출해도, '제 컴퓨터에서는(만) 되는데요?'라는 말이 나올 수도 있다.
-> 리눅스 기반으로 돌아가기에 어느 환경에서든 동일하게 돌아간다.
5. 일련의 과정 중 오류가 발생할 수 있는 가능성이 높다. 설치에서도 그렇고, 환경 설정에서도 그렇고, 발생할 수 있는 가능성이 상당히 높다.
-> 리눅스 기반이라 환경은 모두 같고, 설정 과정을 스크립트로 만들어놓는 것이기에 딸깍 한 번으로 설치부터 환경 설정까지 전부 다 가능하다.
6. DB 서버 계속 백그라운드로 돌아가면 사양만 먹기에 상당히 찝찝하다.
-> 필요 없을 때는 끄면 된다. 그리고 프로세스마다 독립적이기에 DB 자체를 독립적으로 다룰 수 있다. MySQL Server 같은 경우 Docker를 사용하지 않은 상황에서 여러 프로젝트에 맞게 하려면 MySQL Server 내부에서 여러 DB를 생성해서 관리할 수 있지만, 몇 년후에 내가 만든 DB를 보고 이게 뭐지!를 할 수 있기에 이런 가능성이 많이 줄어든다.
💡 나중에 Docker 컴포즈 하면 각 환경 주머니들(컨테이너)도 묶을 수 있다.
그럼 N번딸깍할 것을 단 1회딸깍으로 끝낼 수 있다.
그렇다면, 이미지는 결국 실행 환경 구축의 과정을 스크립트로 만들어 둔 것이고, 이를 이용해서 각자 격리된 컨테이너를 만들 수 있게 해놓은 템플릿이다.
이미지 자체는 불변성이기에 이미지를 수정할 수는 없다.
그렇기에 밑에서 나오는 이미지를 만들기 위해 스크립트를 적어두는 것을 Dockerfile이라고 하는데 이 파일을 바꾸고 아예 재빌드를 해서 다른 이미지를 만들어야 한다.
이미지는 Docker Hub 또는 Private Registry를 통해 공유가 가능하다.
실행할 수 있는 형태는 아니다! 이미지를 통해 컨테이너를 찍어내고, 컨테이너를 실행해야 하는 것이기에 이미지 자체는 실행할 수 없다.
$ docker images # 현재 내 컴퓨터에 존재하는 이미지들의 목록을 본다.
$ docker pull <이미지_이름>[:태그명] # Docker Hub에서 땡겨온다. Github Pull이랑 동일하다.
$ docker rmi <이미지_이름> # 설치된 이미지를 제거한다.
$ docker search <이미지_부분 이름> # Docker Hub에서 검색도 된다. 다만, Docker Desktop이 더 보기 좋은듯
$ docker image prune -a # 사용되지 않는 이미지만 삭제 (컨테이너로 존재 안 하는 이미지)
$ docker image prune # 모든 이미지 삭제


프로젝트 디렉토리 최상단에 Dockerfile과 .dockerignore 파일을 만든다.
Dockerfile에는 이미지에 포함될 스크립트들을 작성한다.

.dockerignore에는 Docker 이미지 빌드 시 포함하지 않을 파일과 디렉터리를 지정하는 역할을 수행한다. .gitignore랑 같다고 생각하면 된다.
대표적으로 어떤 것들이 있을까?
node_modules(의존성 코드), dist(빌드 된 코드), .git(깃), .env(환경 변수), logs, .vscode 등 프로젝트를 동작할 때 필요 없는 것들을 넣으면 된다.
다만 이건 환경마다 다르니까, 100% 정답이라고 생각하지는 말자.
예를 들어, 개발 환경을 위한 이미지를 만들 수 있고, 배포 환경을 본뜨기 위한 이미지를 만들 수도 있기에 본인이 사용 목적에 따라 잘 포함시키고 빼야지 최적화된 크기의 이미지를 만들 수 있다.
❗ 이미지 너무 크면 Pull 할 때 이미지를 다운받는 게 커서 화딱지 나고, 소스 코드가 많이 포함되어 있다면 build 할 때 코드들을 포함하는 시간 때문에 화딱지난다. 그러니 최적화는 필수다.
응용하면 아래와 같이 환경에 따라 여러 Dockerfile을 둘 수도 있다.

(이렇게 해도 되는 건지는 솔직히 잘 모르겠다. Docker 이번에 처음 써보면서 공부한 내용을 적는거라, 다음에 책이나 강의보거나 다른 프로젝트들 참고해보고 다시 알아오겠다.
$ docker build -t <이미지_이름>[:태그명]
ex)
$ docker build -f docker/Dockerfile.dev -t denamu-was-dev
| 옵션 | 설명 |
|---|---|
| --add-host strings | 호스트-아이피 매핑을 추가한다. |
| --allow strings | network.host, security.insecure 같은 특권 권한을 허용한다. |
| --nework string | Docker 빌드에 연결할 네트워크를 선택하여 컨테이너간 통신이 가능하게 한다. |
| 옵션 | 설명 |
|---|---|
| --annotation stringArray | 주석을 추가한다. |
| --attest stringArray | 인증 매개변수를 추가한다. |
| --label stringArray | 이미지에 메타데이터를 키-값으로 설정한다. |
| --metadata-file string | 빌드 결과 메타 데이터를 지정된 파일에 기록한다. |
| --provenance string | 빌드의 출처나 소스를 기록하는 데 사용한다. |
| 옵션 | 설명 |
|---|---|
| --build-arg stringArray | 빌드 타임 변수들을 정의하여 Dockerfile 내에서 참조할 수 있게 한다. |
| --build-context stringArray | 추가 빌드 컨텍스트 지정이 가능하다. |
| --builder string | 빌더 인스턴스를 재정의한다. |
| 옵션 | 설명 |
|---|---|
| --cache-from stringArray | 외부 캐시 소스를 지정하여 이전 빌드의 레이어를 재사용 |
| --cache-to stringArray | 빌드 캐시를 외부로 보내는 목적지 |
| --no-cache | 캐시를 사용하지 않고 모든 것을 새로 빌드한다. |
| --no-cache-filter stringArray | 빌드 중 일부 스테이지에 대해서만 캐시를 사용하지 않도록 지정한다. |
| 옵션 | 설명 |
|---|---|
| --output stringArray | 빌드 결과를 출력할 목적지를 지정 |
| --platform stringArray | 빌드 대상 플랫폼을 설정 |
| --target string | 멀티 스테이지 빌드에서 특정 빌드 단계를 타겟으로 설정 |
| 옵션 | 설명 |
|---|---|
| -D, --debug | 디버그 로깅으로 빌드 과정의 문제 추적 가능 |
| -f, --file string | 사용할 Dockerfile의 이름을 지정, default = Dockerfile |
| -q, --quiet | 빌드 출력은 억제하고 빌드가 성공하면 이미지 ID만 출력 |
| 옵션 | 설명 |
|---|---|
| --iidfile string | 이미지 ID를 파일로 저장 |
| --load | 빌드한 이미지를 로컬 Docker에 로드 |
| --push | 빌드한 이미지를 레지스트리로 푸시 |
| --shm-size bytes | 빌드 컨테이너에 대한 공유 메모리 크기를 설정 |
| --secret stringArray | 빌드 중 사용할 비밀 정보를 지정 |
| --ssh stringArray | 빌드 중 SSH 에이전트 소켓이나 키를 노출시킬 때 사용 |
도커 이미지는 레이어 형식으로 이루어져 있다. 여기서도 마트료시카를 꺼낼 수 밖에...

색깔이 동일한 부분의 레이어를 READ/WRITE로 읽고 쓸 수 있는 영역이라 생각하자.
마지막 이미지에서는 소스 코드만 건들일 수 있고, 그 외의 이미지들은 건들일 수 없다.
대부분 가장 원조가 되는 이미지는 Ubuntu나 alpine이나 Debian 같은 Linux 배포 이미지가 된다.
그 이후 Node 같은 이미지 안에 리눅스 배포판 이미지들이 들어온다.
WAS를 띄우려면 Node 이미지(환경)가 필요할 것이다.
Node 이미지에는 리눅스 배포판 이미지를 포함한 후, WAS를 빌드하기 위한 레이어들을 추가하여 하나의 WAS 이미지로 만든다.
이렇게 사전에 만들어진 이미지에 새로운 레이어를 붙여 새로운 이미지를 만들 수 있다.
레이어 방식의 장점은 무엇일까?
변경된 부분만 다시 빌드하면 된다.
각 명령어(ex. RUN, COPY, ADD)가 새로운 레이어를 생성하고, Dockerfile에서 변경이 발생한 부분이 있다면 변경된 레이어만 다시 빌드하고 나머지 레이어는 캐시를 사용하도록 하여 이미지를 빠르게 빌드할 수 있다.
레이어 방식을 사용할 경우 여러 이미지를 공유할 수 있다.
동일한 레이어가 여러 이미지에서 사용된다면, 해당 레이어는 한 번만 저장되고 여러 이미지에서 재사용 가능하다.
불변이다.
레이어는 불변이기에 한 번 만들어진 레이어는 변경할 수 없다. 새로운 명령어가 실행되면 기존 레이어 위에 새로운 레이어가 추가된다. 즉, 과거의 상태를 보존하면서 변경된 부분만 처리할 수 있다.
베이스 이미지
FROM <이미지_이름>:<태그>
ex)
FROM node:22-alpine
이전 이미지 하나를 선택해야 한다. NestJS를 돌리기 위해서는 Node 이미지를 선택해야 할 것이고, FastAPI를 돌리기 위해서는 Python 이미지를 선택해야 할 것이다.
메타 데이터
LABEL 키="값"
ex)
LABEL maintainer="your_email@example.com"
LABEL version="1.0"
이미지에 메타 데이터를 추가하는 명령어다.
작업 디렉토리
WORKDIR <컨테이너 내 이동_경로>
ex)
WORKDIR /var/web05-Denamu
쉘의 Change Directory(cd) 명령과 동일하게 작업 디렉토리를 선택한다.
이후의 모든 명령은 작업 디렉토리 기준으로 이루어진다.
💡 디렉토리가 없으면 자동으로 만들어준다. 리눅스의 mkdir -p로 만들고 cd 처리하는 듯
파일 복사
COPY <HostOS 경로> <컨테이너 경로>
ex)
COPY package*.json .
HostOS 경로에 있는 파일이나 디렉터리를 컨테이너 경로로 복사한다.
빌드 시 실행
RUN ["명령어","명령어2","명령어3"]
RUN 전체 명령어
ex)
RUN npm ci
쉘에서 커멘드 실행하는 것과 동일하다. 빌드 시 실행하며 소프트웨어 설치용, 의존성 설치용으로 자주 사용된다.
컨테이너 실행마다 사용
CMD ["실행 파일","인자1","인자2"]
ex)
CMD ["npm","run","start"]
컨테이너가 실행될 때마다 기본적으로 실행할 명령어를 지정한다.
❗ CMD는 1개만 존재해야 한다.
❓ 그럼 Base Image의 CMD는 어떻게 되나요? 무시된다. 가장 최상위 레이어의 CMD만 수행한다.
컨테이너 실행 시 무조건 실행되는 명령어
ENTRYPOINT ["실행 파일", "인자1", "인자2"]
ex)
ENTRYPOINT ["python", "app.py"]
ENTRYPOINT가 CMD보다 강력한 느낌이다. CMD는 컨테이너 실행 시 다른 명령어로 덮어쓸 수 있지만, ENTRYPOINT는 필수적으로 수행하는 것이라 추가적으로 인자를 받고 인자를 실행하는 방식인 것 같다.
환경 변수
ENV 변수명=값
ex)
ENV NODE_ENV=production
컨테이너 내 환경 변수를 설정한다. 컨테이너 내에서만 유지된다.
빌드 시 전달할 변수 설정
ARG 변수명=기본값
ex)
ARG APP_VERSION=1.0
RUN echo "App Version: $APP_VERSION"
$ docker build --build-arg APP_VERSION=2.0 -t testimage
컨테이너에서 열어줄 포트 지정
EXPOSE 포트 번호
ex) EXPOSE 8080
$ docker run -p 8000[HostOS Port]:8000[Container Port] testimage
컨테이너에서 열어줄 프로세스 포트 번호를 입력한다.
볼륨
VOLUME 경로
ex)
VOLUME /var/lib/mysql # MySQL 데이터 저장
컨테이너와 호스트 간의 데이터 공유를 위한 익명의 볼륨을 생성하고 연결한다.
VOLUME은 데이터 영속성에 유용하다.
유저 선택
USER 사용자
ex)
USER node
컨테이너에서 실행될 기본 사용자를 설정한다.
헬스 체킹
HEALTHCHECK --interval=시간 --timeout=시간 --retries=횟수 CMD 명령어
ex)
HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -f http://localhost:8080 || exit 1
컨테이너 이미지를 만들면서 빌드 등에는 필요하지만, 최종 컨테이너 이미지에는 필요 없는 환경을 제거할 수 있게 한다.
예를 들어 보자,
NGINX를 통해 정적 파일 배포를 하게 해야 한다.
하지만, 그 전에 프론트엔드 코드가 빌드가 되어야 한다.
그럴려면 node나 NGINX 이미지를 베이스 이미지로 하고 베이스 이미지로 선택되지 않은 것을 별도로 설치해야 한다.
생각을 해보자. node는 과연 필요할까? 빌드 할 때 외에는 실행 환경에서 필요가 없다.
그럼 node를 이미지 안에 들고가는 것은 매우 비효율적인 결과를 낳는다.
해결책으로는 멀티 스테이지 빌드를 사용한다.
1번 스테이지에서는 노드를 베이스 이미지로 이용해서 빌드를 하게 만든다.
빌드를 완료하고 나면, dist(빌드된 파일)만 다음 스테이지로 옮긴다.
2번 스테이지에서는 NGINX를 베이스 이미지로 이용해서 1번 스테이지에서 나온 빌드 파일을 복사해서 가지고 있고 NGINX를 시작하게 한다.
❓ node를 1 스테이지로 하고 2 스테이지도 node를 하는 건 과연 유용한가?
위와 같은 Dockerfile이 있다고 가정한다.
스테이지 1 = 의존성 설치
스테이지 2 = node_modules만 가져온다.이렇게 했을 경우에는? npm 설치를 하면서 발생한 캐시는 스테이지 1에 방생해두고 스테이지 2로 알맹이만 뽑아가는 것이기에 이미지 크기가 줄어든다.
프로덕션 이미지에 넣으면 완전 좋을 것 같다!
빌드만 하고 빌드된 코드를 가져간다면 용량이 확 줄어든다.
Docker 이미지의 크기는 줄이는 게 가장 좋다.
세 가지 방법을 소개한다.
실행 환경에 필요없는 파일은 .dockerignore 처리해서 용량을 줄이자.
위에서 설명한 내용이다. .dockerignore로 들고가는 파일을 최소화한다.
alpine 버전을 사용하자.
Alpine Linux는 초소형 베이스 이미지이다.
경량화된 Linux 배포판이기에 여러 불필요한 패키지를 제거한 상태에서 제공된다.
bash도 없다. <- 예?
기본 이미지 크기가 3~5MB다. TMI) Ubuntu Docker 이미지가 29MB다.
실 사용 단점
몇몇 기능이 없어서 설치를 해야 하는데, glibc(GNU C Library) 대신 musl libc를 사용하기에 일부 기능이 호환되지 않을 수 있다. ex) Ubuntu는 cron인데, alpine은 crond였다.
멀티 스테이징을 사용하자.
위 멀티 스테이징을 참고하자.
이미 위에서 다 말하고 와서 컨테이너가 무엇인지 알게 되었을 것 같다.
컨테이너는 불변의 객체라고 생각하면 편하다. 당장에는 내부 파일이 바뀔 수 있지만, 컨테이너를 재시작할 경우, 변경 사항들이 사라진다.
ex) 개발 환경 구축을 해둔 컨테이너가 있다. 그 컨테이너에 작업을 했는데 실수로 컨테이너를 껐다 킨다.
-> 야근이다.

야근을 막기 위해 볼륨(Volume)을 사용하면 데이터 영속성을 유지할 수 있다.
볼륨이 설정되어 있을 경우 컨테이너를 껐다 켜도, 코드는 유지되어 있다. ex) MySQL 이미지는 자동으로 볼륨 설정되게 되어 있어서 컨테이너를 껐다 켜도 데이터는 유지되어 있다.
볼륨은 자세하게 다루기 위해 다음 포스팅으로 미룬다.
$ docker run [옵션] <이미지_이름> # 컨테이너 생성
옵션이 너무 많아서 주요 옵션만 정리합니다.
★: 자주 사용
| 주요 옵션 | 설명 |
|---|---|
| -a | 컨테이너의 표준 입출력(STDIN, STDOUT, STDERR)에 연결 |
| -c | 컨테이너의 CPU 사용 우선순위를 설정 |
| ★ -d | 컨테이너를 백그라운드에서 실행한다. 컨테이너 ID 출력도 함 |
| ★ -e | 컨테이너 내부 환경 변수를 설정한다. |
| -h | 컨테이너의 호스트 이름을 설정한다. |
| ★ -i | 컨테이너에 표준 입력을 유지하여 사용자 입력을 받을 수 있게 한다. (-it로 자주 사용한다.) |
| -l | 컨테이너에 메타데이터(라벨)을 추가한다. |
| -m | 컨테이너의 메모리 제한을 설정한다. |
| ★ -p | 호스트와 컨테이너 간 포트를 매핑한다. |
| -P | 컨테이너에서 노출된 모든 포트를 랜덤한 호스트 포트에 매핑한다. |
| -q | 실행 결과를 최소한으로 출력한다. |
| ★ -t | 가상 터미널을 할당하여 터미널 출력을 지원한다. (-it로 자주 사용한다.) |
| -u [사용자] | 컨테이너 내부에서 실행할 사용자를 지정한다. |
| ★ -v | 볼륨 연결을 한다. |
| ★ -w | 컨테이너의 작업 디렉토리를 지정한다. |
| ★ --rm | 컨테이너가 종료될 때 자동으로 삭제된다. (임시 컨테이너 용도) |
$ docker exec [옵션] <컨테이너_이름> 실행_명령 # 컨테이너 내에서 명령어 실행
| 주요 옵션 | 설명 |
|---|---|
| -d | 컨테이너를 백그라운드에서 실행 |
| -e | 환경 변수 설정 |
| -i | 표준 입력(STDIN) 유지 |
| -t | 가상 터미널 할당 |
| -u [사용자] | 특정 사용자로 실행 |
| -w | 컨테이너 내부 작업 디렉토리 변경 |
$ docker commit [옵션] <컨테이너_이름_or_id> <새로운_이미지_이름>
# 이미 실행중인 컨테이너를 이미지로 다시 만들 수 있다.
| 주요 옵션 | 설명 |
|---|---|
| -a | 이미지 작성자 정보 추가 |
| -c | Dockerfile 명령을 적용하여 이미지 수정 |
| -m | 커밋 메시지 추가 |
| -p | 커밋 중 컨테이너 일시 정지 default=true |
$ docker ps # 컨테이너 목록 확인

| 주요 옵션 | 설명 |
|---|---|
| -a | 모든 컨테이너 표시 (default = 실행 중인 컨테이너만 표시) |
| -f | 특정 조건에 맞는 컨테이너만 표시 |
| -n [숫자] | 최근 생성된 개수의 컨테이너 표시 |
| -l | 가장 최근에 생성된 컨테이너 표시 |
| -q | 컨테이너 ID만 표시 |
| -s | 컨테이너 전체 파일 크기 표시 |
$ docker start [옵션] <컨테이너_이름> # 컨테이너를 실행
| 주요 옵션 | 설명 |
|---|---|
| -a | 컨테이너 표준 출력(STDOUT), 오류 출력(STDERR)을 현재 터미널과 연결 |
| -i | 컨테이너 표준 입력(STDIN) 활성화 |
$ docker stop [옵션] <컨테이너_이름> # 컨테이너 종료
| 주요 옵션 | 설명 |
|---|---|
| -s [신호] | 컨테이너에 특정 종료 신호(SIGNAL) 전송 default = SIGTERM |
| -t [초] | 강제 종료 전 대기 시간 설정 Linux default = 10, Window default = 30 |
$ docker restart [옵션] <컨테이너_이름> # 컨테이너 재시작
| 주요 옵션 | 설명 |
|---|---|
| -s [신호] | 컨테이너에 특정 종료 신호(SIGNAL) 전송 default = SIGTERM |
| -t [초] | 강제 종료 전 대기 시간 설정 Linux default = 10, Window default = 30 |
$ docker rm [옵션] <컨테이너_이름> # 컨테이너 삭제
| 주요 옵션 | 설명 |
|---|---|
| -f | 실행 중인 컨테이너를 강제로 삭제할 때 사용. SIGKILL 신호 전송 |
| -l | 컨테이너와 관련된 링크만 제거 |
| -v | 익명 볼륨도 함께 삭제할 때 사용 |
$ docker container prune # 필요없는 컨테이너 제거
| 주요 옵션 | 설명 |
|---|---|
| -f | 강제 삭제 |
| --filter [조건] | 필터 조건에 맞는 컨테이너들 제거 |
$ docker logs [옵션] <컨테이너_이름> # 지정한 컨테이너의 로그 확인

| 주요 옵션 | 설명 |
|---|---|
| -f | 로그를 실시간으로 추적하며 출력할 때 사용 |
| -n [개수] | 로그의 끝에서부터 n개의 줄만 출력 |
| -t | 로그에 타임 스탬프를 함께 출력 |
$ docker cp <컨테이너_이름>:컨테이너_경로 HostOS경로 # 컨테이너 내 파일을 호스트로 복사
$ docker cp HostOS경로 <컨테이너_이름>:컨테이너_경로 # HostOS 경로에 있는 파일을 컨테이너 내로 복사
| 주요 옵션 | 설명 |
|---|---|
| -a | 아카이브 모드로 복사할 때, 파일의 UID/GID 메타데이터 포함하여 복사 |
| -L | 원본 경로가 심볼릭 링크일 때, 그 링크를 따라가서 실제 파일 복사 |
| -q | 복사하는 동안 진행 상태 출력 억제 |
$ docker stats # 실행 중인 컨테이너들의 리소스 사용 현황을 실시간으로 모니터링

실시간으로 컨테이너의 자원 소모량을 볼 수 있다.
$ docker top <컨테이너_이름> # 컨테이너 내에서 실행 중인 프로세스 확인
컨테이너 내에서 실행중인 프로세스의 사용량을 확인한다.

$ docker rename <컨테이너_이름> <새_이름> # 이름 변경
컨테이너 이름만 변경한다.
사용자가 원하는 이미지가 없을 경우(ex. 본인이 만든 프로젝트 환경을 이미지화 하는 경우) 동작 순서

Docker File라는 도커 이미지로 만드는 빌드 스크립트를 작성한다.
Docker File을 이용해서 도커 이미지로 만드는 명령어를 수행할 경우 빌드 스크립트를 차근 차근 수행해 나간다.
이미지가 생성되면 로컬 Registry(로컬 레포지토리)에 저장이 된다.
도커를 잘 사용한다면, 확실히 인프라쪽에서는 상당한 이점을 가져올 것 같다. CI/CD도 빠르게 가능할 것 같고, 어디서든 작업 환경을 만들어버리기 너무 좋다.
앞으로 더 Docker에 대해 깊게 다뤄보겠다.
반성
적고 보니, VOLUME과 Docker의 네트워크를 다 이해하고 수행해야 하는 것인데, 너무 빠르게 적어버린 것 같다.
프로젝트 기간은 한정적이다 보니, 일단 수행한 거 위주로 작성을 해버렸다. 다음에는 블로그 포스팅 잘 신경쓰자.