— DevOps Advanced Study

🐳 Docker 실무

Multi-stage Build를 통한 이미지 최적화와 Docker Compose를 이용한 다중 컨테이너 오케스트레이션을 학습합니다.

2026 DevOps
12 슬라이드
Build once, Run anywhere
01 — Nginx 웹 서버 개요

🟢Nginx란?

Nginx (Engine-X)

Nginx는 가볍고 성능이 뛰어난 웹 서버(Web Server)이자 리버스 프록시(Reverse Proxy)입니다.

🌟 주요 특징

  • 비동기 이벤트 기반 구조: 적은 메모리로 많은 동시 접속 처리가 가능합니다.
  • 리버스 프록시: 클라이언트의 요청을 대신 받아 백엔드 서버로 전달합니다.
  • 정적 파일 서빙: HTML, CSS, 이미지 등을 매우 빠르게 제공합니다.

📚 용어 정리

1. 비동기 (Asynchronous)

  • 동기(Sync): 이전 작업이 완료될 때까지 다음 작업이 대기하는 방식입니다.
  • 비동기(Async): 이전 작업의 완료 여부와 상관없이 다음 작업을 동시에 진행하는 방식입니다.

2. 프록시 / 리버스 프록시 (Reverse Proxy)

  • 포워드 프록시: 클라이언트 앞에 위치하여, 클라이언트를 대신해 외부 서버에 요청을 보냅니다.
  • 리버스 프록시: 서버 앞에 위치하여, 외부 요청을 받아 내부 백엔드 서버로 전달합니다.

3. 로드밸런싱 (Load Balancing)

들어오는 트래픽을 여러 서버에 균등하게 분산시켜 특정 서버의 과부하를 방지하는 기술입니다.

02 — Multi-stage Build 개념

🏗️ Multi-stage Build란?

하나의 Dockerfile 안에서 여러 개의 FROM 구문을 사용하여 빌드 단계(Stage)를 나누는 기법입니다.

❌ 일반 빌드 (Single-stage)
📦 Node.js SDK + 컴파일러
📄 소스 코드 전체
📁 node_modules (수백 MB)
🗂️ dist/ (실제 필요한 결과물)
✅ Multi-stage Build
STAGE 1: builder
📦 Node.js SDK
📄 소스 코드
📁 node_modules
🗂️ dist/ ← 생성
COPY dist/만
STAGE 2: production
🟢 nginx:alpine
🗂️ dist/ ← 복사됨
① FROM … AS <name>
각 Stage에 이름을 붙여 구분합니다. 하나의 Dockerfile에 FROM이 여러 번 등장할 수 있습니다.
② COPY --from=<name>
이전 Stage의 파일시스템에서 필요한 파일만 선택적으로 복사합니다. 나머지는 최종 이미지에 포함되지 않습니다.
③ 마지막 FROM이 최종
가장 마지막 FROM 구문의 Stage만 실제 배포 이미지로 생성됩니다. 앞선 Stage는 중간 작업 공간입니다.

🚀 왜 사용하는가?

  • 이미지 크기 최소화: 빌드에만 필요한 도구(SDK, 컴파일러 등)를 최종 이미지에서 제외합니다.
  • 보안 강화: 소스 코드나 불필요한 바이너리를 배포 이미지에 포함하지 않아 공격 표면을 줄입니다.
  • 캐싱 최적화: 단계별로 레이어를 나누어 빌드 속도를 향상시킬 수 있습니다.

💡 비교: 일반 빌드 방식은 "빌드 도구 + 소스 + 결과물"이 모두 포함되어 수백 MB가 넘어가지만, Multi-stage는 "결과물"만 담아 수십 MB로 줄일 수 있습니다.

03 — Dockerfile 설정

💻 Multi-stage 예제 (Dockerfile)

npm ci를 활용하여 의존성을 설치하고, 빌드된 결과물(dist)만 Nginx 이미지로 복사합니다.

Dockerfile

# Stage 1: Build Stage FROM node:22-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: Production Stage FROM nginx:alpine AS production COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
npm install 매번 최신 버전을 조회해 설치. 버전이 달라질 수 있어 재현성이 낮음
npm ci package-lock.json을 기준으로 정확히 고정된 버전 설치. CI/CD 환경 권장
daemon off; Nginx를 백그라운드 데몬이 아닌 포그라운드로 실행. PID 1이 유지되어야 컨테이너가 종료되지 않음

💡 레이어 캐싱 전략

package*.json을 소스보다 먼저 COPY하는 이유:

  • 소스 코드는 자주 바뀌지만 package.json은 상대적으로 안정적
  • 소스만 변경됐을 때 npm ci 레이어를 캐시에서 재사용 → 빌드 속도 향상

.dockerignore

