Docker 익숙해지기

WONY_yoon·2025년 12월 13일

Docker 기본 학습

1️⃣ Docker 개념 맛보기

옛날 방식 (VM)

  • OS 통째로 올려야 해서 무겁고 느림
  • 각 실습 환경 만들려면 VM을 하나씩 설치

Docker 방식 (컨테이너)

  • 하나의 OS 위에서 격리된 작은 박스들을 여러 개 돌리는 느낌
  • 각 박스 = 컨테이너
  • 컨테이너를 찍어내는 설계도 = 이미지
  1. 비유하여 확인
    → 이미지(Image) = 라면 레시피 + 라면 봉지
    → 컨테이너(Container) = 실제로 끓인 라면 그릇
    → 같은 이미지를 가지고 여러 그릇(컨테이너)을 막 찍어낼 수 있음
    → 마음에 안 들면 컨테이너는 그냥 버리고, 이미지만 남겨두면 됨

  2. 기억하고 갈 것
    → 이미지(Image): 컨테이너를 만들기 위한 템플릿
    → 컨테이너(Container): 실제로 실행 중인 하나의 “프로그램 + 환경”
    → 레지스트리(Registry): 이미지를 저장해둔 창고 (예: Docker Hub)
    → Dockerfile: 이미지를 만드는 레시피(나중에 할 거)

2️⃣ Doker 설치하기 (MAC OS 기준)

Docker Desktop 설치

  1. 자신 OS에 맞는 것을 선택하여 설치

    https://docs.docker.com/desktop/setup/install/mac-install/

  2. 다운로드 폴더에서 더블클릭하여 설치

설치 후 실행

  1. command + spacebar 로 Docker 실행
  2. 상단에 고래 표시 나오면 성공!
  3. 터미널에서 버전 확인
    docker --version

실행 후 설정할 것들

  1. 처음 실행 시 권한요청 뜨면 허용
  2. 패스워드 입력 뜨면 비밀번호 입력
  3. 로그인 등 진행 해주면 됨

3️⃣ 컨테이너 실행

hell0-world 컨테이너 실행

  1. 터미널에서 명령어 실행
    docker run hello-world
  1. 이 명령이 하는 일
  • 내 컴퓨터에 hello-world 이미지 있는지 확인
  • 없으면 Docker Hub에서 자동으로 다운(pull)
  • 그 이미지로 컨테이너를 하나 만들고 잠깐 실행
  • "Hello from Docker!" 같은 메시지 출력 후 종료

두 번째 컨테이너: nginx 웹 서버

  1. 터미널에서 명령어 실행
    docker run --name my-nginx -d -p 8080:80 nginx
    → allow 클릭
    → download 완료되면 목록에 하나더 뜸
  1. 명령어 옵션 해석
  • --name my-nginx : 컨테이너 이름을 my-nginx로 지정
  • -d : 백그라운드로 실행 (터미널을 점유하지 않도록)
  • -p 8080:80 : 내 Mac의 8080 포트 → 컨테이너의 80 포트 연결
  • nginx : Nginx 웹 서버 이미지 사용
  1. 브라우저에서 주소창에
    http://localhost:8080

4️⃣ 컨테이너의 휘발성

실행 중인 컨테이너 확인

docker ps

컨테이너 중지/재시작

  1. 컨테이너 중지
    docker stop my-nginx
  2. 컨테이너 다시 시작
    docker start my-nginx

실행 중이 아닌 컨테이너까지 모두 보기

docker ps -a

컨테이너 로그 확인

docker logs my-nginx

컨테이너 내부로 직접 들어가기

"이제 컨테이너 = 작은 리눅스"
docker exec -it my-nginx bash
이 상태로 확인해보기
ls /
cd /usr/share/nginx/html
ls
cat index.html
exit

index.html 의도적으로 수정해보기

이제 브라우저 새로고침
HELLO FROM CONTAINER ⭕

컨테이너 재시작

HELLO FROM CONTAINER ⭕

휘발성 확인

  1. 컨테이너 삭제하기
    docker stop my-nginx
    docker rm my-nginx
    2. 같은 이미지로 새 컨테이너 생성
    3. 브라우저 재접속
  2. 결과 해석
    → Welcome to nginx! (원래 화면) 이 보임
    → 내가 수정한 index.html이 컨테이너 삭제와 함께 완전히 사라짐
    → nginx 이미지 자체는 변하지 않음
    → 💢 컨테이너 = 이미지로부터 만들어진 일회성 실행 결과물 💢

