화. 8월 12th, 2025

G: 안녕하세요! 🚀 컨테이너 오케스트레이션의 핵심 도구 중 하나인 Docker Compose에 대해 궁금하신가요? 복잡한 여러 개의 도커 컨테이너를 한 번에 관리하고 싶으신가요? 그렇다면 이 글이 여러분의 궁금증을 시원하게 해결해 드릴 겁니다! 💡

이 가이드에서는 Docker Compose의 기본 개념부터 핵심 YAML 문법, 실전 예제, 그리고 배포 전략까지, 모든 것을 상세하게 다룰 예정입니다. 이제 함께 Docker Compose의 세계로 떠나볼까요? 🐳


1. Docker Compose, 너는 누구인가? 🤔

Docker Compose는 여러 개의 Docker 컨테이너로 구성된 애플리케이션을 정의하고 실행하기 위한 도구입니다. 간단한 YAML 파일을 사용하여 애플리케이션의 서비스, 네트워크, 볼륨 등을 한 번에 설정하고 관리할 수 있게 해줍니다.

💡 왜 Docker Compose를 사용해야 할까요?

  • 간편한 멀티 컨테이너 관리: 웹 서버, 데이터베이스, 백엔드 서비스 등 여러 컨테이너를 각각 docker run 명령어로 실행할 필요 없이, 단 하나의 명령어로 모든 서비스를 시작하고 중지할 수 있습니다.
  • 재현 가능한 개발 환경: docker-compose.yml 파일 하나로 팀원 모두가 동일한 개발 환경을 구축할 수 있어 “내 컴퓨터에서는 되는데…” 문제를 줄여줍니다.
  • 버전 관리 용이: 설정 파일이 코드처럼 관리되므로, 변경 사항을 쉽게 추적하고 롤백할 수 있습니다.
  • 빠른 개발 및 테스트: 로컬 환경에서 복잡한 서비스 스택을 빠르게 띄워 개발 및 테스트 효율을 높일 수 있습니다.

한 마디로, Docker Compose는 여러분의 도커 컨테이너들을 위한 “오케스트라 지휘자”와 같습니다! 🎼


2. Docker Compose의 핵심! docker-compose.yml 파일 파헤치기 📚

Docker Compose의 모든 마법은 바로 docker-compose.yml (또는 docker-compose.yaml) 파일에서 시작됩니다. 이 파일은 YAML 형식으로 작성되며, 여러분의 애플리케이션 서비스들을 어떻게 구성할지 정의합니다.

기본적인 구조는 다음과 같습니다:

version: '3.8' # Docker Compose 파일 형식 버전 (필수)
services:      # 애플리케이션을 구성하는 각 서비스 정의
  서비스_이름_1:
    이미지_정보
    포트_매핑
    볼륨_매핑
    환경_변수
    ...
  서비스_이름_2:
    ...
networks:      # 서비스 간 통신을 위한 네트워크 정의 (선택 사항)
  네트워크_이름_1:
    드라이버_정보
volumes:       # 데이터 지속성을 위한 볼륨 정의 (선택 사항)
  볼륨_이름_1:
    드라이버_정보

자, 이제 각 섹션을 자세히 살펴보겠습니다.

2.1. version (버전 명시)

  • Docker Compose 파일 형식의 버전을 지정합니다. 최신 기능과 문법을 사용하려면 가장 높은 안정 버전을 사용하는 것이 좋습니다. 일반적으로 '3.8'을 많이 사용합니다.
  • 예시: version: '3.8'

2.2. services (핵심 서비스 정의) ✨

