저번 포스팅에 이어 도커를 더 알아보자.
이번 포스팅은 깃헙을 참고한다.
먼저, 클론 후 파일 구조를 보자.
git clone https://github.com/KennethanCeyer/mlops-quicklab.git
tree -L 2 .
├── README.md
├── airflow
│ ├── README.md
│ └── basic
├── assets
│ └── logo.png
├── dockers
│ ├── README.md
│ ├── basic
│ ├── compose
│ ├── optimization
│ └── swarm
├── mlflow
│ ├── Dockerfile
│ ├── README.md
│ ├── docker-compose.yaml
│ ├── requirements.txt
│ ├── train_and_register.py
│ └── trainer.py
└── vertexai
└── predictions
12 directories, 10 files
이번 포스팅은 dockers 폴더를 사용할 것이다.
cd dockers
tree -L 2 .
.
├── README.md
├── basic
│ ├── Dockerfile
│ ├── README.md
│ ├── app
│ └── requirements.txt
├── compose
│ ├── Dockerfile
│ ├── README.md
│ ├── app
│ ├── docker-compose.yml
│ └── requirements.txt
├── optimization
│ ├── Dockerfile
│ ├── Dockerfile.base
│ ├── README.md
│ ├── app
│ └── requirements.txt
└── swarm
├── Dockerfile
├── README.md
├── app
├── docker-compose.yml
├── locustfile.py
└── requirements.txt
9 directories, 17 files
Docker 빌드
docker build란?
이미지를 만드는 과정이다.
이 이미지를 만들기 위해서는 "어떤 방식으로 이미지를 만들 것인지"를 알아야 하는데, 이 정보들은 보통 Dockerfile에 있다.
cd basic
cat Dockerfile
FROM python:3.11
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
COPY ./app /code/app
RUN apt-get update
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
1. FROM python:3.11 - 기반 이미지 설정
이건 Docker Hub에 있는 python의 3.11 버전이라는 뜻이다. https://hub.docker.com/_/python/tags?name=3.11
python Tags | Docker Hub
python •• Python is an interpreted, interactive, object-oriented, open-source programming language.
hub.docker.com
(dockerhub -> python 검색 -> Tag -> 3.11 검색)
FROM은 빌드할 이미지의 시작점을 지정하는 것이고,
여기선 python 3.11이 설치된 공식 Docker 이미지를 사용한다는 뜻이다.
2. WORKDIR /code - 작업 디렉터리 설정
이후의 명령어들(COPY, RUN, CMD)이 실행될 컨테이너 내부의 기본 디렉터리를 /code로 지정하고,
컨테이너 내부에서 작업할 위치를 통일한다.
3. COPY
COPY ./requirements.txt /code/requirements.txt
COPY ./app /code/app
Dockerfile에 있는 로컬 디렉터리의 파일을 빌드 중인 이미지 내부의 경로로 복사한다.
(왼쪽은 호스트, 오른쪽은 컨테이너)
4. RUN - 이미지 만들 때 동작
apt-get update - 시스템 업데이트
- 컨테이너 내부에서 리눅스 패키지 목록을 최신 상태로 업데이트한다.
pip install --no-cache-dir --upgrade -r /code/requirements.txt
- txt파일 안에 있는 패키지들을 설치한다.
5. CMD - 이미지를 실행(컨테이너화)할 때 최초로 실행됨
이 이미지를 기반으로 컨테이너가 시작될 때 실행될 기본 명령어를 정의한다.
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
- app 폴더 안에 main.py 파일이 들어있고, 이걸 열어보면 root 라고 하는 함수가 존재.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
(선택)
conda로 가상환경 만들기 (로컬에서 여러 플젝 할 때 충돌 안 나게)
conda create -n quicklab-modu python=3.11
conda activate quicklab-modu
이제, 필요한 패키지들을 모두 설치하자.
pip3 install -r requirements.txt
설치된 uvicon 실행
uvicorn app.main:app --host 0.0.0.0 --port 8000
INFO: Started server process [11716]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
마지막 줄의 링크에 접속하면 아래와 같은 웹페이지가 뜬다.