“컨테이너 안에서 직접 만든 변경사항은 컨테이너가 죽으면 같이 죽는다.”

5️⃣ 앱 → 컨테이너로 만들기

작업 디렉토리 생성

원하는 디렉토리로 이동한 후 해당 명령어 실행
mkdir docker-python-app
cd docker-python-app

Python 웹 서버 작성

  1. app.py 파일 생성
    nano app.py

  2. 내용 붙여넣기

from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(b"<h1>Hello from Docker Image</h1>")

HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()

Dockerfile 작성

  1. 동일한 디렉토리에 Dockerfile 생성
    nano Dockerfile

  2. 내용 붙여넣기
    FROM python:3.11-slim
    WORKDIR /app
    COPY app.py .
    EXPOSE 8000
    CMD ["python", "app.py"]

  3. 중요 개념
    → FROM : 실행 환경
    → COPY : 코드를 이미지 안으로 넣음
    → CMD : 컨테이너 시작 시 실행할 명령

이미지 빌드

  1. 이미지 빌드하기
    docker build -t my-python-app .

  2. 명령어 의미
    → . (온점) : 현재 디렉토리 기준
    → my-phthon-app : 내가 만든 이미지 이름

컨테이너 실행

  1. 터미널에서 컨테이너 명령어 실행
    docker run -d -p 9000:8000 --name python-app my-python-app
  2. 브라우저 접속
    http://localhost:9000

휘발성 여부 확인

  1. 컨테이너 삭제
    docker stop python-app
    docker rm python-app
  2. 컨테이너 다시 살리기docker run -d -p 9000:8000 --name python-app my-python-app
  3. 브라우저 접속
    → 동일한 것 확인 가능

“컨테이너는 날아가도, 이미지는 남는다.코드는 이미지에 들어 있어야 한다."

6️⃣ 데이터 유지 (Volume)

현재 컨테이너 정리

  1. 컨테이너 중지
    docker stop python-app 2>/dev/null || true
  2. 컨테이너 삭제
    docker rm python-app 2>/dev/null || true
  3. 명령어 의미
docker stop python-app   2>/dev/null   || true
└─────①─────┘           └──②──┘       └─③─┘
① python-app 컨테이너를 중지
  문제 : 컨테이너가 이미 없으면 에러 발생 + exit code ≠ 0

② 에러 메시지 숨기기 - 에러 출력(stderr)을 쓰레기통(/dev/null)으로 버려라
  기본 개념
   0 : 표준 입력 (stdin)
   1 : 표준 출력 (stdout)
   2 : 표준 에러 (stderr)

③ 실패해도 성공한 척하기 - docker stop 실패해도 true로 최종 결과를 성공으로 만들자
  쉘에서 : A || B
   A가 실패(exit code ≠ 0) 하면
   B를 실행
   
결론적으로 
python-app 컨테이너를 중지하되, 해당 컨테이너가 없어서 에러가 나도
에러 메시지는 표시하지말고 명령 전체는 실패로 취급하지 마라

볼륨 없이 데이터 생성

  1. 컨테이너 실행 (쉘로 들어가서 테스트)
    docker run -it --name vol-test-no python:3.11-slim bash
  2. 컨테이너 안에서 파일 생성
    mkdir -p /data
    date > /data/created.txt
    cat /data/created.txt
    exit
  3. 컨테이너 삭제
    docker rm vol-test-no
  4. 같은 컨테이너 새로 만들고 확인
    docker run -it --name vol-test-no python:3.11-slim bash

    cat /data/created.txt
  5. 발생한 결과
    → No such file or directory
    → 컨테이너 삭제로 데이터 소멸

볼륨 사용하여 데이터 생성

  1. 볼륨 생성
    docker volume create mydata
    docker volume ls | grep mydata
  2. 볼륨 마운트해서 컨테이너 실행
    docker run -it --name vol-test-yes -v mydata:/data python:3.11-slim bash

    → 컨테이너 안에서 파일 생성
    date > /data/created.txt
    cat /data/created.txt
    exit

    → 컨테이너 삭제
    docker rm vol-test-yes

    → 새 컨테이너 만들고 같은 볼륨 다시 마운트
    docker run -it --name vol-test-yes -v mydata:/data python:3.11-slim bash
    cat /data/created.txt
    exit
  3. 발생한 결과
    → 이전에 만든 날짜/시간이 그대로 출력됨
    → 컨테이너가 바뀌어도 데이터는 볼륨에 남아있음

