docker

Docker

概要

Dockerに関する知識とメモ

基本概念

コンテナとは

イメージとは

ボリューム(Volumes)とは

  • Dockerコンテナのデータを永続化するための仕組み
  • コンテナが削除されてもデータが保持される
  • ホストとコンテナ間でファイルを共有できる

重要な概念:

  • コンテナ自体はデータを永続化できない - コンテナ削除でデータも消失
  • ボリュームでデータの永続化を実現 - コンテナとは独立してデータを保存
  • 本番環境でのデータ保護 - サーバーデプロイ時もボリューム設定でデータを保持

ボリュームの種類

  1. 名前付きボリューム(Named Volumes)

    • Dockerが管理する永続化ストレージ
    • 複数のコンテナ間でデータを共有可能
  2. バインドマウント(Bind Mounts)

    • ホストの特定のディレクトリをコンテナにマウント
    • ホストのファイルシステムに直接アクセス
  3. tmpfs マウント

    • メモリ上に一時的なファイルシステムを作成
    • コンテナ停止時にデータは消失

ボリュームの基本的な使い方

# 名前付きボリュームの作成
docker volume create my-volume

# ボリューム一覧の確認
docker volume ls

# ボリュームの詳細情報
docker volume inspect my-volume

# ボリュームを使ってコンテナを起動
docker run -v my-volume:/app/data nginx

# バインドマウントの例
docker run -v /host/path:/container/path nginx

# 読み取り専用でマウント
docker run -v /host/path:/container/path:ro nginx

ボリュームの実際の保存場所

名前付きボリュームの保存場所:

  • Linux: /var/lib/docker/volumes/<volume_name>/_data
  • macOS (Docker Desktop): Docker VM内の /var/lib/docker/volumes/<volume_name>/_data
  • Windows (Docker Desktop): Docker VM内の /var/lib/docker/volumes/<volume_name>/_data

Docker VMとは?

  • macOSやWindowsでは、DockerはLinuxカーネルが必要なため、仮想マシン(VM)上で動作する
  • Docker Desktopが自動的にLinux VMを作成・管理している
  • このVMは通常のFinderやエクスプローラーからは見えない隠れた仮想環境

ローカルファイルからの確認について:

  • Linux: ホストマシンのファイルシステムに直接保存されるため、通常のファイルマネージャーでアクセス可能
  • macOS/Windows: Docker VM内に保存されるため、通常のFinderやエクスプローラーからは直接アクセス不可
  • ただし、Docker Desktopの設定でファイル共有を有効にしている場合、一部アクセス可能な場合もある
# ボリュームの詳細情報で保存場所を確認
docker volume inspect my-volume
# Mountpoint フィールドに実際のパスが表示される

# macOS/WindowsでDocker VM内にアクセスする場合
docker run --rm -it --privileged --pid=host alpine:latest nsenter -t 1 -m -u -n -i sh
# この後、/var/lib/docker/volumes/ を確認可能

# よりシンプルな方法:一時コンテナでボリュームをマウントしてアクセス
docker run --rm -it -v my-volume:/data alpine:latest sh
# /data ディレクトリ内でボリュームのデータを確認・編集可能

注意点:

  • macOS/WindowsのDocker Desktopでは、ボリュームはDocker VM内に保存される
  • ホストから直接アクセスするのは推奨されない
  • ボリューム内のデータにアクセスしたい場合は、コンテナ経由でアクセスすることを推奨

本番環境でのボリューム活用

データ永続化の重要性:

# 開発環境
docker run -v $(pwd)/data:/app/data myapp

# 本番環境(Docker Compose例)
version: '3.8'
services:
  web:
    image: myapp
    volumes:
      - app-data:/app/data
      - /opt/config:/app/config  # ホストの設定ファイル
  
  db:
    image: postgres
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  app-data:     # 名前付きボリューム(Dockerが管理)
  postgres-data: # DBデータの永続化

クラウド環境での考慮点:

  • AWS: EBS Volume, EFS
  • GCP: Persistent Disk
  • Azure: Azure Disk
  • Kubernetes: PersistentVolume (PV) / PersistentVolumeClaim (PVC)

よく使うコマンド

基本操作

# コンテナ一覧
docker ps                             # 実行中のコンテナ
docker ps -a                          # すべてのコンテナ

# イメージ一覧
docker images

# コンテナ起動
docker run <image_name>

# コンテナ停止
docker stop <container_name_or_id>

# コンテナ削除
docker rm <container_name_or_id>

実行中のコンテナに入る

# 基本的な方法(bashで入る)
docker exec -it <container_name_or_id> /bin/bash

# shで入る(bashが使えない場合)
docker exec -it <container_name_or_id> /bin/sh