services 섹션은 여러분의 애플리케이션을 구성하는 개별 컨테이너(서비스)들을 정의하는 곳입니다. 각 서비스는 고유한 이름을 가지며, 해당 서비스가 어떻게 실행될지 다양한 속성으로 상세하게 설정할 수 있습니다.

  • image: 🐳 이 서비스가 사용할 Docker 이미지입니다. Docker Hub에서 가져오거나, 로컬에 빌드된 이미지를 사용할 수 있습니다.

    • 예시: image: nginx:latest, image: mongo:4.4
  • build: 🛠️ 이 서비스의 이미지를 직접 빌드하고 싶을 때 사용합니다.

    • context: Dockerfile이 위치한 경로를 지정합니다.
    • dockerfile: 사용할 Dockerfile의 이름을 지정합니다 (기본값은 Dockerfile).
    • 예시:
      services:
        webapp:
          build:
            context: ./app # 현재 디렉터리 아래 app 폴더에 Dockerfile이 있음
            dockerfile: Dockerfile.dev # app/Dockerfile.dev 파일을 사용
  • ports: 🚪 컨테이너의 포트를 호스트 머신의 포트로 매핑합니다. 호스트_포트:컨테이너_포트 형식으로 작성합니다.

    • 예시: ports: - "80:80" (호스트의 80번 포트를 컨테이너의 80번 포트로 연결), - "3000:3000"
  • volumes: 📂 데이터의 지속성을 위해 호스트 머신의 디렉터리나 Docker 볼륨을 컨테이너 내부의 경로에 연결합니다.

    • 바인드 마운트 (Bind Mount): 호스트_경로:컨테이너_경로 – 개발 환경에서 소스 코드 변경 시 즉시 반영되어 편리합니다.
      • 예시: - "./app:/usr/src/app" (현재 디렉터리 아래 app 폴더를 컨테이너 /usr/src/app에 연결)
    • 명명된 볼륨 (Named Volume): 볼륨_이름:컨테이너_경로 – Docker가 관리하는 볼륨으로, 데이터 영속성이 중요할 때 사용합니다. (아래 volumes 섹션에서 정의해야 함)
      • 예시: - "db_data:/data/db"
  • environment: ⚙️ 컨테이너 내부에서 사용할 환경 변수를 설정합니다. 민감한 정보는 .env 파일을 활용하는 것이 좋습니다.

    • 예시:
      environment:
        - NODE_ENV=production
        - DB_HOST=database
        - DB_PORT=27017
    • .env 파일 사용 예시:
      # .env 파일 내용
      DB_USER=admin
      DB_PASSWORD=mysecretpassword
      # docker-compose.yml 파일 내용
      services:
        db:
          image: postgres
          environment:
            - POSTGRES_USER=${DB_USER}       # .env 파일의 DB_USER 값을 가져옴
            - POSTGRES_PASSWORD=${DB_PASSWORD} # .env 파일의 DB_PASSWORD 값을 가져옴
  • depends_on: 🔗 서비스 간의 의존성을 정의합니다. 명시된 서비스가 먼저 시작된 후에 해당 서비스가 시작됩니다.

    • 주의: depends_on은 시작 순서만 보장하며, 서비스가 완전히 “준비”되었음을 보장하지는 않습니다. (예: 데이터베이스가 완전히 부팅되고 연결을 받을 준비가 되었는지 보장하지 않음) 이를 위해서는 healthcheck나 애플리케이션 레벨의 재시도 로직이 필요합니다.
    • 예시:
      services:
        webapp:
          depends_on:
            - database # database 서비스가 시작된 후에 webapp 서비스 시작
        database:
          image: mongo
  • networks: 🌐 해당 서비스가 연결될 네트워크를 지정합니다. 기본적으로 Docker Compose는 모든 서비스가 속하는 하나의 기본 네트워크를 생성합니다. 사용자 정의 네트워크를 사용할 경우 명시적으로 연결해야 합니다.

    • 예시:
      services:
        frontend:
          networks:
            - frontend_network
        backend:
          networks:
            - frontend_network
            - backend_network
      networks: # 아래 networks 섹션에서 정의해야 함
        frontend_network:
        backend_network:
  • command & entrypoint: ✍️ 컨테이너가 시작될 때 실행될 명령어를 오버라이드하거나 추가합니다.

    • command: 이미지의 기본 CMD를 오버라이드합니다.
    • entrypoint: 이미지의 기본 ENTRYPOINT를 오버라이드합니다.
    • 예시:
      services:
        my_app:
          image: my_custom_image
          command: ["npm", "start"] # 컨테이너 시작 시 npm start 실행
          # entrypoint: ["/usr/bin/my_custom_script"]
  • restart: 🔄 컨테이너 종료 시 재시작 정책을 정의합니다.

    • no: 재시작하지 않음 (기본값)
    • always: 컨테이너가 종료되거나 Docker 데몬이 재시작될 때 항상 재시작
    • on-failure: 비정상적인 종료(종료 코드 0이 아님) 시에만 재시작
    • unless-stopped: 컨테이너가 수동으로 중지되지 않는 한 항상 재시작
    • 예시: restart: always
  • healthcheck: 💖 서비스의 상태를 주기적으로 확인하여 컨테이너가 “건강한” 상태인지 여부를 파악합니다. depends_on과 함께 사용하여 의존성 컨테이너가 실제로 준비되었는지 확인하는 데 유용합니다.

    • 예시:
      services:
        db:
          image: postgres
          healthcheck:
            test: ["CMD-SHELL", "pg_isready -U postgres"]
            interval: 5s     # 5초마다 테스트
            timeout: 5s      # 테스트 타임아웃 5초
            retries: 5       # 실패 시 5번 재시도
            start_period: 10s # 시작 후 10초간은 헬스체크 대기