7️⃣ 네트워크

사용자 정의 네트워크 생성

docker network create app-net
docker network ls | grep app-net

서버 컨테이너 하나 띄우기

docker run -d --name net-nginx --network app-net nginx

클라이언트 컨테이너에서 net-nginx로 접근

docker run -it --rm --network app-net curlimages/curl:8.10.1 curl -s http://net-nginx | head

  • 발생한 결과
    → nginx HTML 일부가 출력됨
    → 컨테이너 이름(net-nginx)이 DNS처럼 동작
  • 같은 네트워크 내에서는 localhost가 아닌 컨테이너 이름으로 통신

포트 바인딩 (외부공개일 때 활용)

  • -p 8080:80 같은 건 내 PC(호스트)에서 컨테이너로 들어갈 때
  • 컨테이너끼리는 같은 네트워크면 -p 없어도 통신 가능

8️⃣ Docker Compose : Python + Redis

Python 앱을 Redis를 사용하는 버전으로 만들기

  1. requirments.txt 생성
cat > requirements.txt << 'EOF'
redis==5.0.8`
EOF
  1. app.py 재작성
cat > app.py << 'EOF'
from http.server import BaseHTTPRequestHandler, HTTPServer
import os
import redis

REDIS_HOST = os.getenv("REDIS_HOST", "redis")
REDIS_PORT = int(os.getenv("REDIS_PORT", "6379"))

r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True)

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        # 카운터 증가
        count = r.incr("hits")

        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=utf-8")
        self.end_headers()
        body = f"""
        <h1>Docker Compose: Web + Redis</h1>
        <p>Redis Host: {REDIS_HOST}:{REDIS_PORT}</p>
        <p>Hits (stored in Redis): <b>{count}</b></p>
        """
        self.wfile.write(body.encode("utf-8"))

HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
EOF

Dockerfile을 의존성 설치가 포함된 버전으로 업데이트

cat > Dockerfile << 'EOF'
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

EXPOSE 8000
CMD ["python", "app.py"]
EOF

docker-compose.yml 작성

  • yml 설명
    → services: 아래에 컨테이너들을 “논리 묶음”으로 선언
    → web은 build : 로컬 Dockerfile로 이미지 빌드
    → redis는 image : 공식 이미지 그대로 사용
    → REDIS_HOST : redis → 서비스 이름이 곧 DNS 이름

volumes: → redis 데이터가 컨테이너 삭제 후에도 유지

cat > docker-compose.yml << 'EOF'
services:
  web:
    build: .
    container_name: compose-web
    ports:
      - "9000:8000"
    environment:
      REDIS_HOST: redis
      REDIS_PORT: "6379"
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    container_name: compose-redis
    volumes:
      - redis-data:/data

volumes:
  redis-data:
EOF

Compose 실행

  1. 올리기
    docker compose up -d --build

  2. 상태 확인
    docker compose ps

동작 확인

  1. 새로고침 할 때마다 'Hits'가 1씩 증가해야 함
  2. 의미
    → 웹 컨테이너가 Redis 컨테이너에 연결
    → 카운터가 Redis에 저장
    → 즉 “서비스 분리 + 네트워크 연결 + 데이터 유지”가 한 번에 달성

컨테이너만 내리기 (데이터는 유지)

  1. 컨테이너 내리기
    docker compose down
  2. 컨테이너 다시 올리기
    docker compose up -d
    → hits 값이 이어짐
  3. hits 값 이어지지 않게 하기
    docker compose down -v
    docker compose up -d

9️⃣ 개발용 Compose

docker-compose.yml에 bind mount 추가

    volumes:
      - ./:/app
  • 의미
    → 로컬 ~/docker-python-app 폴더 내용이 컨테이너의 /app에 “실시간으로” 보임
    → 컨테이너 안의 코드도 자동으로 바뀐 상태가 됨

Compose 재시작

docker compose down
docker compose up -d --build
→ 동일한 내용 표출되는 것 확인 가능

자동 반영 테스트

  1. app.py 수정해보기
    <h1>DEV MODE: Live Reload via Bind Mount</h1>
  2. http://localhost:9000 새로고침
  3. 반영되지 않는다면 docker compose restart web실행 후 새로고침

0개의 댓글