# 特定のユーザーで入る
docker exec -it --user root <container_name_or_id> /bin/bash

# 作業ディレクトリを指定して入る
docker exec -it --workdir /app <container_name_or_id> /bin/bash

# 一時的にコマンドを実行(コンテナに入らない)
docker exec <container_name_or_id> ls -la
docker exec <container_name_or_id> cat /etc/os-release

ボリューム関連コマンド

docker volume create <volume_name>    # ボリューム作成
docker volume ls                      # ボリューム一覧
docker volume rm <volume_name>        # ボリューム削除
docker volume prune                   # 未使用ボリューム削除

Dockerfile

マルチステージビルド

マルチステージビルドとは

1つのDockerfile内で複数のビルドステージを定義し、最終的なイメージを軽量化する手法。

典型的な構成(静的サイトジェネレーターの場合)

# ステージ1: ビルドステージ(Node.jsが必要)
FROM node:24-alpine AS builder

WORKDIR /app

# 依存関係のインストール
COPY package*.json ./
RUN npm ci

# ソースコードのコピー
COPY . .

# ビルド(静的ファイルを生成)
RUN npm run build

# ステージ2: 実行ステージ(Node.jsは不要)
FROM nginx:alpine

# ビルド済みの静的ファイルをコピー
COPY --from=builder /app/dist /usr/share/nginx/html

# Nginx設定
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

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

なぜ2番目のステージでNode.jsが不要なのか

重要なポイント:

  1. ビルドは完了している

    • 1番目のステージ(builder)でnpm run buildを実行し、静的ファイル(HTML、CSS、JS)をdist/ディレクトリに生成済み
    • 2番目のステージでは、このビルド済みファイルをコピーするだけ
  2. 静的ファイルのみを配信

    • Astroなどの静的サイトジェネレーターは、ビルド時にすべてのページをHTMLに変換
    • 実行時にはサーバーサイド処理が不要(Node.jsランタイム不要)
    • Nginxは静的ファイルを配信するだけのWebサーバー
  3. イメージサイズの最適化

    • Node.jsを含めないことで、最終イメージが大幅に軽量化される
    • node:24-alpineは約200MB、nginx:alpineは約40MB
    • 約160MBの削減効果
  4. セキュリティ面のメリット

    • Node.jsを含めないことで、攻撃面が減る
    • 必要最小限のコンポーネントのみを含める

マルチステージビルドの利点まとめ

  • 軽量化: 最終イメージにビルドツールを含めない
  • セキュリティ: 不要なコンポーネントを除外
  • パフォーマンス: 軽量なイメージで起動が速い
  • 明確な分離: ビルド環境と実行環境を分離

実際の使用例

# ビルド(マルチステージビルド)
docker build -t myapp:latest .

# 実行(Nginxのみで動作)
docker run -p 8080:80 myapp:latest

# イメージサイズの確認
docker images myapp:latest
# → Node.jsを含まない軽量なイメージ

Docker Compose

マルチサービス構成の基本

app + web の一般的な構成パターン

典型的な構成:

  • web: リバースプロキシ(Nginx など)- 静的ファイル配信、SSL終端、リクエスト振り分け
  • app: アプリケーションサーバー(Node.js、Python、PHP など)- ビジネスロジック処理

なぜ分けるのか?

  • 責任分離: 静的ファイル配信とアプリロジックを分離
  • パフォーマンス: Nginxが静的ファイルを高速配信、アプリは動的処理に専念
  • スケーラビリティ: webとappを独立してスケール可能
  • セキュリティ: 外部からの直接アクセスはwebのみ、appは内部ネットワークで保護
version: '3.8'
services:
  # Webサーバー(フロントエンド)
  web:
    image: nginx:latest
    restart: unless-stopped
    ports:
      - "80:80"      # 外部からのアクセスはwebのみ
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - static-files:/usr/share/nginx/html
    depends_on:
      - app          # appサービスが起動してからwebを起動
    networks:
      - frontend     # 外部ネットワーク
      - backend      # 内部ネットワーク(appとの通信用)

  # アプリケーションサーバー(バックエンド)
  app:
    image: node:16-alpine
    restart: unless-stopped
    # ポート公開なし(外部から直接アクセス不可)
    expose:
      - "3000"       # 内部ネットワークでのみアクセス可能
    environment:
      - NODE_ENV=production
    volumes:
      - app-data:/app/data
      - static-files:/app/public  # 静的ファイルをwebと共有
    networks:
      - backend      # 内部ネットワークのみ
      - db-network   # データベースとの通信用

  # データベース
  db:
    image: postgres:13
    restart: unless-stopped
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - db-network   # アプリとの通信専用
    secrets:
      - db_password

