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!" 바로 반영됨! (재빌드/재시작 불필요)





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
'{Bootcamp} > KT Cloud Tech up 클라우드 인프라' 카테고리의 다른 글
| [kt cloud] 쿠버네티스 실습 (1) (0) | 2026.09.17 |
|---|---|
| [kt cloud] 도커 실습 (3) (0) | 2026.09.17 |
| [kt cloud] 도커 실습 (1) (0) | 2026.09.15 |
| [kt cloud] 리눅스 관리자 실습 (6) (0) | 2026.09.11 |
| [kt cloud] 리눅스 관리자 실습 (5) (0) | 2026.09.10 |