이것도 이전 포스팅에서 언급했었던 것과 마찬가지로, 실시간으로 로그를 볼 수 있다.

커멘드라인도 없으므로, 제어권은 유비콘에게 있고, 계속 실행중이라는 뜻 (종료하려면 CTRL+C)
여기서 WSL을 사용중이라면 host가 0.0.0.0이 아님
hostname -I
이 코드 결과를 0.0.0.0대신 작성
예시:
uvicorn app.main:app --host 172.26.58.106 --port 8001
이제, 이미지 빌드를 해보자.
docker build . -t fastapi-app:latest
[+] Building 67.2s (11/11) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 295B 0.0s
=> [internal] load metadata for docker.io/library/python:3.11 2.3s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 52B 0.0s
=> [1/6] FROM docker.io/library/python:3.11@sha256:38639aa0267125ab0def 50.8s
=> => resolve docker.io/library/python:3.11@sha256:38639aa0267125ab0def2 0.0s
=> => sha256:258d02e80207372ffa988e0886f193f22d069828517 6.35kB / 6.35kB 0.0s
=> => sha256:2981f7e8980b9f4b6605026e1c5f99b4971ebba1 49.29MB / 49.29MB 18.2s
=> => sha256:b22766554d6bfa95c7325b00ee002f2705a7b860 25.61MB / 25.61MB 11.0s
=> => sha256:58f2d358b447d091790c5ef0943550bbcf57bac4 67.78MB / 67.78MB 17.2s
=> => sha256:38639aa0267125ab0def2778f0aa8453043178ec6 10.32kB / 10.32kB 0.0s
=> => sha256:65a37c5d066bac59dccfa5f3fac4b99a2e8eea11987 2.32kB / 2.32kB 0.0s
=> => sha256:dd420cee8193b72cf70974a80e88896c8e58d9 235.97MB / 235.97MB 46.5s
=> => sha256:5c5320e2b85aa6a73d5a99c3b0ed13ed25e19735ef 6.08MB / 6.08MB 19.4s
=> => extracting sha256:2981f7e8980b9f4b6605026e1c5f99b4971ebba15f626e46 0.8s
=> => sha256:385f8897758201c7b09aa909cc7dadd97c0c16c3 23.95MB / 23.95MB 23.9s
=> => extracting sha256:b22766554d6bfa95c7325b00ee002f2705a7b8605908c3eb 0.3s
=> => sha256:8493300c7ffc400508d0f0f98eb8e5e8c6da8c7a073c82 250B / 250B 19.9s
=> => extracting sha256:58f2d358b447d091790c5ef0943550bbcf57bac46c4b8bfc 1.2s
=> => extracting sha256:dd420cee8193b72cf70974a80e88896c8e58d925edd1cdc5 3.6s
=> => extracting sha256:5c5320e2b85aa6a73d5a99c3b0ed13ed25e19735efa6b8fd 0.1s
=> => extracting sha256:385f8897758201c7b09aa909cc7dadd97c0c16c3e2e286a5 0.4s
=> => extracting sha256:8493300c7ffc400508d0f0f98eb8e5e8c6da8c7a073c827a 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 856B 0.0s
=> [2/6] WORKDIR /code 0.6s
=> [3/6] COPY ./requirements.txt /code/requirements.txt 0.0s
=> [4/6] COPY ./app /code/app 0.0s
=> [5/6] RUN apt-get update 2.8s
=> [6/6] RUN pip install --no-cache-dir --upgrade -r /code/requirements 10.3s
=> exporting to image 0.4s
=> => exporting layers 0.4s
=> => writing image sha256:96b0090a8b537601ae0837bbf6d4b5eee3508b0e01af3 0.0s
=> => naming to docker.io/library/fastapi-app:latest 0.0s
dockerfile이 있는 폴더 위치를 지정하는데, 지금은 현재 위치에 있으므로 . 사용.
-t는 tag 옵션: 특정 이미지의 이름을 지정해준다.
fastapi-app는 이미지 이름
latest는 태그 옵션 (보통 여기는 버전을 씀. ex: v.1) 이것도 마찬가지로 디폴트로 latest.
이제 이미지가 잘 만들어졌나 확인해보면,
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
fastapi-app latest 96b0090a8b53 3 minutes ago 1.19GB
잘 됐다.
이제 이 이미지로 docker run (컨테이너화) 시켜보면,
docker run --name fastapi-app -p 8000:80 fastapi-app
INFO: Started server process [1]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)