2.3. networks (네트워크 정의) 🌐

서비스 간 격리된 통신 환경을 구축하거나, 특정 서비스 그룹만 통신하도록 할 때 사용합니다. bridge, overlay 등 다양한 드라이버를 사용할 수 있습니다.

  • 예시:
    networks:
      frontend_network:
        driver: bridge # 기본값이지만 명시 가능
      backend_network:
        driver: bridge

2.4. volumes (볼륨 정의) 💾

데이터의 영속성을 위해 명명된 볼륨을 정의합니다. 데이터베이스 파일 등 컨테이너가 삭제되어도 보존되어야 하는 데이터를 저장하는 데 사용됩니다.

  • 예시:
    volumes:
      db_data:     # db_data라는 이름의 볼륨 정의
        driver: local # 로컬 드라이버 사용 (기본값)
      static_files: # 정적 파일을 위한 볼륨

3. Docker Compose 기본 명령어 💻

docker-compose.yml 파일을 작성했다면, 이제 몇 가지 간단한 명령어로 애플리케이션을 관리할 수 있습니다.

  • docker-compose up: 🚀 docker-compose.yml 파일에 정의된 모든 서비스를 빌드하고 시작합니다.

    • docker-compose up: 포그라운드에서 실행되며 로그를 출력합니다.
    • docker-compose up -d: 백그라운드 (detached mode)에서 실행합니다. 서비스가 시작된 후 터미널 제어권을 돌려줍니다. (가장 많이 사용)
  • docker-compose down: 🛑 실행 중인 모든 서비스를 중지하고 컨테이너를 제거합니다.

    • docker-compose down -v: 컨테이너와 함께 연결된 볼륨까지 제거합니다. (데이터베이스 등 영속적인 데이터가 필요 없는 경우 주의해서 사용)
  • docker-compose ps: 📄 현재 실행 중인 서비스들의 상태를 보여줍니다.

  • docker-compose logs [서비스_이름]: 📜 특정 서비스 또는 전체 서비스의 로그를 확인합니다.

    • docker-compose logs -f webapp: webapp 서비스의 로그를 실시간으로 추적합니다.
  • docker-compose exec [서비스_이름] [명령어]: 🖥️ 실행 중인 특정 서비스 컨테이너 내부에서 명령어를 실행합니다.

    • docker-compose exec webapp bash: webapp 컨테이너 내부로 들어가 bash 쉘을 실행합니다.
  • docker-compose build [서비스_이름]: 🔨 특정 서비스 또는 모든 서비스의 이미지를 다시 빌드합니다. build 명령어가 있는 서비스에만 해당됩니다.

  • docker-compose stop [서비스_이름]: ⏸️ 특정 서비스를 중지합니다. (제거는 하지 않음)

  • docker-compose start [서비스_이름]: ▶️ 중지된 서비스를 다시 시작합니다.


4. 실전 예제: Nginx + Node.js + MongoDB 애플리케이션 👨‍💻

가장 흔하게 볼 수 있는 웹 애플리케이션 스택을 Docker Compose로 구축해보겠습니다.

  • Nginx: 리버스 프록시 및 정적 파일 서빙
  • Node.js (Express): 백엔드 API 서버
  • MongoDB: 데이터베이스

