본문 바로가기

(11.25) DevOps - Docker 이미지와 레이어, Dockerfile, Docker 빌드관리, Compose

@starweb2025. 11. 26. 18:56

[ 15주차 - 1125 ]

    금일 커리큘럼
        ├ 09:00 ~ 12:00 Devops (Docker 이미지와 레이어, Dockerfile)
        └ 13:00 ~ 18:00 Devops (Docker 이미지 빌드/관리, Docker 레지스트리, Docker Compose)

1. Docker 이미지와 레이어

1.1 Docker 이미지 레이어

  • Docker 이미지는 여러 레이어(layer)로 구성된 파일 시스템
  • 각 레이어는 변경 사항을 포함하며, 불변(immutable) 특성을 가짐
  • 레이어는 캐시되어, 동일한 레이어를 사용하는 이미지 간에 저장 공간을 절약
  • 이미지 빌드 시, 변경된 레이어만 새로 생성되어 빌드 속도가 향상
  • 레이어 = 읽기 전용 파일 시스템 스냅샷

1.2 Docker 레이어 구조

예시 Dockerfile

# dockerfile 예시
FROM ubuntu:20.04          # 베이스 이미지 (layer 1)
RUN apt-get update         # apt 패키지 목록 업데이트 (layer 2)
RUN apt-get install -y python3  # python3 설치 (layer 3)

COPY . /app                # 애플리케이션 코드 복사 (layer 4)
CMD ["python3", "/app/app.py"]  # 실행 명령어 (layer 5)

레이어 구조 설명

  • Layer 1 : ubuntu:20.04 베이스 이미지
  • Layer 2 : apt-get update 명령어 실행 결과
  • Layer 3 : apt-get install -y python3 명령어 실행 결과
  • Layer 4 : 애플리케이션 코드 복사 결과
  • Layer 5 : 컨테이너 실행 시 사용할 명령어 설정

1.3 Docker 이미지 관리 명령어

이미지 빌드

# docker build -t <이미지_이름>:<태그> <Dockerfile_경로>

# 1. 해당 경로에서
docker build -t my_python_app:latest .


# 1. 특정 경로에서
docker build -t my_python_app:latest -f /path/to/Dockerfile .

이미지 목록 조회

docker images

# 예시 출력
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
my_python_app      latest    abcdef123456   10 minutes ago   500MB
ubuntu             20.04     123456abcdef   2 weeks ago      72MB
nginx              latest    fedcba654321   3 weeks ago      133MB
# ... (생략)

특정 이미지 레이어 확인

# docker history <이미지_이름>:<태그>
docker history my_python_app:latest

# 예시 출력
IMAGE          CREATED         CREATED BY                                      SIZE
abcdef123456   10 minutes ago  /bin/sh -c CMD ["python3", "/app/app.py"]       0B
123456abcdef   15 minutes ago  /bin/sh -c COPY . /app                          5MB
fedcba654321   20 minutes ago  /bin/sh -c apt-get install -y python3           100MB
654321fedcba   25 minutes ago  /bin/sh -c apt-get update                       50MB

이미지 삭제

# docker rmi <이미지_이름>:<태그>
docker rmi my_python_app:latest

1.4 이미지 변경 추적과 캐싱

Docker 빌드 프로세스

  • Docker는 Dockerfile의 각 명령어를 실행할 때마다 새로운 레이어를 생성
  • 이전에 빌드된 레이어가 캐시에 존재하면, 해당 레이어를 재사용하여 빌드 속도를 향상시킴
  • 예를 들어, apt-get update 명령어가 이미 캐시에 있다면, Docker는 이를 다시 실행하지 않고 캐시된 레이어를 사용
  • 이는 개발 및 테스트 과정에서 매우 유용하며, 반복적인 빌드 시간을 크게 단축시킴

캐시 무효화

  • Dockerfile의 내용이 변경되면, 해당 변경 이후의 모든 레이어는 다시 빌드됨
  • 예를 들어, COPY . /app 명령어 이후에 소스 코드가 변경되면, 이 명령어와 그 이후의 모든 명령어는 다시 실행됨
  • 이를 통해 개발자는 효율적으로 이미지를 관리하고, 빌드 시간을 최적화할 수 있음

Docker 캐시 무효화 예제

  • ENV 변경으로 캐시 무효화 케이스 (dockerfile)
# 첫번째 빌드용 Dockerfile
FROM node:18                  # 레이어 1
ENV APP_MODE=dev              # 레이어 2
RUN echo "Install steps..."   # 레이어 3
COPY . /app                   # 레이어 4