node_modules dist .git .gitignore .vscode npm-debug.log* README.md
04 — Nginx 설정

⚙️ 설정 파일 (Nginx)

SPA(Single Page Application) 라우팅을 위한 Nginx 설정입니다.

nginx.conf

server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } }
지시어(Directive) 설명
listen 80 HTTP 기본 포트. HTTPS는 443 + SSL 설정 추가 필요
server_name 실제 운영 시 도메인명 입력 (example.com). 여러 도메인 공백으로 구분 가능
root Dockerfile에서 COPY --from=builder /app/dist로 복사한 경로와 일치해야 함
location / 모든 URL 패턴을 이 블록에서 처리. API 서버가 있다면 location /api 블록으로 분리 가능

💡 Nginx SPA 설정

  • try_files: 요청된 파일이 없으면 /index.html로 라우팅하여 Vue Router의 History 모드를 정상적으로 지원합니다.
  • 기본 포트 80으로 들어오는 모든 요청을 처리하며, /usr/share/nginx/html에서 정적 파일들을 서빙합니다.
05 — 이미지 빌드 및 컨테이너 실행

🚀 이미지 빌드 및 컨테이너 실행

Docker 이미지를 빌드하고 실행하여 Multi-stage 빌드의 결과물을 확인합니다.

1. 이미지 빌드

# 이미지 빌드 [devops@localhost ~]$ docker build -t vue-multistage:1.0 . ... => [production 3/3] COPY --from=builder /app/dist /usr/share/nginx/html => exporting to image => => naming to docker.io/library/vue-multistage:1.0 # 이미지 크기 확인 (~91.2MB) [devops@localhost ~]$ docker images IMAGE ID DISK USAGE CONTENT SIZE vue-multistage:1.0 d396442cd561 91.2MB 26MB
docker build 옵션
-t <이름:태그> vue-multistage는 이미지 이름, 1.0은 버전 태그. 생략 시 latest로 자동 지정
. (빌드 컨텍스트) 현재 디렉토리를 빌드 컨텍스트로 설정. Dockerfile과 복사할 파일들의 기준 경로가 됨

2. 컨테이너 실행

# 컨테이너 백그라운드 실행 [devops@localhost ~]$ docker run -d -p 8000:80 --name=multistage-container vue-multistage:1.0 # 프로세스 확인 [devops@localhost ~]$ docker ps CONTAINER ID IMAGE STATUS PORTS NAMES 155b60a86034 vue-multistage:1.0 Up 30 seconds 0.0.0.0:8000->80/tcp, [::]:8000->80/tcp multistage-container
docker run 옵션
-d detached 모드. 컨테이너를 백그라운드에서 실행. 없으면 터미널이 컨테이너에 붙어 포그라운드로 실행됨
-p 8000:80 포트 포워딩. 호스트포트:컨테이너포트 형식. 호스트의 8000 → 컨테이너 Nginx 80으로 연결
--name 컨테이너에 이름 부여. 생략 시 Docker가 무작위 이름 자동 생성. 이름으로 stop/rm 가능
-p 8000:80 포트 포워딩 구조
🌐 브라우저
localhost:8000
🖥️ 호스트
:8000
📦 컨테이너
Nginx :80
06 — Docker Compose 개요

📋 Docker Compose란?

여러 개의 컨테이너를 정의하고 실행하기 위한 도구입니다. YAML 파일을 사용하여 애플리케이션의 서비스를 설정합니다.
* YAML은 Yet Another Markup Language 또는 YAML Ain't Markup Language를 의미합니다.

🌟 주요 특징

  • 단일 명령 실행: docker-compose up 한 번으로 모든 서비스를 시작합니다.
  • 서비스 격리: 각 프로젝트마다 독립된 환경을 유지합니다.
  • 데이터 보존: 컨테이너가 삭제되어도 볼륨을 통해 데이터를 유지합니다.
  • 네트워크 자동 생성: 서비스 간 통신을 위한 기본 네트워크를 자동으로 구축합니다.
07 — YAML 문법 개요

📝 YAML 코드란?

YAML 코드는 사람이 읽고 쓰기 쉽게 만들어진 데이터 직렬화 언어입니다. Docker Compose와 Kubernetes 등 다양한 인프라 도구의 설정 파일로 널리 사용됩니다.

📌 핵심 규칙

  • 들여쓰기: 스페이스(Space)를 사용한 들여쓰기로 계층 구조를 나타냅니다. (탭(Tab) 문자 사용은 권장되지 않습니다)
  • Key-Value: 키: 값 형태로 데이터를 정의합니다. 콜론(:) 뒤에는 반드시 공백(스페이스)이 하나 있어야 합니다.
  • 리스트(배열): 하이픈(-)으로 시작하여 여러 개의 항목을 나열합니다.
  • 주석: # 기호를 사용하여 주석을 작성합니다.