4.1. 프로젝트 구조

my-mern-app/
├── app/
│   ├── Dockerfile          # Node.js 앱을 위한 Dockerfile
│   ├── package.json        # Node.js 의존성
│   └── server.js           # Node.js Express 앱
├── nginx/
│   └── nginx.conf          # Nginx 설정 파일
└── docker-compose.yml      # Docker Compose 메인 파일

4.2. 파일 내용 작성

my-mern-app/app/Dockerfile:

# Node.js 18 버전 기반 이미지 사용
FROM node:18-alpine

# 작업 디렉터리 설정
WORKDIR /usr/src/app

# package.json 및 package-lock.json 복사
COPY package*.json ./

# 의존성 설치
RUN npm install

# 앱 소스 코드 복사
COPY . .

# 앱이 사용할 포트 노출
EXPOSE 3000

# 앱 실행 명령어
CMD ["npm", "start"]

my-mern-app/app/package.json:

{
  "name": "node-app",
  "version": "1.0.0",
  "description": "Simple Node.js app",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.6.3"
  }
}

my-mern-app/app/server.js:

const express = require('express');
const mongoose = require('mongoose');
const app = express();
const port = 3000;

// 환경 변수에서 MongoDB 연결 정보 가져오기
const DB_HOST = process.env.DB_HOST || 'localhost';
const DB_PORT = process.env.DB_PORT || '27017';
const DB_NAME = process.env.DB_NAME || 'testdb';
const MONGODB_URI = `mongodb://${DB_HOST}:${DB_PORT}/${DB_NAME}`;

mongoose.connect(MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('MongoDB connected! 🚀'))
  .catch(err => console.error('MongoDB connection error:', err));

// MongoDB 스키마 및 모델 정의 (간단 예시)
const ItemSchema = new mongoose.Schema({
  name: String,
  description: String
});
const Item = mongoose.model('Item', ItemSchema);

app.use(express.json());

// API 엔드포인트
app.get('/', (req, res) => {
  res.send('Hello from Node.js App! 🎉');
});

app.get('/items', async (req, res) => {
  try {
    const items = await Item.find();
    res.json(items);
  } catch (error) {
    res.status(500).send(error.message);
  }
});

app.post('/items', async (req, res) => {
  try {
    const newItem = new Item(req.body);
    await newItem.save();
    res.status(201).json(newItem);
  } catch (error) {
    res.status(400).send(error.message);
  }
});

app.listen(port, () => {
  console.log(`Node.js app listening at http://localhost:${port}`);
});

my-mern-app/nginx/nginx.conf:

events {
    worker_connections 1024;
}