# 두번째 빌드용 Dockerfile (ENV 변경)
FROM node:18                  # 레이어 1
ENV APP_MODE=prod             # 레이어 2 (캐시 무효화 발생 부분)
RUN echo "Install steps..."   # 레이어 3
COPY . /app                   # 레이어 4
  • 캐시 사용/무효화 빌드 예제 (명령어)
# 첫번째 빌드 (모든 레이어 새로 빌드)
docker build -t my_node_app:dev -f Dockerfile.dev .
# [1/4] FROM node:18                # new
# [2/4] ENV APP_MODE=dev            # new
# [3/4] RUN echo "Install steps..." # new
# [4/4] COPY . /app                 # new

# 두번째 빌드 (ENV 변경으로 인해 레이어 2부터 다시 빌드)
docker build -t my_node_app:prod -f Dockerfile.prod .
# [1/4] FROM node:18                # used cache
# [2/4] ENV APP_MODE=prod           # 캐시 무효화, 다시 빌드
# [3/4] RUN echo "Install steps..." # new
# [4/4] COPY . /app                 # new

2. Dockerfile 작성

  • Dockerfile은 Docker 이미지를 자동으로 빌드하기 위한 설정 파일
  • docker build 명령어를 통해 Dockerfile을 읽고 이미지를 생성
  • 이미지 빌드 과정을 명시적으로 정의하여, 일관된 환경을 제공

2.1 Dockerfile 기본 구조

  • FROM : 베이스 이미지 지정
# FROM <이미지_이름>:<태그>
FROM ubuntu:20.04
# 베이스 이미지로 우분투 20.04 사용
  • RUN : 이미지 빌드 시 실행할 명령어
# RUN <명령어>
RUN apt-get update && apt-get install -y python3
# apt 패키지 목록 업데이트 및 python3 설치
  • COPY, ADD : 파일/디렉토리 복사
# COPY <호스트_경로> <컨테이너_경로>
COPY . /app
# 호스트의 현재 디렉토리 내용을 컨테이너의 /app 디렉토리에 복사

# ADD <호스트_경로> <컨테이너_경로>
ADD config.tar.gz /config
# 호스트의 config.tar.gz 파일을 컨테이너의 /config 디렉토리에 압축 해제하며 복사
  • WORKDIR : 작업 디렉토리 설정
# WORKDIR <디렉토리_경로>
WORKDIR /app
# 이후 명령어들은 /app 디렉토리에서 실행됨
  • CMD : 컨테이너 실행 시 기본 명령어 지정
# CMD ["명령어", "인자1", "인자2", ...]
CMD ["python3", "app.py"]
# 컨테이너가 시작될 때 python3 app.py 명령어 실행
  • ENV : 환경 변수 설정
# ENV <변수_이름> <값>
ENV APP_ENV=prod
# APP_ENV 환경 변수를 prod로 설정

ENV APP_HOME=/app
ENV LANG=Kor.UTF-8 TZ=Asia/Seoul

WORKDIR $APP_HOME
  • EXPOSE : 컨테이너가 수신할 포트 지정
# EXPOSE <포트_번호>
EXPOSE 8080
# 컨테이너가 8080 포트를 수신하도록 지정

EXPOSE 8080 8888
EXPOSE 80/tcp
EXPOSE 443/tcp

2.2 Dockerfile 예시

Python Flask 애플리케이션

Python Flask - dockerfile
FROM python:3.9-slim

# 작업 디렉토리 설정
WORKDIR /app

# 의존성 파일 먼저 복사 (캐싱 활용)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 소스 코드 복사
COPY . /app

# 환경 변수 설정
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0

# 포트 명시
EXPOSE 5000

# 실행 명령
CMD ["flask", "run"]

Spring Boot 애플리케이션

Spring Boot - dockerfile (멀티스테이지)
# 빌드 스테이지
FROM gradle:7.6-jdk17 AS build
WORKDIR /app
COPY build.gradle settings.gradle ./
COPY src ./src
RUN gradle build --no-daemon

# 런타임 스테이지
FROM openjdk:17-jdk-alpine
WORKDIR /app

