Super Kawaii Cute Cat Kaoani
본문 바로가기
{Bootcamp}/KT Cloud Tech up 클라우드 인프라

[kt cloud] 도커 실습 (2)

by wonee1 2026. 9. 16.
728x90

 

 

1. Dockerfile 

 

1-1. Dockerfile 구성 요소

 

기본 문법 규칙

주석 # 으로 시작
기본 형식 <명령> <매개변수>
대소문자 구분 안 하지만 관례상 명령은 대문자 (FROM, RUN...)
시작 반드시 FROM으로 시작
각 명령 독립적으로 실행 (각 줄이 하나의 레이어)

 

→ 각 명령은 독립적이 중요한데, 이게 앞에서 배운 레이어(layer) 구조와 연결된다. 명령 하나 = 이미지 레이어 하나씩 쌓이는 것.

 

주요 명령어

FROM base image 지정 (로컬에 없으면 Docker Hub에서 자동 download)
MAINTAINER 생성자 정보 (예: hhs <hhs@hhs.com>)
RUN 빌드 중 명령 실행
ADD 파일/디렉터리 추가
ENV 환경 변수 생성
CMD 컨테이너 실행 시 돌릴 프로세스 지정

 

 

FROM

FROM ubuntu
→ 로컬에 ubuntu 이미지 있나 확인
→ 없으면 Docker Hub에서 자동 pull

 

RUN vs CMD 

RUN : 이미지 "만들 때" 실행 (빌드 시점)   예) RUN apt install nginx
CMD : 컨테이너 "띄울 때" 실행 (실행 시점)  예) CMD nginx -g "daemon off;"

 

→  CMD는 컨테이너가 뜨는 순간 실행되니까, 여기 오타 나면 뜨자마자 죽는다.

 

ADD vs COPY

COPY : 단순 복사 (로컬 파일 → 이미지)
ADD  : 복사 + 압축 해제 + URL 다운로드까지 (기능 더 많음)
→ 단순 복사면 COPY 권장 (명확함)

 

 

MAINTAINER

요즘은 LABEL maintainer="..." 방식으로 대체됨
(MAINTAINER는 옛날 문법, 지금도 동작은 함)

 

 

 

 

1-2. Docker 이미지 빌드 

 

Docker 이미지 빌드

Dockerfile로 이미지를 만드는 docker build 명령이다. 

docker build <options> <Dockerfile 경로>

 

docker build Dockerfile을 읽어 이미지 생성
<options> -t(태그) 등
<Dockerfile 경로> Dockerfile이 있는 위치 (보통 . = 현재 폴더)

 

-t (--tag) 옵션 — 이미지 이름 붙이기

docker build -t hhs/hello_world:1.0 .
             ↑  ↑    ↑          ↑
           태그 사용자ID 이미지이름  태그(버전)

 

[사용자ID]/[이미지이름]:[tag]

hhs / hello_world : 1.0
 ↑        ↑         ↑
Docker   이미지     버전
Hub용    이름      (생략시 latest)

 

사용자ID Docker Hub에 올릴 때 필요 (안 올리면 생략 가능)
이미지 이름 필수
:tag 버전 표시, 생략하면 자동으로 :latest

 

 

docker build -t myapp .              # → myapp:latest
docker build -t myapp:1.0 .          # → myapp:1.0
docker build -t hhs/myapp:1.0 .      # → Docker Hub용 (hhs 계정)

 

빌드 위치 & 동작 원리

Dockerfile이 있는 위치에서 build
         ↓
Docker가 Dockerfile 구문을 한 줄씩 처리
         ↓
FROM → RUN → COPY → CMD 순서로 레이어 쌓음
         ↓
완성된 이미지를 return (로컬 이미지 목록에 저장)

 

→ 앞에서 배운 레이어 구조가 여기서 만들어진다. Dockerfile의 각 명령이 실행되면서 이미지 레이어가 하나씩 쌓이는 것.

 