http {
    upstream backend {
        server node_app:3000; # node_app 서비스의 3000번 포트로 연결
    }

    server {
        listen 80;
        server_name localhost;

        location / {
            proxy_pass http://backend; # 모든 요청을 backend (node_app)로 전달
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # 정적 파일을 서빙하려면 다음과 같이 추가
        # location /static/ {
        #     alias /usr/share/nginx/html/static/;
        # }
    }
}

my-mern-app/docker-compose.yml:

version: '3.8'

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80" # 호스트의 80포트를 Nginx의 80포트로 연결
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro # Nginx 설정 파일 마운트 (읽기 전용)
    depends_on:
      - node_app # node_app이 시작된 후 Nginx 시작
    networks:
      - app_network # Nginx와 node_app이 동일한 네트워크 사용

  node_app:
    build: ./app # app 폴더의 Dockerfile을 사용하여 이미지 빌드
    ports:
      - "3000:3000" # 개발 시 테스트를 위해 호스트 포트도 열어둠 (선택 사항)
    volumes:
      - ./app:/usr/src/app # 소스 코드 변경 시 즉시 반영되도록 바인드 마운트
      - /usr/src/app/node_modules # node_modules는 호스트에서 제외 (도커 컨테이너 내부의 것을 사용)
    environment:
      DB_HOST: mongo_db # MongoDB 서비스 이름을 호스트로 지정
      DB_PORT: 27017
      DB_NAME: testdb
    depends_on:
      - mongo_db # mongo_db가 시작된 후 node_app 시작
    networks:
      - app_network # Nginx와 node_app, mongo_db가 동일한 네트워크 사용

  mongo_db:
    image: mongo:4.4 # MongoDB 이미지 사용
    ports:
      - "27017:27017" # 개발 시 테스트를 위해 호스트 포트도 열어둠 (선택 사항)
    volumes:
      - mongo_data:/data/db # 데이터 영속성을 위해 명명된 볼륨 사용
    networks:
      - app_network # 모든 서비스가 동일한 네트워크 사용

networks:
  app_network:
    driver: bridge # 기본 브릿지 네트워크

volumes:
  mongo_data: # mongo_db 서비스에서 사용할 볼륨 정의

4.3. 실행하기

  1. 위 파일들을 해당 경로에 맞게 생성합니다.

  2. my-mern-app 디렉터리에서 터미널을 엽니다.

  3. 다음 명령어를 실행합니다:

    docker-compose up -d
  4. 잠시 기다린 후, 모든 컨테이너가 시작되었는지 확인합니다:

    docker-compose ps

    모든 서비스(nginx, node_app, mongo_db)의 상태가 Up으로 표시되면 성공입니다.

  5. 웹 브라우저에서 http://localhost/ 에 접속하면 Node.js 앱의 응답인 “Hello from Node.js App! 🎉” 메시지를 볼 수 있습니다.

  6. http://localhost/items 에 접속하면 빈 배열 []이 보일 겁니다. (아직 데이터가 없으므로)

  7. Postman 또는 curl로 데이터를 추가해 보세요:

    curl -X POST -H "Content-Type: application/json" -d '{"name": "Docker Compose", "description": "The best tool ever!"}' http://localhost/items
  8. 다시 http://localhost/items 에 접속하면 추가된 데이터를 확인할 수 있습니다!

4.4. 정리하기

작업이 끝났다면, 다음 명령어로 모든 컨테이너와 네트워크를 깔끔하게 제거할 수 있습니다:

docker-compose down -v # -v 옵션은 mongo_data 볼륨도 함께 삭제합니다.

5. Docker Compose 배포 전략 및 고급 팁 🚀

개발 환경에서 Docker Compose는 매우 강력하지만, 프로덕션 환경에서는 몇 가지 고려 사항이 있습니다.

5.1. .env 파일로 환경 변수 관리 🔐

민감한 정보(API 키, 데이터베이스 비밀번호 등)는 docker-compose.yml 파일에 직접 넣는 대신 .env 파일을 사용하여 관리하는 것이 좋습니다. .gitignore.env 파일을 추가하여 Git에 커밋되지 않도록 합니다.

# .env
DB_PASSWORD=mySuperSecretPassword
API_KEY=your_api_key_here
# docker-compose.yml
services:
  my_service:
    environment:
      - SECRET_KEY=${API_KEY} # .env 파일에서 가져옴

5.2. 여러 개의 Compose 파일 사용 📂

개발 환경과 프로덕션 환경의 설정이 다를 경우, 여러 개의 Compose 파일을 조합하여 사용할 수 있습니다.

  • docker-compose.yml: 공통 설정
  • docker-compose.dev.yml: 개발 환경 전용 오버라이드 (볼륨 마운트, 디버그 포트 등)
  • docker-compose.prod.yml: 프로덕션 환경 전용 오버라이드 (리소스 제한, 리스타트 정책, 빌드 컨텍스트 등)

사용 예시:

  • 개발 환경: docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
  • 프로덕션 환경: docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

5.3. 스케일링 (Scaling) 📈

Docker Compose는 up 명령어를 통해 서비스의 인스턴스를 쉽게 확장할 수 있습니다 (Docker Swarm 또는 Kubernetes 없이 단순한 복제).

docker-compose up --scale webapp=3 -d

위 명령은 webapp 서비스의 컨테이너를 3개로 복제하여 실행합니다. 이는 로드 밸런싱이 필요한 경우 유용합니다. (단, 실제 로드 밸런싱을 위해서는 Nginx와 같은 리버스 프록시가 필요합니다.)

5.4. 프로덕션 환경 고려 사항 🏭

  • 리버스 프록시: Nginx, Traefik 등을 사용하여 외부 요청을 적절한 서비스로 라우팅하고, SSL/TLS를 설정합니다.
  • 지속적인 통합/배포 (CI/CD): Jenkins, GitLab CI, GitHub Actions 등과 연동하여 코드 푸시 시 자동으로 빌드, 테스트, 배포되도록 자동화합니다.
  • 로깅 및 모니터링: ELK 스택 (Elasticsearch, Logstash, Kibana)이나 Prometheus, Grafana 등을 사용하여 애플리케이션 로그와 성능 지표를 수집하고 시각화합니다.
  • 보안: 이미지 최소화, 취약점 스캔, 비밀번호 관리 등 보안 모범 사례를 따릅니다.
  • 리소스 제한: resources 섹션을 사용하여 각 서비스에 할당할 CPU 및 메모리 제한을 설정할 수 있습니다.
    services:
      my_service:
        deploy:
          resources:
            limits:
              cpus: '0.5' # 0.5 코어
              memory: 512M # 512MB
            reservations:
              cpus: '0.25'
              memory: 256M

6. 문제 해결 팁 (Troubleshooting) 🐞

Docker Compose 사용 중 흔히 발생하는 문제와 해결 팁입니다.

  • 컨테이너가 실행되지 않음 (Exited):
    • docker-compose logs [서비스_이름] 명령어로 로그를 확인합니다. 오류 메시지가 문제의 단서를 제공할 것입니다.
    • 컨테이너 내부에서 수동으로 명령어를 실행해 보세요: docker-compose exec [서비스_이름] bash (또는 sh) -> ls -l, cat, npm start 등.
  • 포트 충돌 (Port Conflict):
    • port already in use 오류가 발생하면, 해당 포트가 이미 호스트 머신에서 사용 중인 것입니다. netstat -tulnp | grep [포트_번호] (Linux/macOS) 또는 netstat -ano | findstr :[포트_번호] (Windows PowerShell)로 어떤 프로세스가 사용 중인지 확인하고 종료하거나, 다른 포트 번호로 변경하세요.
  • 볼륨 마운트 문제 (Volume Permissions):
    • permission denied 오류는 컨테이너 내부의 프로세스가 마운트된 볼륨에 쓰기/읽기 권한이 없을 때 발생합니다. 호스트의 디렉터리 권한을 변경하거나, Dockerfile에서 컨테이너 내부 사용자의 권한을 조절해야 할 수 있습니다.
  • 의존성 서비스가 준비되지 않음 (depends_on의 한계):
    • depends_on은 시작 순서만 보장하며, 서비스의 “준비” 상태를 보장하지 않습니다. 백엔드 앱이 데이터베이스에 연결하려고 할 때 데이터베이스가 아직 완전히 부팅되지 않아 연결 실패할 수 있습니다.
    • 해결책:
      • 애플리케이션 코드에 재시도(Retry) 로직을 구현합니다.
      • healthcheck를 사용하여 서비스의 준비 상태를 정확히 파악하고, depends_on과 함께 condition: service_healthy를 사용합니다.
        services:
          webapp:
            depends_on:
              database:
                condition: service_healthy # database 서비스가 healthy 상태일 때 webapp 시작
          database:
            healthcheck:
              test: ["CMD-SHELL", "pg_isready -U postgres"]
              interval: 5s
  • YAML 문법 오류:
    • docker-compose config 명령어를 사용하여 YAML 파일의 문법을 검사할 수 있습니다. 오류가 있다면 어떤 줄에서 발생했는지 알려줍니다.

7. 마무리하며 🎉

축하드립니다! 이제 여러분은 Docker Compose의 기본적인 개념부터 심화 사용법, 그리고 실제 애플리케이션 배포 시 고려해야 할 사항들까지 모두 이해하게 되셨습니다. Docker Compose는 로컬 개발 환경을 넘어 소규모 프로덕션 환경에서도 매우 유용하게 사용될 수 있는 강력한 도구입니다.

이 가이드가 여러분의 Docker 여정에 큰 도움이 되었기를 바랍니다. 궁금한 점이 있다면 언제든지 댓글로 남겨주세요! Happy Dockering! 🐳💖


답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다