💻 작성 예시

# 사용자 설정 예시 name: "kobong" age: unknown # 하위 속성 (들여쓰기로 계층 표현) address: city: "Seoul" zipcode: "12345" # 리스트(배열) skills: - Docker - Linux
08 — Docker Compose CLI 명령어

⚡ 주요 명령어 (CLI)

실제 운영 시 가장 자주 사용되는 Compose 명령어 모음입니다.

명령어 설명
docker-compose up -d 모든 서비스를 생성하고 백그라운드에서 실행
docker-compose down 모든 컨테이너와 네트워크를 정지시키고 삭제
docker-compose logs -f 실행 중인 서비스의 로그를 실시간 확인
docker-compose ps 현재 Compose 프로젝트의 컨테이너 상태 확인
docker-compose exec [서비스] sh 실행 중인 컨테이너 내부에 접속
09 — 실습: 방문자 카운터 (1)

🔥 실습: 방문자 카운터 (1) — 소스 코드

애플리케이션 소스 코드와 컨테이너 이미지를 빌드하기 위한 Dockerfile 구성입니다.

📄 main.py

import os from fastapi import FastAPI from redis import Redis app = FastAPI() redis_client = Redis( host=os.getenv("REDIS_HOST", "localhost"), port=int(os.getenv("REDIS_PORT", "6379")), decode_responses=True, ) VISITOR_COUNT_KEY = "visitor_count" @app.get("/") def count_visitor(): count = redis_client.incr(VISITOR_COUNT_KEY) return { "message": "Hello FastAPI + Redis!", "visitor_count": count, }

📄 Dockerfile

FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY main.py . EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

📄 requirements.txt

fastapi uvicorn[standard] redis
10 — 실습: 방문자 카운터 (2)

🔥 실습: 방문자 카운터 (2) — Compose & 실행

Docker Compose를 활용하여 다중 컨테이너 환경을 구성하고 실행합니다.

📄 docker-compose.yaml

services: app: build: . ports: - "8000:8000" environment: REDIS_HOST: redis REDIS_PORT: 6379 depends_on: - redis restart: always redis: image: redis:7-alpine ports: - "6379:6379" restart: always

🚀 실행 및 확인

# 컨테이너 실행 [devops@localhost]$ docker compose up -d # 상태 확인 [devops@localhost]$ docker compose ps NAME STATUS PORTS fastapi-redis-counter-app-1 Up 7 seconds 0.0.0.0:8000->8000/tcp fastapi-redis-counter-redis-1 Up 7 seconds 0.0.0.0:6379->6379/tcp # 리소스 정리 [devops@localhost]$ docker compose down

💡 주요 포인트

  • 서비스 이름 통신: appredis라는 이름으로 자동 연결됩니다.
  • 의존성 관리: depends_on으로 실행 순서를 제어합니다.
  • 원자적 연산: Redis의 incr()는 동시 요청에도 안전합니다.
11 — 부록: Node & Vue.js 설치

✨ 부록: Node & Vue.js 설치

Node.js와 npm을 설치하고 최신 Vue 템플릿을 생성하여 개발 서버를 실행하는 과정입니다.

1. Node.js & npm 설치

# nodejs 패키지 검색 [devops@localhost ~]$ sudo dnf search nodejs ================ Name Exactly Matched: nodejs ================ nodejs.x86_64 : JavaScript runtime ... # nodejs & npm 설치 [devops@localhost ~]$ sudo dnf install -y nodejs npm Installing: nodejs x86_64 1:22.22.2-1.el10_1 nodejs-npm x86_64 1:10.9.7-1... # 버전 확인 [devops@localhost ~]$ node -v v22.22.2 [devops@localhost ~]$ npm -v 10.9.7

2. Vue 프로젝트 생성 및 실행

# Vue 최신 버전 템플릿 생성 [devops@localhost project]$ npm create vue@latest Vue.js - The Progressive JavaScript Framework ◇ Project name: multi-stage ◇ Use TypeScript? Yes ... Scaffolding project in /home/.../multi-stage # 의존성 설치 [devops@localhost project]$ cd multi-stage/ [devops@localhost multi-stage]$ npm install added 263 packages, and audited 264 packages in 45s # 개발 서버 실행 [devops@localhost multi-stage]$ npm run dev VITE v8.0.10 ready in 2153 ms ➜ Local: http://localhost:5173/

💡 참고 사항

  • 설치 시 create-vue 패키지를 사용하여 프로젝트를 스캐폴딩합니다.
  • 기본 개발 서버 주소는 http://localhost:5173/ 입니다.
  • 외부 네트워크에서 접근하려면 --host 플래그를 추가해야 합니다.
이동  ·  T 테마