# ネットワーク定義(サービス間通信の設定)
networks:
  frontend:        # 外部アクセス用
    driver: bridge
  backend:         # web ↔ app 通信用
    driver: bridge
    internal: true # 外部からアクセス不可
  db-network:      # app ↔ db 通信用
    driver: bridge
    internal: true # 外部からアクセス不可

volumes:
  app-data:
  postgres-data:
  static-files:    # webとappで共有

secrets:
  db_password:
    external: true

サービス間通信の仕組み

1. ネットワークレベルでの通信制御

networks:
  backend:
    driver: bridge
    internal: true  # 外部からアクセス不可、内部通信のみ

2. サービス名での名前解決

  • Docker Compose内ではサービス名がホスト名として機能
  • 例:webサービスからappサービスへは http://app:3000 でアクセス

3. Nginxの設定例(web → app への通信)

# nginx.conf
server {
    listen 80;
    server_name localhost;

    # 静的ファイル(CSS、JS、画像など)
    location /static/ {
        alias /usr/share/nginx/html/;
        expires 30d;
    }

    # API リクエストをappサービスにプロキシ
    location /api/ {
        proxy_pass http://app:3000/;  # サービス名'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;
    }

    # その他のリクエストもappにプロキシ
    location / {
        proxy_pass http://app:3000/;  # サービス名'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;
    }
}

4. 通信フロー

外部クライアント → web:80/443 → app:3000 → db:5432
               ↑               ↑           ↑
           外部公開          内部通信      内部通信

5. 依存関係とヘルスチェック

services:
  web:
    depends_on:
      - app        # app起動後にweb起動
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]

  app:
    depends_on:
      - db         # db起動後にapp起動
    healthcheck:
      test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]

  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]

実際の運用での確認方法

サービス間通信のテスト:

# webコンテナからappへの通信確認
docker-compose exec web curl http://app:3000/health

# appコンテナからdbへの通信確認
docker-compose exec app nc -zv db 5432

# ネットワーク情報確認
docker network ls
docker network inspect <project_name>_backend

# サービス間のネットワーク接続確認
docker-compose exec web nslookup app

ログでの通信確認:

# 全サービスのログ
docker-compose logs -f

# 特定サービスのログ
docker-compose logs -f web
docker-compose logs -f app

# リアルタイムでの通信監視
docker-compose exec web tail -f /var/log/nginx/access.log

本番環境での重要な設定

命名規則

多くのオープンソースプロジェクトではケバブケース(kebab-case)が使用される

# 推奨:ケバブケース(kebab-case)
services:
  web-app:        # ハイフンで区切る
    image: nginx
  api-server:     # 複数単語もハイフンで繋ぐ
    image: node
  worker-queue:
    image: redis

volumes:
  app-data:       # ボリュームもケバブケース
  log-files:
  backup-storage:

networks:
  frontend-net:   # ネットワークもケバブケース
  backend-net:

理由:

  • 可読性: 単語の区切りが明確
  • 一貫性: Kubernetesなど他のツールとの統一
  • 標準的: 多くのOSSプロジェクトで採用
  • URL安全: ハイフンはURL内で安全に使用可能

restart ポリシー

restart: unless-stopped は本番環境では必須の設定