마지막 . 의 의미 

docker build -t myapp .
                      ↑
              "현재 폴더"를 build context로 지정
              (여기서 Dockerfile과 COPY할 파일들을 찾음)

 

 

 

 

1-3.dockerignore & 도커 구문

 

 .dockerignore — 빌드에서 제외할 파일

 

Context = Dockerfile과 같은 위치의 "모든 파일"
          ↓
build 시 이 파일들이 전부 docker 엔진으로 전송됨
          ↓
불필요한 파일이 많으면? → 전송 느려지고 이미지 커짐 ⚠️

 

# vi .dockerignore
sub_class/test.txt     # 특정 파일 제외
sub_class/*.ppt        # 특정 폴더의 ppt 전부 제외
*.cpp                  # 모든 cpp 파일 제외
.svn                   # 버전관리 폴더 제외
.git                   # git 폴더 제외

 

파일명 그 파일 하나 제외
폴더/*.확장자 특정 폴더의 해당 확장자 전부
*.확장자 모든 위치의 해당 확장자
.git, .svn 소스관리 폴더 (이미지에 불필요)

 

→ .gitignore랑 같은 개념이야. 빌드에 안 넣을 것들을 미리 적어두는 것.

 

CMD 구문 — 정식 문법

 

# 방식 1 — shell 형식 (/bin/sh 기반)
CMD nginx -g "daemon off;"
CMD /usr/local/bin/app-server

# 방식 2 — exec 형식 (/bin/sh 없이, 권장)
CMD ["nginx", "-g", "daemon off;"]
CMD ["mysqld", "--datadir=/var/lib/mysql"]
     ↑           ↑
   실행파일    매개변수 (각각 따옴표로 분리)

 

shell CMD 명령 인자 /bin/sh -c로 감싸서 실행
exec CMD ["명령","인자1","인자2"] sh 안 거치고 직접 실행 (권장)

 

 

CMD 핵심 특징

  • Container 실행 시점에 처리 (docker run / start 할 때) — 빌드가 아니라 실행 때!
  • Dockerfile에서 한 번만 사용 (main service 하나) — 여러 개 쓰면 마지막 것만 적용
  • /bin/sh 기반 실행이 기본 (shell 형식일 때)

 

 CMD + ENTRYPOINT 조합 

ENTRYPOINT ["mysqld"]                      # 고정 명령 (항상 실행)
CMD ["--datadir=/var/lib/mysql"]           # 기본 인자 (바꿀 수 있음)
ENTRYPOINT = 바뀌지 않는 실행 명령 (틀)
CMD        = ENTRYPOINT에 넘길 "기본 매개변수"

→ docker run 할 때 인자 주면 CMD 부분만 교체됨
  docker run myimage --datadir=/other  → mysqld --datadir=/other

 

→ ENTRYPOINT와 같이 쓰면 CMD는 매개변수만 전달하는 역할이 된다.

 

 

CMD 실습 

 

도커 파일 작성
이미지 빌드

 

이미지 실행 & 컨테이너 생성

 

 

 

Dockerfile 구문 — ENV (환경변수)

이미지 안에서 쓸 환경변수를 정의하는 명령이다.

ENV <환경변수> <value>
ENV PATH /home/hhs/bin:$PATH
    ↑    ↑
  변수명  값

 

적용 범위 RUN, CMD, ENTRYPOINT에서 사용 가능
사용 방법 $변수명 형태로 참조
덮어쓰기 docker run -e로 실행 시 overwrite 가능

 

ENV PATH /home/hhs/bin:$PATH    # PATH 변수 설정
CMD echo $PATH                  # $PATH로 참조 → 값 출력
ENV로 정의 → $PATH로 꺼내씀
결과: /home/hhs/bin:/usr/bin:/bin... (설정한 값 출력)

 

-e로 덮어쓰기 (실행 시 변경)

docker run -e PATH=/tmp/project test:env
           ↑
    이미지에 박힌 ENV 값을 실행할 때 바꿔치기
이미지 안 ENV : PATH=/home/hhs/bin:$PATH   (기본값)
        ↓ docker run -e PATH=/tmp/project
실제 실행 시   : PATH=/tmp/project          (덮어써짐)

 

→ 이미지를 다시 빌드하지 않고도, 실행할 때마다 환경변수를 바꿀 수 있다. 같은 이미지로 개발/운영 환경을 다르게 쓸 때 유용하다.

env 장점

장점 1: 값을 한 곳에서 관리 (여러 줄에서 $변수로 재사용)
장점 2: docker run -e로 유연하게 변경 (재빌드 불필요)
예시: 버전, 경로, 설정값 등을 변수로 빼두기

 

 

Dockerfile 구문 — EXPOSE

 

EXPOSE <port no.>....

 

목적 host와 연결만 목적, 외부에 노출 안 됨
실제 외부 접속 컨테이너 실행 시 -p/-P로 host port 연결 필요
형식 -p host_port:container_expose_port

 

① docker run --rm -d -p 8080:80 --name web nginx
② docker inspect web
③ curl 172.17.0.4:80

 

docker run --rm -d -p 8080:80 --name web nginx nginx 컨테이너 실행
docker inspect web 컨테이너 상세정보(IP 등) 확인
curl 172.17.0.4:80 컨테이너 IP로 직접 접속 테스트

 

--rm = 컨테이너 종료되면 자동 삭제
       → 테스트용으로 딱 좋음 (지저분한 Exited 컨테이너 안 남김)

→ 앞 실습에서 docker ps -a에 죽은 컨테이너들(pensive_noether 등)이 쌓여 있었지. --rm 쓰면 그런 게 안 남는다.

docker inspect:

docker inspect web
→ 컨테이너의 IP, 포트, 볼륨, 네트워크 등 모든 상세정보 출력
  (앞에서 /etc/hosts로 IP 봤던 것보다 정확한 방법)

 

두 가지 접속 방법 비교

방법 1 (컨테이너 IP 직접):  curl 172.17.0.4:80
  → 호스트 안에서만 가능, 컨테이너 IP 알아야 함

방법 2 (포트 매핑):  http://host_ip:8080
  → 외부(윈도우 크롬 등)에서 접속 가능! ← -p 8080:80 덕분

 

윈도우 크롬에서 접속

http://host_ip:8080
        ↑        ↑
     rocky IP   -p로 매핑한 호스트 포트
→ -p 8080:80 이 있어야 외부 브라우저 접속 가능

→ 이게 EXPOSE와 -p의 차이를 실제로 보여준다. 외부(윈도우) 접속이 되는 건 -p 8080:80 때문이다.

 

EXPOSE 80 443     # 80, 443 두 포트 선언 (한 줄에 여러 개)
EXPOSE 8080       # 8080 선언

 

 

 

 

 

 

 

 

 

1-4.Dockerfile 최적화하기 

 

Dockerfile을 더 효율적으로 개선하는 방법이다. 

 

RUN/ENV 여러 줄 → 하나로 통합 명령 하나 = 레이어 하나 → 통합하면 레이어 감소
안 바뀌는 건 앞, 바뀌는 건 뒤 빌드 캐시 활용률 ↑ → 빌드 시간 ↓

 

RUN 통합 (Before vs After)

# Before — RUN 3개 (레이어 3개)
RUN mkdir -m 1777 /share
RUN touch /share/hello.txt
RUN useradd student

# After — RUN 1개 (레이어 1개) ✅
RUN mkdir -m 1777 /share \
    && touch /share/hello.txt \
    && useradd student
\    = 다음 줄로 이어짐 (줄바꿈 무시)
&&   = 앞 명령 성공하면 다음 실행

→ 3개 명령을 1개 RUN으로 묶음 → 레이어 3개 → 1개로 감소

 

→ 레이어가 줄면 이미지가 가벼워지고 관리도 쉬워진다.

 

레이어 캐시

 

캐시 작동 원리

빌드 시 각 레이어를 해시값으로 검사
  → 명령라인 + 파일 내용이 이전과 같으면 → 캐시 재사용 (건너뜀, 빠름) ✅
  → 하나라도 다르면 → 캐시 미스 → 그 줄부터 아래 전부 다시 실행 ❌

 

핵심 규칙 — 캐시 미스는 그 줄 아래 전부 무효화

FROM ubuntu              ← 거의 안 바뀜
RUN apt install nginx    ← 거의 안 바뀜      ┐ 앞에 둠
COPY 자주바뀌는소스 .     ← 자주 바뀜         ┘ 뒤에 둠

만약 순서가 반대라면?
COPY 소스 (자주 바뀜) → 캐시 미스 → 아래 apt install도 매번 다시! (느림)

 

→ 자주 바뀌는 걸 앞에 두면, 그 아래 안 바뀌는 것들까지 매번 재실행된다. 그래서 안 바뀌는 무거운 작업(패키지 설치)은 앞, 자주 바뀌는 것(소스 복사)은 뒤에 둔다.

 

최적화 버전

FROM ubuntu
RUN mkdir -m 1777 /share \
    && touch /share/hello.txt \
    && useradd student

USER student
RUN touch /share/student.txt
CMD ls -l /share

 

 

 

1-5.Docke volume 

 

 

기본 형식

VOLUME <container dir>
VOLUME [dir1, dir2, ...]
VOLUME /app                    # 단일 디렉토리
VOLUME [/app, /etc/app]        # 여러 디렉토리

 

핵심 개념

 

컨테이너에 저장하지 않고 호스트에 저장하기 위한 디렉토리를 지정하는 명령이다.

일반 파일: 컨테이너 안에 저장 → 컨테이너 지우면 같이 사라짐
VOLUME:   호스트에 저장 → 컨테이너 지워도 데이터 유지

 

VOLUME(Dockerfile) vs -v(run)

 

구분 VOLUME (Dockerfile) -v (docker run)
위치 이미지 빌드 시 컨테이너 실행 시
하는 일 이 디렉토리는 볼륨이라고 선언 호스트 특정 경로와 실제 연결
호스트 경로 지정 못 함 가능
 
VOLUME /app 만 쓰면 → 호스트의 특정 경로와 연결 못 함
→ docker run -v 로 연결해야 원하는 호스트 폴더에 붙음

docker run -v /share/app:/app test:vol
              호스트경로      컨테이너경로(VOLUME)

-v 옵션 

docker run -v /share:/usr/share/nginx/html -p 86:80 test:html
              ↑                              ↑
           볼륨 마운트                    포트 매핑
-v 호스트경로:컨테이너경로
   ↑            ↑
  /share    /usr/share/nginx/html
 (rocky의    (컨테이너 안 nginx
  폴더)       웹 루트)

 

-v 호스트:컨테이너 호스트 폴더를 컨테이너 안에 연결
-p 호스트:컨테이너 포트 매핑 (86→80)

 

 

 

볼륨이 하는 일 — 폴더 공유

rocky의 /share  ←──연결──→  컨테이너의 /usr/share/nginx/html

호스트에서 /share에 index.html 넣으면
→ 컨테이너 안 nginx가 그걸 바로 읽음
→ curl 하면 그 내용 나옴

 

앞 실습: 컨테이너 안에 들어가서 파일 수정 (exec로 진입)
볼륨 사용: 호스트에서 파일 수정 → 컨테이너에 즉시 반영 (진입 불필요!)

 

→ 컨테이너 안 들어가고도 호스트에서 파일만 바꾸면 웹페이지가 바뀐다.

 

docker run -v /share:/usr/share/nginx/html -p 86:80 -d test:html

curl localhost:86

 

# 컨테이너 안 들어가지 않고, 호스트에서 파일 수정
echo "changed!" > /share/index.html

curl localhost:86    # → "changed!" 바로 반영됨! (재빌드/재시작 불필요)

 

도커 파일 작성
생성 후 볼륨 연결
아이피 확인
curl 결과 뜨는 것 확인

 

 

 

 

Docker 컨테이너에서 Volume 사용하는 4가지 방법

컨테이너 데이터를 저장하는 방법이 상황별로 4가지가 있다. 앞에서 -v /share:...로 쓴 건 이 중 1번 방식이었다.

1. Bind Mount -v /host/path:/container/path 호스트의 내가 지정한 폴더 위치 직접 관리
2. Named Volume -v db1:/var/lib/mysql Docker 관리 영역 (이름으로) 관리 편함
3. Anonymous Volume -v /container/path Docker 관리 영역 (랜덤 id) 임시용
4. tmpfs --mount type=tmpfs,destination=/var/tmp 메모리(RAM) 디스크 저장 안 함

 

 

1)  Bind Mount (호스트 경로 명시)

docker run -v /share:/usr/share/nginx/html nginx
              ↑
      호스트의 실제 경로를 직접 지정
호스트 /share  ←→  컨테이너 폴더

내가 정확히 어느 폴더인지 아는 방식
→ 오늘 실습에서 쓴 방법 (NFS /share 연결한 것)
  • 장점: 호스트에서 파일 직접 보고 수정 가능 (위치 명확)
  • 단점: 호스트 경로에 의존 → 다른 서버로 옮기면 경로 맞춰야 함

2) Named Volume (이름 붙인 볼륨)

docker volume create db1                    # 먼저 볼륨 생성
docker run -v db1:/var/lib/mysql mysql      # 이름으로 연결
              ↑
          볼륨 이름 (경로 아님!)
저장 위치: /var/lib/docker/volumes/db1/
           ↑ Docker가 알아서 관리

내가 경로 신경 안 써도 됨, "db1"이란 이름으로만 다룸
  • 장점: Docker가 관리해서 편함, 이름으로 재사용·공유 쉬움
  • 용도: DB 데이터처럼 영구 보관하되 위치는 신경 안 쓸 때 (권장 방식)

3) Anonymous Volume (-v 생략, 랜덤)

docker run -v /var/lib/mysql mysql
              ↑
      컨테이너 경로만 씀 (호스트 쪽 생략)
저장 위치: /var/lib/docker/volumes/[랜덤id]/
                                    ↑ 이름 대신 랜덤 문자열

예: /var/lib/docker/volumes/a3f8c9.../
  • 특징: Docker가 랜덤 id로 자동 생성 (익명 볼륨)
  • 단점: 이름이 없어 나중에 찾기 어려움 → 임시/테스트용

4) tmpfs (메모리 저장)

docker run --mount type=tmpfs,destination=/var/tmp nginx
                   ↑                    ↑
              메모리 타입          컨테이너 안 경로
저장 위치: RAM(메모리) — 디스크에 안 씀!

컨테이너 종료 → 데이터 완전 소멸 (휘발성)
  • 특징: 디스크가 아니라 메모리에 저장 → 매우 빠름
  • 용도: 캐시, 임시 파일, 민감 데이터(흔적 안 남김)
  • 주의: 컨테이너 꺼지면 데이터 사라짐 (영구 저장 아님)

방법별 선택 기준

호스트에서 파일 직접 만지고 싶다     → 1. Bind Mount
DB 등 영구 데이터, 관리 편하게       → 2. Named Volume (권장)
그냥 임시로 볼륨만 있으면 됨          → 3. Anonymous
빠르고 휘발성(캐시/임시)             → 4. tmpfs

 

 

 

 

 

728x90