# 빌드 결과만 복사
COPY --from=build /app/build/libs/*.jar app.jar

# 환경 변수
ENV SPRING_PROFILES_ACTIVE=prod
ENV SERVER_PORT=8080

# 포트
EXPOSE 8080

# 실행
ENTRYPOINT ["java", "-jar", "app.jar"]

React.js 애플리케이션

React.js - dockerfile (멀티스테이지)
# 빌드 스테이지
FROM node:18 AS build
WORKDIR /app

# package.json 먼저 복사 (캐싱)
COPY package*.json ./
RUN npm ci --only=production

# 소스 코드 복사 및 빌드
COPY . .
RUN npm run build

# 프로덕션 스테이지
FROM nginx:alpine

# 빌드 결과를 nginx로 복사
COPY --from=build /app/build /usr/share/nginx/html

# nginx 설정 복사 (선택사항)
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

베스트 프랙티스 - 레이어 최소화

dockerfile - 레이어 최소화
# ❌ 나쁜 예 - 레이어 3개
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get clean

# ✅ 좋은 예 - 레이어 1개
RUN apt-get update && \
    apt-get install -y python3 && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

베스트 프랙티스 - 빌드 순서 최적화

dockerfile - 빌드 순서 최적화
# ✅ 좋은 예 - 변경 빈도가 낮은 것부터
FROM python:3.9-slim

# 1. 의존성 파일 (변경 빈도 낮음)
COPY requirements.txt .
RUN pip install -r requirements.txt

# 1. 소스 코드 (변경 빈도 높음)
COPY . /app

# ❌ 나쁜 예 - 소스 코드를 먼저 복사
# COPY . /app  ← 소스 변경 시 아래 pip install도 재실행
# RUN pip install -r requirements.txt

베스트 프랙티스 - 멀티 스테이지 빌드

dockerfile - 멀티 스테이지 빌드
# 빌드 도구와 런타임 분리 → 이미지 크기 최소화
FROM maven:3.8-openjdk-17 AS build
WORKDIR /app
COPY . .
RUN mvn clean package

# 런타임 이미지 (빌드 도구 불포함)
FROM openjdk:17-jdk-alpine
COPY --from=build /app/target/*.jar app.jar
CMD ["java", "-jar", "app.jar"]

.dockerignore 활용

.dockerignore
# .dockerignore 파일
.git
.gitignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.env
.vscode
__pycache__
*.pyc
target/
build/

3. 이미지 빌드/관리

3.1 이미지 빌드

# docker build -t <이미지명>:<태그> <빌드컨텍스트경로>

# 기본 빌드 (현재 디렉토리)
docker build -t myapp:1.0 .

# 다른 Dockerfile 지정
docker build -f Dockerfile.prod -t myapp:prod .

# 빌드 인자 전달
docker build --build-arg ENV=production -t myapp:prod .

# 캐시 사용 안 함 (완전 재빌드)
docker build --no-cache -t myapp:latest .

# 특정 플랫폼용 빌드
docker build --platform linux/amd64 -t myapp:amd64 .

# 빌드 과정 상세 출력
docker build --progress=plain -t myapp:latest .

3.2 이미지 태그

# 버전 태그 추가
docker tag myapp:latest myapp:v1.0
docker tag myapp:latest myapp:stable

# Docker Hub 업로드용 태그
docker tag myapp:latest username/myapp:latest

# 사설 레지스트리용 태그
docker tag myapp:latest registry.company.com/myapp:v1.0

3.3 이미지 목록/삭제

# 이미지 목록 조회
docker images

# 이미지 삭제
docker rmi myapp:latest

# 여러 이미지 삭제
docker rmi ubuntu:20.04 python:3.9 nginx:alpine

# 이미지 ID로 삭제
docker rmi 1234abcd

# 강제 삭제 (-f)
docker rmi -f myapp:latest

4. docker 레지스트리

  • Docker 레지스트리는 Docker 이미지를 저장하고 배포하는 중앙 저장소
  • 퍼블릭 레지스트리(예: Docker Hub)와 프라이빗 레지스트리(사내 전용)로 구분
  • 이미지를 푸시(push)하고 풀(pull)하여 컨테이너 배포에 활용

4.1 레지스트리 종류

  • Docker Hub : 가장 널리 사용되는 퍼블릭 레지스트리
  • Amazon ECR : AWS에서 제공하는 프라이빗 레지스트리
  • Google Container Registry : GCP에서 제공하는 레지스트리
  • Azure Container Registry : Azure에서 제공하는 레지스트리

4.2 docker hub 사용법

로그인

# Docker Hub에 로그인
docker login # 사용자명과 비밀번호 입력

# 특정 레지스트리에 로그인
docker login myregistry.com

# 로그아웃
docker logout

이미지 푸시/풀

# 이미지 푸시
# docker push <레지스트리_이름>/<이미지_이름>:<태그>
docker push <도커허브계정>/myapp:latest


# 이미지 풀
# docker pull <레지스트리_이름>/<이미지_이름>:<태그>
docker pull <도커허브계정>/myapp:latest

4.3 AWS ECR 사용법

ECR 로그인

# AWS CLI로 ECR 로그인
aws ecr get-login-password --region <region> | \
    docker login --username AWS \
    --password-stdin <aws_account_id>.dkr.ecr.<region>.amazonaws.com

# --username AWS : 고정값
# --password-stdin : 보안상 권장되는 로그인 방식 
# <aws_account_id> : AWS 계정 ID
# <region> : ECR 리전 (예: us-west-2)


# 로그아웃
docker logout <aws_account_id>.dkr.ecr.<region>.amazonaws.com

이미지 푸시/풀

# 이미지 태그 (ECR용)
docker tag myapp:latest <aws_account_id>.dkr.ecr.<region>.amazonaws.com/myapp:latest
# /<이미지>:<태그> 형식으로 태그 지정


# 이미지 푸시
docker push <aws_account_id>.dkr.ecr.<region>.amazonaws.com/myapp:latest

# 이미지 풀
docker pull <aws_account_id>.dkr.ecr.<region>.amazonaws.com/myapp:latest

5. Docker Compose 기본

  • 여러 개의 Docker 컨테이너를 하나의 설정 파일로 관리하는 도구
  • docker-compose 가 필요한 이유는 복잡한 멀티 컨테이너 애플리케이션을 쉽게 정의하고 실행하기 위함
  • docker-compose.yml 파일에 서비스, 네트워크, 볼륨 등을 정의하여 일괄적으로 관리 가능

5.1 docker-compose 구조

  • services : 실행할 컨테이너들 정의
  • networks : 네트워크 설정 (선택 사항)
  • volumes : 데이터 볼륨 설정 (선택 사항)

docker-compose.yml 기본 구조

services: # 실행할 컨테이너들
  service1:
    # 서비스 설정
  service2:
    # 서비스 설정

networks: # 네트워크 설정 (선택)
  network1:

volumes: # 볼륨 설정 (선택)
  volume1:

compose - nginx 웹서버 예시

services:
  web:  # nginx 웹서버 서비스
    image: nginx:alpine # 사용할 이미지
    ports:
      - "8080:80"

5.2 compose 핵심 설정

서비스 정의 (services)

services:
  db:
    image: mysql:8.0 # Docker Hub의 이미지

포트 매핑 (ports)

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80" # 호스트 8080 -> 컨테이너 80
      - "8443:443" # 호스트 8443 -> 컨테이너 443

환경 변수 (environment)

# 방법 1 : 키-값 쌍
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password # 루트 비밀번호
      MYSQL_DATABASE: testdb    # 데이터베이스 이름
      MYSQL_USER: testuser      # 사용자 이름
      MYSQL_PASSWORD: password  # 사용자 비밀번호

# 방법 2 : 리스트 형식
services:
  db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=testdb
      - MYSQL_USER=testuser
      - MYSQL_PASSWORD=password

빌드설정 (build)

  • 이미지 대신 Dockerfile로 빌드
# 방법 1 : 간단한 빌드 설정
services:
  app:
    build: .  # 현재 디렉토리의 Dockerfile 사용
    ports:
      - "8080:8080"


# 방법 2 : 상세 빌드 설정
services:
  app:
    build:
      context: ./backend     # Dockerfile이 있는 디렉토리
      dockerfile: Dockerfile # Dockerfile 이름
    ports:
      - "8080:8080"

볼륨 마운트 (volumes)

# 방법 1 : 호스트 디렉토리 마운트
services:
  db:
    image: mysql:8.0
    volumes:
      # 호스트의 db_data 볼륨을 컨테이너의 /var/lib/mysql에 마운트
      - db_data:/var/lib/mysql


# 방법 2 : 호스트 경로 마운트
services:
    web:
        image: nginx:alpine
        volumes:
          # 호스트의 ./html 디렉토리를 컨테이너의 웹 루트에 마운트
          - ./html:/usr/share/nginx/html
          # 호스트의 nginx.conf 파일을 컨테이너에 마운트
          - ./nginx.conf:/etc/nginx/nginx.conf

네트워크 설정 (networks)

# 같은 네트워크
# backend 컨테이너에서 db라는 이름으로 데이터베이스에 접근
services:
  backend:
    image: myapp:1.0
  db:
    image: postgres:15

# 커스텀 네트워크 설정
services:
  web: # nginx 웹서버, frontend 네트워크에 연결
    image: nginx:alpine
    networks:
      - frontend

  app: # app 서버, frontend와 backend 네트워크에 연결
    image: myapp:1.0
    networks:
      - frontend
      - backend

  db: # db 서버, backend 네트워크에 연결
    image: postgres:15
    networks:
      - backend

networks: # 네트워크 정의
  frontend:
  backend:

5.3 Docker Compose 명령어

기본 실행/확인 명령어

# compose 서비스 시작 (-d : 백그라운드)
docker-compose up -d
docker-compose up

# 서비스 중지 및 삭제 (-v : 볼륨도 함께 삭제)
docker-compose down -v
docker-compose down

# 실행 중인 서비스 확인
docker-compose ps

# 로그 확인 (-f : 실시간 출력)
docker-compose logs -f

# 특정 서비스 로그 확인
docker-compose logs -f web

서비스 제어

# 특정 서비스 시작
docker-compose up -d web

# 특정 서비스 중지
docker-compose stop web

# 실행 중인 컨테이너에 실행
docker-compose exec web sh
docker-compose exec db mysql -u root -p

빌드 관련

# 이미지 빌드
docker-compose build

# 빌드 후 시작
docker-compose up -d --build

# 특정 서비스만 빌드
docker-compose build app

5.4 Docker Compose 예시

spring boot + mysql

  • application.yml 예시
spring:
  datasource:
    url: ${SPRING_DATASOURCE_URL}
    username: ${SPRING_DATASOURCE_USERNAME}
    password: ${SPRING_DATASOURCE_PASSWORD}
  • docker-compose.yml 예시
services:
  mysql2: # mysql 서비스
    image: mysql:8.0
    container_name: myapp-mysql
    environment:
      MYSQL_ROOT_PASSWORD: mydb
      MYSQL_DATABASE: mydb
      MYSQL_USER: myuser
      MYSQL_PASSWORD: myuser
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3307:3306"

  app: # 스프링부트 서비스
    build: .
    container_name: myapp-backend
    environment:
      # 환경변수로 DB 연결정보 전달 (서비스이름으로 호스트 지정)
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql2:3306/mydb
      SPRING_DATASOURCE_USERNAME: myuser
      SPRING_DATASOURCE_PASSWORD: myuser
    ports:
      - "8080:8080"
    depends_on: # 의존성 설정
      - mysql2

volumes:
  mysql-data: # mysql 데이터 영속화 볼륨

etc. 스프링부트에서 docker 실습

1. 네트워크 및 MySQL 컨테이너 생성

네트워크 생성

docker network create --driver bridge spring-net

MySQL 컨테이너 실행

docker run -d \
    --name mymysql \
    --network spring-net \
    -e MYSQL_ROOT_PASSWORD=rootpass \
    -e MYSQL_DATABASE=testdb \
    -e MYSQL_USER=testuser \
    -e MYSQL_PASSWORD=testpass \
    -p 3307:3306 \
    mysql:8


# 로그확인
docker logs mymysql

# 접속확인
docker exec -it mymysql mysql -u testuser -p

1. 스프링부트 애플리케이션 설정

spring:
  profiles:
    active: prod
  datasource:
    url: jdbc:mysql://mymysql:3306/testdb  # localhost -> mymysql (컨테이너 이름)
    username: testuser
    password: testpass
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

2. Dockerfile 작성

# Base Image: JDK 21 사용 (가볍고 안정적인 eclipse-temurin 사용 권장)
FROM eclipse-temurin:21-jdk-alpine

# 작업 디렉토리 생성
WORKDIR /app

# 필수 패키지 설치 (ping 용도)
RUN apk update && apk add --no-cache iputils

# 빌드 산출물(프로젝트 JAR)을 복사
COPY build/libs/*-SNAPSHOT.jar todoapp.jar

# 컨테이너가 사용하는 포트
EXPOSE 8080

# 실행 명령
ENTRYPOINT ["java", "-jar", "todoapp.jar", "--spring.profiles.active=prod"]

3. 이미지 빌드 및 실행

# 스프링부트 빌드
./gradlew build -x test

# 도커 이미지 빌드
docker build -t todoapp:1.0 .

# 도커 컨테이너 실행
docker run -d \
  --name springboot \
  --network spring-net \
  -p 8080:8080 \
  todoapp:1.0

4. 연결 접속 테스트

# 로그 확인
docker logs -f springboot

# 컨테이너 상태 확인
docker ps

# springboot 컨테이너에서 MySQL 연결 테스트
docker exec -it springboot ping mymysql

# 웹브라우저 접속
# http://localhost:8080/

5. docker hub 배포

# 도커 허브 로그인
docker login

# 이미지 태그 (도커 허브용)
docker tag todoapp:1.0 <도커허브계정>/todoapp:1.0

# 이미지 푸시
docker push <도커허브계정>/todoapp:1.0
starweb
@starweb :: starweb 님의 블로그

starweb 님의 블로그 입니다.

공감하셨다면 구독도 환영합니다!

목차