restart ポリシーの種類:

  • no: 再起動しない(デフォルト)
  • always: 常に再起動(Docker起動時も含む)
  • on-failure: 異常終了時のみ再起動
  • unless-stopped: 手動で停止するまで再起動(推奨
# 本番環境での推奨設定例
version: '3.8'
services:
  web:
    image: nginx:latest
    restart: unless-stopped  # 重要:自動復旧設定
    ports:
      - "80:80"
    volumes:
      - web-data:/usr/share/nginx/html
      - /opt/config/nginx:/etc/nginx/conf.d:ro
    depends_on:
      - app
  
  app:
    image: myapp:latest
    restart: unless-stopped  # 重要:自動復旧設定
    environment:
      - NODE_ENV=production
    volumes:
      - app-data:/app/data
    depends_on:
      - db
  
  db:
    image: postgres:13
    restart: unless-stopped  # 重要:自動復旧設定
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    secrets:
      - db_password

volumes:
  web-data:
  app-data:
  postgres-data:

secrets:
  db_password:
    external: true

なぜ unless-stopped が推奨なのか?

メリット:

  • サーバー再起動時: 自動でコンテナが起動
  • コンテナクラッシュ時: 自動で復旧
  • 手動制御可能: docker stop で明示的に停止可能
  • 意図しない起動回避: 手動停止したコンテナは起動時に自動起動しない

always との違い:

# unless-stopped の場合
docker stop my-container  # 手動停止
# → サーバー再起動時も起動しない

# always の場合  
docker stop my-container  # 手動停止
# → サーバー再起動時に自動起動してしまう

本番環境でのその他重要設定

services:
  app:
    image: myapp:latest
    restart: unless-stopped
    
    # リソース制限(重要)
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          memory: 256M
    
    # ヘルスチェック(重要)
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    
    # ログ設定
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    
    # セキュリティ設定
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp

ヘルスチェックの詳細設定

ヘルスチェックとは:

  • コンテナの健全性を定期的に監視する仕組み
  • サービスが正常に動作しているかを自動確認
  • 異常時の自動復旧やロードバランサーからの除外に活用

設定パラメータ:

  • test: 実行するヘルスチェックコマンド
  • interval: チェック間隔(デフォルト: 30s)
  • timeout: タイムアウト時間(デフォルト: 30s)
  • retries: 失敗の許容回数(デフォルト: 3)
  • start_period: 初回チェックまでの猶予時間(デフォルト: 0s)

サービス別ヘルスチェック例:

services:
  # Webアプリケーション
  web:
    image: nginx:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  # MySQL データベース
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secretpassword
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-uroot", "-p$MYSQL_ROOT_PASSWORD"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 60s

  # PostgreSQL データベース
  postgres:
    image: postgres:13
    environment:
      POSTGRES_PASSWORD: secretpassword
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis
  redis:
    image: redis:6-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 30s
      timeout: 3s
      retries: 3

  # Node.js API
  api:
    image: node:16-alpine
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

ヘルスチェック状態の確認:

# コンテナの健康状態確認
docker ps

# 詳細なヘルスチェック履歴
docker inspect <container_name> | grep -A 20 "Health"

# Docker Composeでの確認
docker-compose ps

ヘルスチェックのベストプラクティス:

  • 軽量なチェック: 過度に重い処理は避ける
  • 適切な間隔: あまり頻繁すぎるとリソース消費が大きい
  • 依存関係を考慮: DBが起動してからAPIをチェック
  • 適切なタイムアウト: ネットワーク遅延を考慮した設定

実際の運用例:

# 本番環境での起動
docker compose -f compose.yaml -f compose.production.yaml up -d

# サービス状態確認
docker-compose ps

# ログ確認
docker-compose logs -f --tail=100

# 特定サービスの再起動
docker-compose restart app

# 設定変更後の再デプロイ
docker-compose pull
docker-compose up -d

Docker Composeの停止・削除

# 基本的な停止(コンテナを停止、削除しない)
docker-compose stop

# 停止と削除(コンテナとネットワークを削除)
docker-compose down

# 特定のサービスのみ停止
docker-compose stop web

# ボリュームも含めて完全削除
docker-compose down -v

# イメージも削除(開発環境のクリーンアップ)
docker-compose down --rmi all

# 孤立したコンテナも削除
docker-compose down --remove-orphans

# 強制停止(緊急時)
docker-compose kill

# プロダクションファイル指定での停止
docker compose -f compose.yaml -f compose.production.yaml down

stopとdownの違い:

  • stop: コンテナを停止するが、コンテナとネットワークは残る
  • down: コンテナを停止し、コンテナとネットワークを削除する

本番環境での推奨手順:

# 1. まず状態確認
docker-compose ps

# 2. ログを確認(必要に応じて)
docker-compose logs

# 3. グレースフルシャットダウン
docker-compose down

# 4. 完全停止の確認
docker ps -a | grep <project_name>

Dangling(ダングリング)リソース

Danglingとは

「ぶら下がっている」という意味で、参照されなくなった孤立したリソースを指す。ディスク容量を無駄に消費するため、定期的にクリーンアップが必要。

Dangling Images(ダングリングイメージ)

  • 同じタグで新しいイメージをビルドした時、古いイメージがタグを失う
  • docker buildで同じタグを何度も使用した場合に発生
# ダングリングイメージの確認
docker images -f dangling=true

# ダングリングイメージの削除
docker image prune
docker image prune -f  # 確認なしで削除

Dangling Volumes(ダングリングボリューム)

  • コンテナ削除後も残ったボリューム
  • 明示的に削除しない限り残り続ける
# ダングリングボリュームの確認
docker volume ls -f dangling=true

# ダングリングボリュームの削除
docker volume prune
docker volume prune -f  # 確認なしで削除

全体的なクリーンアップ

# すべての未使用リソースを確認
docker system df

# すべての未使用リソースを削除
docker system prune

# より積極的なクリーンアップ(未使用イメージも含む)
docker system prune -a

# 未使用ネットワークの削除
docker network prune

トラブルシューティング

参考リンク