잘 되는데, 로컬에서는 http://0.0.0.0:80 이게 아니라 http://127.0.0.1:8000 로 접근해야 정상적으로 된다.
하지만, run 할 때 -dit 옵션을 안 줬으므로 제어권이 없다. -> 종료해야 터미널에서 다른 명령어 실행 가능.
ctrl+c 누르면 아래와 같이 종료된다.
^CINFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [1]
그리고 docker ps -a 해보면?
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e3b7b49e081f fastapi-app "uvicorn app.main:ap…" 10 minutes ago Exited (0) 2 minutes ago fastapi-app
Exited (0) 2 minutes ago -> 프로그램이 종료됐기 때문에 컨테이너도 종료됐다고 나온다.
다시 띄울 수도 있음
docker start fastapi-app
이제 모든 컨테이너를 삭제하자
docker rm -f $(docker ps -qa)
$로 괄호 안의 결과값들을 묶어서 argument로 줄 수 있다.
같은 방법으로 이미지들도 삭제
docker rmi $(docker images -a)
모든 컨테이너, 네트워크, 이미지, 캐시를 제거하는 정석 방법은 아래와 같다.
docker system prune --all
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all images without at least one container associated to them
- all build cache
Are you sure you want to continue? [y/N] y
Deleted build cache objects:
ga5q4klj56sxfprje00uw8sik
vcqj7tioq5x3r8y2i2imwh0o2
u7ktw313rg6vq7xoymnauz5og
w1ahhfzf0hk1whcmk9xrufn9b
zpfvkcijqgiqqhqfdmrzlysf1
n4e1ly5ga2qgpajgzcod8emax
si11qt7tpgropjzghfupll0e0
m972jpz97yxqfm0hedmn5s2pv
83uyl4ylfdpwfvq8fen1dgzl4
o85xnzziahg64q0jvjjlpss1g
jecyah4dybpon3rech6h839ar
fjzge2x6u0qt0y0mwrj82c9wr
2qcr2mng8959v16s9l5njmowj
suhd5w28ce293zie957p8rehd
bp6im011frx0kvphn8kjf38al
Total reclaimed space: 85.88MB
Docker 빌드 과정

이미지들을 레지스트리에 저장하는데, 대표적인 레지스트리는 Docker Hub이다.
또는 구글 클라우드 플랫폼의 레지스트리도 있음.
갑자기 생긴 궁금증인데,
docker의 이미지는 이미지 파일도 아니면서 왜 이미지라고 부르나? 를 찾아봤는데,
"스냅샷" 개념으로 이해하면 될 것 같다.(그 당시의 환경을 그대로 담고 있다.)
Docker에서 자주 사용하는 명령어


회고
기록하면서 공부하니 머리에 잘 들어오긴 하는데 시간이 꽤 오래 걸린다.
'AI > MLOps' 카테고리의 다른 글
| [12/15] 아이펠 리서치 15기 TIL | Model Serving (TorchServe) (0) | 2025.12.15 |
|---|---|
| [12/12] 아이펠 리서치 15기 TIL | Kubernetes(K8s): minikube (1) | 2025.12.12 |
| [12/12] 아이펠 리서치 15기 TIL | Docker Swarm (1) | 2025.12.12 |
| [12/11] 아이펠 리서치 15기 TIL | Docker 이미지 구조 (0) | 2025.12.11 |
| [12/10] 아이펠 리서치 15기 TIL | Docker: 컨테이너 환경 구성 및 이미지 활용 (0) | 2025.12.11 |