1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【完全保存版】Docker入門から本番デプロイまで

1
Posted at

この記事について

Docker初心者が実務で困らないレベル(中級者の入り口)まで到達できる、完全学習ロードマップです。

📚 含まれる内容

  • Dockerの基礎からDockerfile詳細解説まで
  • ボリューム・ネットワークの実践的な使い方
  • Docker Composeによる複数コンテナ管理
  • トラブルシューティング集
  • AWS ECS/ECRへの移行ガイド

🎯 対象読者

  • Dockerを基礎から学びたい方
  • Dockerfileの書き方を詳しく知りたい方
  • 開発環境をDockerで構築したい方
  • AWS ECS/ECRへの移行を考えている方

⏱️ 想定学習時間

全体で20〜30時間(実践含む)

目次

  1. Dockerの基礎知識
  2. Dockerの基本操作
  3. Dockerfileを書く
  4. ボリュームとデータ管理
  5. ネットワーク
  6. Docker Compose
  7. 実践的な開発環境構築
  8. デバッグとトラブルシューティング
  9. デプロイの基礎
  10. 付録

第1章:Dockerの基礎知識

1.1 Dockerとは何か

Dockerは、アプリケーションとその実行環境を「コンテナ」という単位でパッケージ化し、どこでも同じように動かせる技術です。

Dockerが解決する問題:

問題 Docker以前 Dockerを使うと
環境の違い 「自分のPCでは動くのに...」 どの環境でも同じように動く
セットアップの手間 毎回手動でインストール コンテナ起動だけでOK
環境の汚染 システムに直接インストール コンテナ内に隔離される
チーム開発 環境構築手順書が必要 Dockerfileを共有するだけ

1.2 コンテナと仮想マシンの違い

【仮想マシン】
┌─────────────────┐
│   アプリA        │
│   ゲストOS       │
├─────────────────┤
│   アプリB        │
│   ゲストOS       │
├─────────────────┤
│  ハイパーバイザー │
├─────────────────┤
│   ホストOS       │
└─────────────────┘

【Docker コンテナ】
┌──────┬──────┐
│アプリA│アプリB│
├──────┴──────┤
│ Docker Engine │
├──────────────┤
│   ホストOS    │
└──────────────┘
項目 仮想マシン Dockerコンテナ
起動速度 数分 数秒
リソース消費 大きい(OS丸ごと) 小さい(必要な部分のみ)
隔離レベル 完全に独立 プロセスレベルで隔離
用途 異なるOS環境の実行 アプリの軽量な実行環境

1.3 イメージとコンテナの関係

イメージ = 設計図(クラス)

  • アプリケーションと実行環境をパッケージ化したもの
  • 読み取り専用
  • 再利用可能

コンテナ = 実体(インスタンス)

  • イメージから作成される実行中のプロセス
  • 書き込み可能
  • 使い捨て可能
イメージ(ubuntu:20.04)
    ↓ docker run
コンテナA(実行中)
コンテナB(実行中)
コンテナC(停止中)

重要な概念:
1つのイメージから複数のコンテナを作成できる

1.4 Dockerのインストール(Windows)

インストール手順

1. システム要件の確認

項目 要件
OS Windows 10/11 Pro, Enterprise, Education(64bit)
CPU 仮想化対応(BIOSで有効化)
メモリ 4GB以上推奨
WSL2 有効化が必要

2. WSL2のインストール

PowerShellを管理者権限で開き、以下を実行:

# WSLを有効化
wsl --install

# 再起動後、WSL2をデフォルトに設定
wsl --set-default-version 2

3. Docker Desktopのインストール

  1. Docker公式サイトからインストーラーをダウンロード
  2. インストーラーを実行し、指示に従う
  3. 「Use WSL 2 instead of Hyper-V」にチェック
  4. インストール完了後、再起動

4. インストール確認

コマンドプロンプトまたはPowerShellで確認:

docker --version
# 出力例: Docker version 24.0.0, build abc1234

docker run hello-world
# 正常に動作すれば成功メッセージが表示される

トラブルシューティング

エラー 原因 解決方法
WSL2が起動しない 仮想化が無効 BIOSで仮想化を有効化
Docker Desktopが起動しない Hyper-Vの競合 WSL2モードで再インストール
コマンドが認識されない パスが通っていない Docker Desktopを再起動

Windows使用時の重要な注意点

1. パスの表記

Dockerコマンドでホストのパスを指定する際、環境によって表記が異なります:

環境 カレントディレクトリ 絶対パス
PowerShell ${PWD} または $(pwd) C:\Users\username\project
コマンドプロンプト %cd% C:\Users\username\project
Git Bash $(pwd) /c/Users/username/project

推奨: PowerShellまたはGit Bashの使用

2. 改行コードの問題

Windowsでは改行コードがCRLFですが、Linux/DockerではLFが必要です。

# Gitの自動変換を無効化(推奨)
git config --global core.autocrlf false

# シェルスクリプト(.shファイル)は必ずLF形式で保存

エディタ設定:

  • VS Code: 右下のステータスバーで CRLFLF に変更
  • Vim: :set fileformat=unix

3. ファイル共有設定

バインドマウント(-vオプション)を使用する際は、Docker Desktopで共有設定が必要です:

  1. Docker Desktop → Settings → Resources → File Sharing
  2. マウントするドライブ(C:, D:など)にチェック
  3. Apply & Restart

4. パフォーマンス

  • WSL2を使用すると、Hyper-Vより高速
  • ソースコードはWSL2のファイルシステムに配置すると最速
  • Windowsファイルシステム(/mnt/c/)からのマウントは遅い

5. シェルスクリプト実行時の注意

# Windowsで作成したスクリプトは実行権限がない場合がある
chmod +x script.sh

# または、Docker内で実行
docker run --rm -v ${PWD}:/work alpine sh /work/script.sh

第2章:Dockerの基本操作

2.1 最初の一歩:Hello Worldコンテナを動かす

docker run hello-world

このコマンドで何が起こっているか:

  1. ローカルにhello-worldイメージがあるか確認
  2. なければDocker Hubから自動ダウンロード
  3. イメージからコンテナを作成
  4. コンテナを起動してメッセージを表示
  5. コンテナが自動的に停止

2.2 基本コマンド一覧と使い方

イメージ関連コマンド

コマンド 説明
docker pull イメージをダウンロード docker pull ubuntu:20.04
docker images ローカルのイメージ一覧 docker images
docker rmi イメージを削除 docker rmi ubuntu:20.04
docker build Dockerfileからイメージ作成 docker build -t myapp .

コンテナ関連コマンド

コマンド 説明
docker run コンテナを作成して起動 docker run -d -p 8080:80 nginx
docker ps 実行中のコンテナ一覧 docker ps
docker ps -a すべてのコンテナ一覧 docker ps -a
docker stop コンテナを停止 docker stop <コンテナID>
docker start 停止中のコンテナを起動 docker start <コンテナID>
docker restart コンテナを再起動 docker restart <コンテナID>
docker rm コンテナを削除 docker rm <コンテナID>
docker exec 実行中のコンテナでコマンド実行 docker exec -it <コンテナID> bash
docker logs コンテナのログを表示 docker logs <コンテナID>

docker runの主要オプション

オプション 説明
-d バックグラウンドで実行(デタッチモード) docker run -d nginx
-it 対話モードで実行 docker run -it ubuntu bash
-p ポートマッピング(ホスト:コンテナ) docker run -p 8080:80 nginx
--name コンテナに名前をつける docker run --name web nginx
-v ボリュームマウント docker run -v /host:/container nginx
-e 環境変数を設定 docker run -e API_KEY=xxx myapp
--rm 停止時に自動削除 docker run --rm ubuntu echo hello

2.3 コンテナのライフサイクル

┌──────────┐
│ イメージ  │
└─────┬────┘
      │ docker run
      ↓
┌──────────┐   docker stop    ┌──────────┐
│  実行中   │ ───────────────→│  停止中   │
└─────┬────┘                  └─────┬────┘
      ↑                             │
      └─────── docker start ─────────┘
                                    │
                                    │ docker rm
                                    ↓
                                 (削除)

状態の確認:

# 実行中のコンテナのみ表示
docker ps

# すべてのコンテナを表示(停止中も含む)
docker ps -a

# 出力例:
# CONTAINER ID   IMAGE     COMMAND   CREATED        STATUS                    NAMES
# abc123def456   nginx     "nginx"   2 hours ago    Up 2 hours                web
# 789ghi012jkl   ubuntu    "bash"    3 hours ago    Exited (0) 3 hours ago    my-ubuntu

2.4 コンテナの中に入る・ログを見る

実行中のコンテナに入る

新規コンテナを起動して中に入る:

# Ubuntuコンテナを起動してbashを実行
docker run -it ubuntu:20.04 bash

# コンテナ内で操作可能
root@abc123:/# ls
root@abc123:/# pwd
root@abc123:/# exit  # 抜ける(コンテナも停止)

既に起動中のコンテナに入る:

# まずコンテナIDまたは名前を確認
docker ps

# execコマンドで接続
docker exec -it <コンテナIDまたは名前> bash

# 例:
docker exec -it web bash

# コンテナ内で操作可能
root@def456:/# ls
root@def456:/# exit  # 抜ける(コンテナは起動したまま)

runとexecの違い:

コマンド 用途 exitした後
docker run -it 新しいコンテナを起動して入る コンテナが停止する
docker exec -it 既存の起動中コンテナに入る コンテナは起動したまま

停止したコンテナを再起動して入る

# 停止中のコンテナを確認
docker ps -a

# コンテナを再起動
docker start <コンテナID>

# 再起動したコンテナに入る
docker exec -it <コンテナID> bash

ログを確認する

# 最新のログを表示
docker logs <コンテナID>

# リアルタイムでログを追跡(tail -f のように)
docker logs -f <コンテナID>

# 最後の100行のみ表示
docker logs --tail 100 <コンテナID>

# タイムスタンプ付きで表示
docker logs -t <コンテナID>

例:Nginxのアクセスログを確認

# Nginxコンテナを起動
docker run -d --name web -p 8080:80 nginx

# ブラウザで http://localhost:8080 にアクセス

# ログを確認
docker logs web
# 出力例:
# 172.17.0.1 - - [15/Nov/2025:10:30:45 +0000] "GET / HTTP/1.1" 200 615

コンテナ内でのデバッグ作業

# コンテナに入る
docker exec -it web bash

# パッケージをインストール(一時的に)
apt-get update
apt-get install -y curl vim

# ファイルを確認
cat /etc/nginx/nginx.conf

# プロセスを確認
ps aux

# ネットワークを確認
curl localhost

# 抜ける
exit

注意: コンテナ内での変更は、コンテナを削除すると失われます。永続的な変更はDockerfileに記述します。


第3章:Dockerfileを書く

3.1 Dockerfileとは

Dockerfileは、Dockerイメージを作成するための設計書です。テキストファイルに命令を記述することで、自動的にイメージをビルドできます。

Dockerfileの役割:

  • 環境構築手順を自動化
  • イメージの作成プロセスを記録
  • チームメンバーと環境を共有

基本的な流れ:

Dockerfile(設計書)
    ↓ docker build
イメージ(完成品)
    ↓ docker run
コンテナ(実行環境)

3.2 Dockerfileの基本構造

最小限のDockerfile例:

# ベースイメージを指定
FROM ubuntu:20.04

# コマンドを実行
RUN apt-get update && apt-get install -y nginx

# コンテナ起動時のコマンド
CMD ["nginx", "-g", "daemon off;"]

ビルドと実行:

# イメージをビルド
docker build -t my-nginx .

# コンテナを起動
docker run -d -p 8080:80 my-nginx

3.3 Dockerfile命令の詳細解説

このセクションがDockerfile理解の核心です。各命令を詳しく見ていきます。


FROM - ベースイメージの指定

役割: Dockerイメージの土台となるベースイメージを指定します。すべてのDockerfileはFROMから始まります。

基本構文:

FROM <イメージ名>:<タグ>

使用例と解説:

# 公式のNode.js 18イメージを使用
FROM node:18

# 特定のバージョンを明示
FROM python:3.11-slim

# アルパインLinux版(軽量)
FROM node:18-alpine

# 複数のFROMを使う(マルチステージビルド)
FROM node:18 AS builder
# ... ビルド処理 ...
FROM node:18-slim AS production

タグの選び方:

タグ 説明 使用場面
node:18 最新の18系 開発環境
node:18.16.0 特定バージョン 本番環境(再現性重視)
node:18-alpine Alpine Linux版(軽量) 本番環境(サイズ重視)
node:18-slim 最小限のパッケージ 本番環境(バランス型)

重要なポイント:

  • latestタグは避ける(バージョンが変わると動作が変わる可能性)
  • 本番環境では具体的なバージョンを指定
  • Alpine版はサイズは小さいが、一部ライブラリで問題が出ることがある

RUN - コマンドの実行

役割: イメージビルド時にコマンドを実行します。パッケージのインストールやファイル操作に使用します。

基本構文:

# シェル形式(シェルを介して実行)
RUN <コマンド>

# exec形式(シェルなしで実行)
RUN ["実行ファイル", "引数1", "引数2"]

使用例と解説:

# 単一のコマンド
RUN apt-get update

# 複数のコマンドを&&で繋ぐ(推奨)
RUN apt-get update && apt-get install -y \
    curl \
    vim \
    git

# パイプを使う
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -

# exec形式
RUN ["apt-get", "update"]

ベストプラクティス:

悪い例(レイヤーが増える):

RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN apt-get install -y git

良い例(1つのレイヤーにまとめる):

RUN apt-get update && apt-get install -y \
    curl \
    vim \
    git \
    && rm -rf /var/lib/apt/lists/*

各OSでのパッケージインストール:

OS コマンド例
Ubuntu/Debian RUN apt-get update && apt-get install -y <パッケージ>
Alpine RUN apk add --no-cache <パッケージ>
CentOS/RHEL RUN yum install -y <パッケージ>

キャッシュの削除:

# Ubuntuの場合
RUN apt-get update && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*

# Alpineの場合(--no-cacheオプションでキャッシュを作らない)
RUN apk add --no-cache curl

WORKDIR - 作業ディレクトリの設定

役割: 以降の命令で使用する作業ディレクトリを設定します。cdコマンドに相当します。

基本構文:

WORKDIR <ディレクトリパス>

使用例と解説:

# 作業ディレクトリを設定(存在しなければ自動作成)
WORKDIR /app

# この後のRUN, COPY, CMDなどは /app ディレクトリで実行される
COPY . .
RUN npm install

# 相対パスも使える(現在のWORKDIRからの相対パス)
WORKDIR src
# 現在は /app/src にいる

# 絶対パスで上書き
WORKDIR /opt/myapp

WORKDIRを使わない場合の問題:

悪い例:

RUN cd /app && npm install
RUN cd /app && npm run build
# 各RUNで毎回cdが必要

良い例:

WORKDIR /app
RUN npm install
RUN npm run build
# 一度WORKDIRを設定すれば、以降はそのディレクトリで実行

重要なポイント:

  • ディレクトリが存在しない場合は自動的に作成される
  • 相対パスは前のWORKDIRからの相対
  • コンテナ起動時のデフォルトディレクトリにもなる

COPY と ADD - ファイルのコピー

役割: ホストのファイルをコンテナにコピーします。

基本構文:

COPY <ソース> <宛先>
ADD <ソース> <宛先>

COPYの使用例:

# 単一ファイルをコピー
COPY package.json /app/

# 複数ファイルをコピー
COPY package.json package-lock.json /app/

# ディレクトリごとコピー
COPY ./src /app/src

# カレントディレクトリの全内容をコピー
COPY . /app

# ワイルドカード使用
COPY *.json /app/

COPYとADDの違い:

機能 COPY ADD
ファイルコピー
URL指定 ×
tar自動解凍 ×
推奨度 推奨 特殊な場合のみ

ADDの使用例(特殊なケース):

# tarファイルを自動解凍してコピー
ADD archive.tar.gz /app/

# URLからダウンロード(非推奨:RUN + curlを推奨)
ADD https://example.com/file.txt /app/

ベストプラクティス:

基本的にCOPYを使う:

# シンプルで明示的
COPY package.json /app/

ADDは自動解凍が必要な時だけ:

# tarファイルを展開したい場合
ADD app.tar.gz /app/

ADDでURLダウンロードは避ける:

# 悪い例
ADD https://example.com/file.txt /app/

# 良い例(キャッシュの削除も含む)
RUN curl -o /app/file.txt https://example.com/file.txt

COPYの注意点:

# .dockerignoreファイルで除外対象を指定
# .dockerignore の内容例:
# node_modules
# .git
# *.log

# カレントディレクトリをコピー(除外対象以外)
COPY . /app

ENV - 環境変数の設定

役割: コンテナ内で使用する環境変数を設定します。ビルド時と実行時の両方で有効です。

基本構文:

ENV <キー>=<値>
ENV <キー1>=<値1> <キー2>=<値2>

使用例と解説:

# 単一の環境変数
ENV NODE_ENV=production

# 複数の環境変数
ENV APP_HOME=/app \
    APP_PORT=3000 \
    LOG_LEVEL=info

# 環境変数を別の命令で使用
ENV APP_DIR=/opt/myapp
WORKDIR $APP_DIR
COPY . $APP_DIR

よく使われる環境変数:

変数名 用途
NODE_ENV Node.jsの実行モード production, development
PYTHONUNBUFFERED Pythonの出力バッファリング 1 (無効化)
TZ タイムゾーン Asia/Tokyo
LANG ロケール ja_JP.UTF-8
PATH 実行パス $PATH:/app/bin

実用例:

FROM node:18

# 環境変数の設定
ENV NODE_ENV=production \
    APP_PORT=3000 \
    TZ=Asia/Tokyo

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

# 環境変数を使用
EXPOSE $APP_PORT

CMD ["node", "server.js"]

ENVとARGの違い:

項目 ENV ARG
使用時期 ビルド時 + 実行時 ビルド時のみ
docker run での上書き -e で可能 不可
用途 アプリの設定 ビルド時の設定

ARG - ビルド時の変数

役割: イメージのビルド時のみ使用できる変数を定義します。実行時には存在しません。

基本構文:

ARG <変数名>
ARG <変数名>=<デフォルト値>

使用例と解説:

# デフォルト値なし
ARG VERSION

# デフォルト値あり
ARG NODE_VERSION=18
ARG BUILD_ENV=development

# ARGを使ってベースイメージを指定
FROM node:${NODE_VERSION}

# ARGをビルド中に使用
RUN echo "Building version: ${VERSION}"
RUN echo "Build environment: ${BUILD_ENV}"

ビルド時に値を渡す:

# --build-argで値を指定
docker build --build-arg VERSION=1.0.0 --build-arg BUILD_ENV=production -t myapp .

ENVとARGの組み合わせ:

# ビルド時の変数
ARG NODE_VERSION=18
ARG BUILD_DATE

# ベースイメージに使用
FROM node:${NODE_VERSION}

# ビルド情報をENVに変換(実行時にも参照可能)
ENV BUILD_DATE=${BUILD_DATE}

RUN echo "Built on: ${BUILD_DATE}"

ARGのスコープ:

# FROMの前のARG(特殊なケース)
ARG BASE_IMAGE=node:18
FROM ${BASE_IMAGE}

# FROMの後のARGは再宣言が必要
ARG BASE_IMAGE
RUN echo "Base image: ${BASE_IMAGE}"

セキュリティ上の注意:

機密情報をARGに渡さない:

# 悪い例:ビルド履歴に残る
ARG DATABASE_PASSWORD=secret123
ENV DB_PASS=${DATABASE_PASSWORD}

機密情報は実行時に渡す:

# 実行時に環境変数で渡す
docker run -e DATABASE_PASSWORD=secret123 myapp

EXPOSE - ポートの宣言

役割: コンテナがリッスンするポートを文書化します。実際のポートマッピングは行いません。

基本構文:

EXPOSE <ポート番号>[/プロトコル]

使用例と解説:

# HTTPポートを公開
EXPOSE 80

# 複数のポートを公開
EXPOSE 80 443

# プロトコルを明示
EXPOSE 8080/tcp
EXPOSE 53/udp

# 環境変数を使用
ENV APP_PORT=3000
EXPOSE $APP_PORT

重要な誤解:

EXPOSEだけではポートにアクセスできない:

FROM node:18
WORKDIR /app
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
# これだけではホストからアクセスできない

docker runで-pオプションが必要:

# ホストの8080番をコンテナの3000番にマッピング
docker run -p 8080:3000 myapp

# ブラウザで http://localhost:8080 にアクセス可能

EXPOSEの目的:

目的 説明
ドキュメント化 どのポートを使うか明示
docker-composeでの自動マッピング ports:で指定しやすくなる
-Pオプションでの自動公開 docker run -Pで自動マッピング

-Pオプションの使用例:

# EXPOSEしたポートを自動的にランダムなホストポートにマッピング
docker run -P myapp

# どのポートにマッピングされたか確認
docker ps
# 0.0.0.0:32768->3000/tcp のように表示される

CMD と ENTRYPOINT - 起動コマンドの指定

これらは、コンテナ起動時に実行するコマンドを指定する命令です。違いが分かりにくいので、詳しく解説します。

基本的な違い:

項目 CMD ENTRYPOINT
役割 デフォルトコマンド 必ず実行されるコマンド
docker run での上書き 簡単に上書きされる 上書きされない(引数として追加)
用途 デフォルト動作を提供 コンテナを実行ファイルのように扱う

CMD - デフォルトコマンド

役割: コンテナ起動時のデフォルトコマンドを指定します。docker runでコマンドを指定すると上書きされます。

3つの記法:

# 1. exec形式(推奨)
CMD ["実行ファイル", "引数1", "引数2"]

# 2. shell形式
CMD コマンド 引数1 引数2

# 3. ENTRYPOINTのパラメータとして(後述)
CMD ["引数1", "引数2"]

使用例:

# exec形式(推奨)
CMD ["node", "server.js"]
CMD ["python", "app.py"]
CMD ["nginx", "-g", "daemon off;"]

# shell形式(/bin/sh -c で実行される)
CMD node server.js
CMD python app.py

exec形式とshell形式の違い:

項目 exec形式 shell形式
実行方法 直接実行 /bin/sh -c経由
PID 1 シェルが1、コマンドは別
シグナル処理 正しく処理される 処理されない場合がある
環境変数展開 されない される

実用例:

FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000

# デフォルトでサーバーを起動
CMD ["node", "server.js"]

docker runでの上書き:

# CMDで指定したコマンドを実行
docker run myapp
# → node server.js が実行される

# CMDを上書き
docker run myapp node debug.js
# → node debug.js が実行される

docker run myapp bash
# → bash が実行される(対話モードでコンテナに入る)

ENTRYPOINT - 必ず実行されるコマンド

役割: コンテナを実行ファイルのように扱いたい場合に使用します。docker runで指定した引数がパラメータとして追加されます。

基本構文:

# exec形式(推奨)
ENTRYPOINT ["実行ファイル", "固定引数"]

# shell形式
ENTRYPOINT コマンド

使用例:

# curlコマンドを実行するコンテナ
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y curl
ENTRYPOINT ["curl"]

実行例:

# docker runの引数がcurlの引数になる
docker run mycurl https://example.com
# → curl https://example.com が実行される

docker run mycurl -I https://example.com
# → curl -I https://example.com が実行される

ENTRYPOINTの上書き:

# --entrypointオプションで上書き可能
docker run --entrypoint bash mycurl

CMD と ENTRYPOINT の組み合わせ

最も柔軟な使い方は、両方を組み合わせることです。

パターン1:ENTRYPOINTで固定部分、CMDでデフォルト引数

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y curl

# 固定部分
ENTRYPOINT ["curl"]

# デフォルト引数
CMD ["https://example.com"]

実行パターン:

# デフォルト引数を使用
docker run mycurl
# → curl https://example.com

# 引数を上書き
docker run mycurl https://google.com
# → curl https://google.com

# オプション付き
docker run mycurl -I https://google.com
# → curl -I https://google.com

パターン2:アプリケーションでの実用例

FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

# 必ず node を実行
ENTRYPOINT ["node"]

# デフォルトは server.js
CMD ["server.js"]

実行パターン:

# デフォルトでserver.jsを実行
docker run myapp
# → node server.js

# 別のファイルを実行
docker run myapp debug.js
# → node debug.js

# オプション付き
docker run myapp --inspect server.js
# → node --inspect server.js

パターン3:スクリプトをエントリーポイントにする

FROM python:3.11
WORKDIR /app

# エントリーポイントスクリプトをコピー
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

COPY . .

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["python", "app.py"]

docker-entrypoint.sh の例:

#!/bin/bash
set -e

# 初期化処理
echo "Starting application..."
python manage.py migrate

# CMDで指定されたコマンドを実行
exec "$@"

選択ガイド:

ケース 推奨 理由
Webアプリ(柔軟性必要) CMD のみ 開発時に別コマンドを実行しやすい
ツールコンテナ ENTRYPOINT のみ 実行ファイルのように扱える
初期化が必要なアプリ 両方 柔軟性と確実な初期化を両立

VOLUME - ボリュームのマウントポイント

役割: データの永続化やホストとの共有が必要なディレクトリを宣言します。

基本構文:

VOLUME ["<パス>"]
VOLUME <パス>

使用例:

# データベースのデータディレクトリ
VOLUME ["/var/lib/mysql"]

# 複数のボリューム
VOLUME ["/data", "/logs"]

# 環境変数を使用
ENV DATA_DIR=/app/data
VOLUME $DATA_DIR

実用例:MySQLコンテナ

FROM mysql:8.0

# データを永続化するディレクトリを宣言
VOLUME /var/lib/mysql

# 環境変数
ENV MYSQL_ROOT_PASSWORD=rootpass
ENV MYSQL_DATABASE=myapp

EXPOSE 3306

VOLUMEの効果:

# コンテナを起動(自動的に匿名ボリュームが作成される)
docker run -d --name db mysql:8.0

# ボリュームを確認
docker volume ls
# DRIVER    VOLUME NAME
# local     abc123def456...

# 名前付きボリュームで起動(推奨)
docker run -d --name db -v mydb-data:/var/lib/mysql mysql:8.0

# コンテナを削除してもデータは残る
docker rm -f db
docker volume ls
# mydb-data はまだ存在する

重要なポイント:

  • DockerfileのVOLUMEは宣言のみ(ドキュメント化)
  • 実際のマウントはdocker run -vで行う
  • VOLUMEで宣言されたディレクトリは、ビルド後の変更が保持されない

USER - 実行ユーザーの変更

役割: 以降の命令とコンテナ実行時のユーザーを指定します。セキュリティ向上のために使用します。

基本構文:

USER <ユーザー名またはUID>[:グループ名またはGID]

使用例と解説:

# rootユーザーでビルド作業
FROM node:18

WORKDIR /app

# パッケージインストール(rootが必要)
RUN apt-get update && apt-get install -y curl

# アプリケーションのセットアップ
COPY package*.json ./
RUN npm install

# 一般ユーザーを作成
RUN useradd -m -u 1001 appuser

# ファイルの所有者を変更
RUN chown -R appuser:appuser /app

# ユーザーを切り替え
USER appuser

# 以降は appuser として実行
COPY --chown=appuser:appuser . .

CMD ["node", "server.js"]

なぜUSERを使うのか:

rootで実行 一般ユーザーで実行
コンテナ脱出時にホストのrootに 権限が制限されている
セキュリティリスク大 セキュリティリスク小
本番環境では非推奨 本番環境で推奨

Node.jsの公式イメージを使う場合:

FROM node:18

# node イメージには既に node ユーザーが存在
WORKDIR /app

# 所有者を指定してコピー
COPY --chown=node:node package*.json ./

USER node

RUN npm install

COPY --chown=node:node . .

CMD ["node", "server.js"]

LABEL - メタデータの追加

役割: イメージにメタデータを追加します。バージョン管理や情報の記録に使用します。

基本構文:

LABEL <キー>=<値>

使用例:

FROM node:18

# メタデータを追加
LABEL maintainer="yourname@example.com"
LABEL version="1.0.0"
LABEL description="My awesome application"
LABEL org.opencontainers.image.source="https://github.com/user/repo"

# 複数のラベルを一度に
LABEL version="1.0.0" \
      description="My app" \
      maintainer="yourname@example.com"

WORKDIR /app
COPY . .
CMD ["node", "server.js"]

ラベルの確認:

# イメージのラベルを表示
docker inspect myapp | grep -A 10 Labels

# または
docker image inspect --format='{{json .Config.Labels}}' myapp

3.4 レイヤーとキャッシュの仕組み

Dockerイメージはレイヤー構造になっています。これを理解することで、ビルドを高速化できます。

レイヤーの仕組み

【Dockerイメージのレイヤー構造】

┌─────────────────────┐
│ CMD ["node", "app"] │ ← レイヤー6(最上位)
├─────────────────────┤
│ COPY . /app         │ ← レイヤー5
├─────────────────────┤
│ RUN npm install     │ ← レイヤー4
├─────────────────────┤
│ COPY package.json   │ ← レイヤー3
├─────────────────────┤
│ WORKDIR /app        │ ← レイヤー2
├─────────────────────┤
│ FROM node:18        │ ← レイヤー1(ベース)
└─────────────────────┘

レイヤーの特徴:

  • 各命令(FROM、RUN、COPY等)が1つのレイヤーを作成
  • レイヤーは読み取り専用
  • 変更があったレイヤー以降が再ビルドされる

キャッシュの活用

キャッシュが効く条件:

  • 命令の内容が同じ
  • COPYするファイルの内容が同じ(チェックサムで判定)
  • 前のレイヤーのキャッシュが有効

悪い例(キャッシュが効きにくい):

FROM node:18
WORKDIR /app

# すべてのファイルをコピー
COPY . .

# package.jsonが変わらなくてもapp.jsが変わると再実行される
RUN npm install

CMD ["node", "server.js"]

良い例(キャッシュを最大限活用):

FROM node:18
WORKDIR /app

# 依存関係の定義ファイルのみコピー
COPY package*.json ./

# npm installはpackage.jsonが変わった時だけ再実行
RUN npm install

# アプリケーションコードをコピー
COPY . .

CMD ["node", "server.js"]

効果の比較:

ケース 悪い例 良い例
app.jsのみ変更 npm install再実行(遅い) キャッシュ使用(速い)
package.json変更 npm install再実行 npm install再実行

ビルドキャッシュの確認

# キャッシュを使ってビルド
docker build -t myapp .

# 出力例:
# Step 3/6 : COPY package*.json ./
#  ---> Using cache
#  ---> abc123def456
# Step 4/6 : RUN npm install
#  ---> Using cache  ← キャッシュが使われている
#  ---> 789ghi012jkl

キャッシュを無効にしてビルド:

# 強制的に全体を再ビルド
docker build --no-cache -t myapp .

レイヤーサイズの確認

# イメージのレイヤー情報を表示
docker history myapp

# 出力例:
# IMAGE          CREATED        CREATED BY                                      SIZE
# abc123         2 hours ago    CMD ["node" "server.js"]                        0B
# def456         2 hours ago    COPY . /app                                     5.2MB
# ghi789         2 hours ago    RUN npm install                                 85MB
# jkl012         2 hours ago    COPY package*.json ./                           1.2kB

3.5 Dockerfileのベストプラクティス

1. ベースイメージは最小限に

# ❌ 大きいイメージ
FROM node:18
# サイズ: ~900MB

# ✅ 小さいイメージ
FROM node:18-alpine
# サイズ: ~170MB

# ✅ さらに小さく(マルチステージビルド)
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
# 最終イメージサイズ: ~200MB

2. レイヤーを最小限に

# ❌ レイヤーが多い
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN apt-get install -y git

# ✅ 1つのレイヤーにまとめる
RUN apt-get update && apt-get install -y \
    curl \
    vim \
    git \
    && rm -rf /var/lib/apt/lists/*

3. 変更頻度の低いものを先に

# ✅ 推奨順序
FROM node:18-alpine
WORKDIR /app

# 1. ベース環境(ほぼ変更なし)
RUN apk add --no-cache python3 make g++

# 2. 依存関係(時々変更)
COPY package*.json ./
RUN npm ci --only=production

# 3. アプリコード(頻繁に変更)
COPY . .

CMD ["node", "server.js"]

4. .dockerignoreを活用

.dockerignore の例:

# バージョン管理
.git
.gitignore

# 依存関係
node_modules
npm-debug.log

# ビルド成果物
dist
build
*.log

# 開発用ファイル
.env
.env.local
*.md
Dockerfile
docker-compose.yml

# OS生成ファイル
.DS_Store
Thumbs.db

5. マルチステージビルドを使う

# ステージ1: ビルド
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# ステージ2: 本番環境
FROM node:18-alpine
WORKDIR /app

# 必要なファイルだけコピー
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

メリット:

  • 開発ツール(TypeScript、webpack等)が最終イメージに含まれない
  • イメージサイズが小さくなる
  • セキュリティが向上

6. セキュリティのベストプラクティス

FROM node:18-alpine

# rootユーザーを避ける
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

WORKDIR /app

# 依存関係のインストール
COPY --chown=nodejs:nodejs package*.json ./
RUN npm ci --only=production

# アプリケーションコード
COPY --chown=nodejs:nodejs . .

# 一般ユーザーに切り替え
USER nodejs

EXPOSE 3000
CMD ["node", "server.js"]

3.6 実践例:Node.js Webアプリケーション

完全な例を見ていきます。

ディレクトリ構成:

myapp/
├── Dockerfile
├── .dockerignore
├── package.json
├── package-lock.json
└── src/
    └── server.js

Dockerfile(本番環境用):

# ベースイメージ
FROM node:18-alpine AS base
WORKDIR /app

# 依存関係のインストール用
FROM base AS dependencies
COPY package*.json ./
RUN npm ci --only=production

# ビルド用
FROM base AS build
COPY package*.json ./
RUN npm install
COPY src ./src
RUN npm run build

# 本番環境用
FROM base AS production

# 一般ユーザーの設定
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

# 依存関係のコピー
COPY --from=dependencies --chown=nodejs:nodejs /app/node_modules ./node_modules

# ビルド成果物のコピー
COPY --from=build --chown=nodejs:nodejs /app/dist ./dist

# メタデータ
LABEL version="1.0.0" \
      description="My Node.js application" \
      maintainer="yourname@example.com"

# 環境変数
ENV NODE_ENV=production \
    PORT=3000

USER nodejs

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"

CMD ["node", "dist/server.js"]

.dockerignore:

node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
dist
build
*.md
Dockerfile
docker-compose.yml
.DS_Store
coverage
.vscode
.idea

ビルドと実行:

# イメージをビルド
docker build -t myapp:1.0.0 .

# コンテナを起動
docker run -d \
  --name myapp \
  -p 3000:3000 \
  -e DATABASE_URL=postgresql://user:pass@db:5432/mydb \
  myapp:1.0.0

# ログを確認
docker logs -f myapp

# ヘルスチェック
curl http://localhost:3000/health

3.7 実践例:Python Webアプリケーション

Dockerfile(Flask/FastAPI用):

FROM python:3.11-slim AS base

# 環境変数
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /app

# 依存関係のインストール
FROM base AS dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# 本番環境
FROM base AS production

# 一般ユーザーの作成
RUN useradd -m -u 1001 appuser

# 依存関係のコピー
COPY --from=dependencies /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=dependencies /usr/local/bin /usr/local/bin

# アプリケーションのコピー
COPY --chown=appuser:appuser . .

USER appuser

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

requirements.txt:

fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0

ビルドと実行:

docker build -t myapp-python:1.0.0 .
docker run -d -p 8000:8000 --name myapp-python myapp-python:1.0.0

第4章:ボリュームとデータ管理

4.1 なぜボリュームが必要か

コンテナは基本的に「使い捨て」の存在です。コンテナを削除すると、その中のデータも消えます。

問題点:

# MySQLコンテナを起動
docker run -d --name db mysql:8.0

# データベースを使用
# ... データを保存 ...

# コンテナを削除
docker rm -f db

# 再度起動すると、データが消えている!
docker run -d --name db mysql:8.0

ボリュームが解決すること:

課題 ボリュームによる解決
データの消失 コンテナ削除後もデータが残る
ホストとの共有 ソースコードをリアルタイムで反映
コンテナ間共有 複数コンテナで同じデータを使用
バックアップ データを簡単にバックアップ可能

4.2 ボリュームの種類

Dockerには3種類のデータマウント方法があります。

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

特徴:

  • Dockerが管理する永続ストレージ
  • 最も推奨される方法
  • バックアップや移行が簡単

作成と使用:

# ボリュームを作成
docker volume create mydata

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

# ボリュームの詳細
docker volume inspect mydata

# コンテナで使用
docker run -d \
  --name db \
  -v mydata:/var/lib/mysql \
  mysql:8.0

# コンテナを削除してもボリュームは残る
docker rm -f db
docker volume ls  # mydata はまだ存在

# 新しいコンテナで同じボリュームを使用
docker run -d \
  --name db2 \
  -v mydata:/var/lib/mysql \
  mysql:8.0
# 以前のデータがそのまま使える

ボリュームの削除:

# 特定のボリュームを削除
docker volume rm mydata

# 使用されていないボリュームをすべて削除
docker volume prune

2. バインドマウント(Bind Mount)

特徴:

  • ホストの特定のディレクトリをマウント
  • 開発時にソースコードをリアルタイムで反映
  • ホストのファイルシステムに直接アクセス

使用方法:

# Windowsの場合(絶対パス)
docker run -d \
  --name web \
  -p 8080:80 \
  -v C:\Users\yourname\myapp:/usr/share/nginx/html \
  nginx

# または相対パス(カレントディレクトリから)
docker run -d \
  --name web \
  -p 8080:80 \
  -v ${PWD}/html:/usr/share/nginx/html \
  nginx

開発時の典型的な使用例:

# Node.jsアプリの開発
docker run -it --rm \
  --name myapp \
  -p 3000:3000 \
  -v ${PWD}:/app \
  -w /app \
  node:18 \
  npm run dev
# ホストでファイルを編集すると、コンテナ内にも即座に反映される

3. tmpfs マウント

特徴:

  • ホストのメモリ上にマウント
  • 高速だが再起動で消える
  • 機密データの一時保存に使用

使用方法:

docker run -d \
  --name app \
  --tmpfs /tmp:rw,size=100m \
  myapp

使用場面:

  • 一時ファイルの保存
  • キャッシュデータ
  • 機密情報(メモリ上のみに存在させたい)

4.3 ボリュームの比較

項目 名前付きボリューム バインドマウント tmpfs
管理 Dockerが管理 ユーザーが管理 メモリ上
場所 Docker管理領域 任意のホストパス メモリ
永続性 ×
速度 普通 普通 高速
本番環境 推奨 状況による 限定的
開発環境 推奨

4.4 実践:ソースコードをマウントして開発する

シナリオ: Node.jsアプリを開発中、コード変更を即座にコンテナに反映したい

ディレクトリ構成:

myapp/
├── package.json
├── src/
│   └── server.js
└── Dockerfile.dev

Dockerfile.dev(開発用):

FROM node:18-alpine

WORKDIR /app

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

# 開発用サーバー(nodemon)をインストール
RUN npm install -g nodemon

# ポート公開
EXPOSE 3000

# nodemonで起動(ファイル変更を監視)
CMD ["nodemon", "--watch", "src", "src/server.js"]

起動コマンド:

# イメージをビルド
docker build -f Dockerfile.dev -t myapp-dev .

# ソースコードをマウントして起動
docker run -it --rm \
  --name myapp-dev \
  -p 3000:3000 \
  -v ${PWD}/src:/app/src \
  myapp-dev

動作確認:

  1. ブラウザで http://localhost:3000 を開く
  2. src/server.js を編集
  3. 保存すると自動的にサーバーが再起動される
  4. ブラウザをリロードすると変更が反映される

注意点(Windowsの場合):

# Docker Desktopの設定で、マウントするドライブを共有する必要がある
# Settings → Resources → File Sharing で対象ドライブにチェック

4.5 実践:データベースのデータを永続化する

シナリオ: MySQLのデータをコンテナ削除後も保持したい

方法1:名前付きボリュームを使う(推奨)

# ボリュームを作成
docker volume create mysql-data

# MySQLコンテナを起動
docker run -d \
  --name mysql-db \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -e MYSQL_DATABASE=myapp \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8.0

# データベースを使用
docker exec -it mysql-db mysql -uroot -prootpass

mysql> CREATE TABLE users (id INT, name VARCHAR(100));
mysql> INSERT INTO users VALUES (1, 'Alice');
mysql> SELECT * FROM users;
mysql> exit

# コンテナを削除
docker rm -f mysql-db

# 新しいコンテナで同じボリュームを使用
docker run -d \
  --name mysql-db-new \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -e MYSQL_DATABASE=myapp \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8.0

# データが保持されている
docker exec -it mysql-db-new mysql -uroot -prootpass myapp -e "SELECT * FROM users;"
# +------+-------+
# | id   | name  |
# +------+-------+
# |    1 | Alice |
# +------+-------+

方法2:バインドマウントを使う(開発時)

# ホストにデータ用ディレクトリを作成
mkdir -p C:\mysql-data

# MySQLコンテナを起動
docker run -d \
  --name mysql-db \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -e MYSQL_DATABASE=myapp \
  -v C:\mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8.0

4.6 ボリュームのバックアップとリストア

バックアップ:

# ボリュームの内容をtarファイルに出力
docker run --rm \
  -v mysql-data:/data \
  -v ${PWD}:/backup \
  ubuntu \
  tar czf /backup/mysql-backup.tar.gz -C /data .

# バックアップファイルが作成される
ls mysql-backup.tar.gz

リストア:

# 新しいボリュームを作成
docker volume create mysql-data-restored

# バックアップファイルから復元
docker run --rm \
  -v mysql-data-restored:/data \
  -v ${PWD}:/backup \
  ubuntu \
  tar xzf /backup/mysql-backup.tar.gz -C /data

# 復元したボリュームを使用
docker run -d \
  --name mysql-restored \
  -v mysql-data-restored:/var/lib/mysql \
  mysql:8.0

4.7 ボリュームのベストプラクティス

ケース 推奨方法 理由
本番DBデータ 名前付きボリューム 管理が簡単、バックアップしやすい
開発時のコード バインドマウント リアルタイム反映
ログファイル 名前付きボリューム 永続化が必要
一時ファイル tmpfs 高速、不要なデータ
機密情報 tmpfs メモリ上のみに保存

注意点:

# Dockerfile内でVOLUMEを宣言すると、以降の変更が反映されない
FROM nginx
VOLUME /usr/share/nginx/html
# この後COPYしてもボリュームには反映されない
COPY index.html /usr/share/nginx/html/  # 効果なし

# 正しい方法:
FROM nginx
COPY index.html /usr/share/nginx/html/
# VOLUMEは必要に応じてdocker runで指定

第5章:ネットワーク

5.1 Dockerネットワークの基本

Dockerのネットワークは、コンテナ間の通信やホストとの通信を制御します。

Dockerネットワークの目的:

  • コンテナ同士を接続
  • 外部からのアクセスを制御
  • ネットワークの分離(セキュリティ)

デフォルトで存在するネットワーク:

docker network ls
ネットワーク 説明 用途
bridge デフォルトのネットワーク 単一ホスト上のコンテナ通信
host ホストのネットワークを直接使用 ネットワークの分離不要な場合
none ネットワークなし ネットワーク分離が必要な場合

5.2 ポートマッピング(-p オプション)の仕組み

コンテナのポートをホストに公開する方法です。

基本構文:

docker run -p <ホストポート>:<コンテナポート> <イメージ>

使用例:

# ホストの8080番をコンテナの80番にマッピング
docker run -d -p 8080:80 nginx
# ブラウザで http://localhost:8080 にアクセス

# 複数のポートをマッピング
docker run -d \
  -p 8080:80 \
  -p 8443:443 \
  nginx

# すべてのインターフェースで公開
docker run -d -p 0.0.0.0:8080:80 nginx

# 特定のIPアドレスで公開
docker run -d -p 127.0.0.1:8080:80 nginx

# ホストポートを自動割り当て
docker run -d -P nginx
# Dockerが空いているポートを自動的に割り当てる
docker ps  # ポート番号を確認

ポートマッピングの仕組み:

【ブラウザ】
    ↓
http://localhost:8080
    ↓
【ホスト】ポート8080
    ↓ (マッピング)
【コンテナ】ポート80
    ↓
【Nginx】

ポート番号の確認:

docker ps

# 出力例:
# PORTS
# 0.0.0.0:8080->80/tcp
# ホストの8080番がコンテナの80番にマッピングされている

5.3 コンテナ間通信

同じネットワーク上のコンテナは、コンテナ名で通信できます。

例:WebアプリとDBの接続

# 1. カスタムネットワークを作成
docker network create myapp-network

# 2. DBコンテナを起動
docker run -d \
  --name mysql-db \
  --network myapp-network \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -e MYSQL_DATABASE=myapp \
  mysql:8.0

# 3. Webアプリコンテナを起動
docker run -d \
  --name webapp \
  --network myapp-network \
  -p 8080:3000 \
  -e DATABASE_HOST=mysql-db \
  -e DATABASE_PORT=3306 \
  myapp:latest

# Webアプリから"mysql-db"というホスト名でDBに接続できる

コンテナ名前解決の仕組み:

【webappコンテナ】
    ↓
接続先: mysql-db:3306
    ↓
【Docker DNSサーバー】
    ↓
mysql-db → 172.18.0.2
    ↓
【mysql-dbコンテナ】

接続確認:

# webappコンテナ内から確認
docker exec -it webapp sh

# mysql-dbに接続できるか確認
ping mysql-db

# MySQLに接続
mysql -h mysql-db -uroot -prootpass

5.4 ネットワークの種類

1. bridge(デフォルト)

特徴:

  • コンテナごとに独立したネットワーク
  • 同じbridge上のコンテナ同士で通信可能
  • 外部への接続はNATを使用

デフォルトbridgeの問題点:

  • コンテナ名で名前解決できない(IPアドレス直指定が必要)
  • 自動的なサービスディスカバリなし

カスタムbridgeネットワークの作成(推奨):

# ネットワーク作成
docker network create my-bridge

# コンテナを接続
docker run -d --name web --network my-bridge nginx
docker run -d --name app --network my-bridge myapp

# コンテナ名で通信可能
docker exec app ping web

ネットワークの詳細確認:

docker network inspect my-bridge

2. host

特徴:

  • ホストのネットワークを直接使用
  • ポートマッピング不要
  • ネットワークの分離なし

使用例:

# hostネットワークを使用
docker run -d --network host nginx

# ホストの80番ポートで直接リッスン
# http://localhost にアクセス可能

使用場面:

  • パフォーマンスが重要な場合
  • 多数のポートを公開する場合
  • ネットワークオーバーヘッドを避けたい場合

注意点:

  • Windowsでは完全にはサポートされていない
  • セキュリティ面でのリスクあり

3. none

特徴:

  • ネットワーク接続なし
  • 完全に分離された環境

使用例:

docker run -d --network none myapp

使用場面:

  • セキュリティが重要な処理
  • ネットワークアクセスが不要な処理

5.5 ネットワークの管理コマンド

# ネットワーク一覧
docker network ls

# ネットワーク作成
docker network create <ネットワーク名>

# ネットワーク詳細
docker network inspect <ネットワーク名>

# コンテナをネットワークに接続
docker network connect <ネットワーク名> <コンテナ名>

# コンテナをネットワークから切断
docker network disconnect <ネットワーク名> <コンテナ名>

# ネットワーク削除
docker network rm <ネットワーク名>

# 使用されていないネットワークを削除
docker network prune

5.6 実践例:3層アーキテクチャ

構成: フロントエンド + バックエンド + データベース

# ネットワーク作成
docker network create frontend
docker network create backend

# データベース(backendネットワークのみ)
docker run -d \
  --name postgres \
  --network backend \
  -e POSTGRES_PASSWORD=dbpass \
  postgres:15

# バックエンドAPI(両方のネットワークに接続)
docker run -d \
  --name api \
  --network backend \
  -e DATABASE_URL=postgresql://postgres:dbpass@postgres:5432/mydb \
  myapi:latest

docker network connect frontend api

# フロントエンド(frontendネットワークのみ)
docker run -d \
  --name web \
  --network frontend \
  -p 80:80 \
  -e API_URL=http://api:3000 \
  myweb:latest

ネットワーク分離の効果:

【web】─── frontend ───【api】─── backend ───【postgres】
         ○可能          ○可能          ×不可
  • webはapiに接続できる
  • apiはpostgresに接続できる
  • webはpostgresに直接接続できない(セキュリティ)

第6章:Docker Compose

6.1 Docker Composeとは

Docker Composeは、複数のコンテナを定義・管理するツールです。YAMLファイルで設定を記述します。

Docker Composeが解決すること:

課題 Docker Composeによる解決
複雑なコマンド YAMLファイルで設定を管理
コンテナ間の依存関係 自動的に起動順序を制御
ネットワーク設定 自動的にネットワークを作成
環境の再現 ファイル共有で簡単に再現

比較:

docker runの場合:

# ネットワーク作成
docker network create myapp-network

# DBコンテナ起動
docker run -d \
  --name db \
  --network myapp-network \
  -e POSTGRES_PASSWORD=dbpass \
  -v pgdata:/var/lib/postgresql/data \
  postgres:15

# Webコンテナ起動
docker run -d \
  --name web \
  --network myapp-network \
  -p 8080:3000 \
  -e DATABASE_URL=postgresql://postgres:dbpass@db:5432/mydb \
  myapp:latest

Docker Composeの場合:

# docker-compose.yml
version: '3.8'

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: dbpass
    volumes:
      - pgdata:/var/lib/postgresql/data

  web:
    image: myapp:latest
    ports:
      - "8080:3000"
    environment:
      DATABASE_URL: postgresql://postgres:dbpass@db:5432/mydb
    depends_on:
      - db

volumes:
  pgdata:
# 起動は1コマンド
docker-compose up -d

6.2 docker-compose.ymlの書き方

基本構造

version: '3.8'  # Composeファイルのバージョン

services:       # コンテナの定義
  service1:
    # サービス1の設定
  service2:
    # サービス2の設定

volumes:        # 名前付きボリュームの定義
  volume1:

networks:       # カスタムネットワークの定義
  network1:

最小限の例

version: '3.8'

services:
  web:
    image: nginx
    ports:
      - "8080:80"

6.3 各設定項目の詳細解説

version(バージョン)

Composeファイルのフォーマットバージョンを指定します。

version: '3.8'  # 推奨

バージョンによる違い:

バージョン リリース 主な機能
3.0 2016 Swarmモード対応
3.8 2020 最新機能をサポート

注意: 基本的に 3.8 を使用すればOKです。


services(サービス)

コンテナの定義です。各サービスが1つのコンテナに対応します。

基本例:

services:
  web:
    image: nginx:latest
    container_name: my-nginx
  
  db:
    image: postgres:15
    container_name: my-postgres

image(イメージ)

使用するDockerイメージを指定します。

services:
  web:
    # 公式イメージ
    image: nginx
  
  app:
    # タグ指定
    image: node:18-alpine
  
  custom:
    # プライベートレジストリ
    image: myregistry.com/myapp:1.0.0

build(ビルド)

Dockerfileからイメージをビルドする場合に使用します。

基本形:

services:
  web:
    build: .  # カレントディレクトリのDockerfileを使用

詳細指定:

services:
  web:
    build:
      context: .              # ビルドコンテキスト
      dockerfile: Dockerfile.dev  # 使用するDockerfile
      args:                   # ビルド引数
        NODE_VERSION: 18
        BUILD_ENV: development
      target: development     # マルチステージビルドのターゲット

imageと併用:

services:
  web:
    build: .
    image: myapp:latest  # ビルドしたイメージに名前をつける

ports(ポートマッピング)

ホストとコンテナのポートをマッピングします。

services:
  web:
    ports:
      # 基本形
      - "8080:80"
      
      # 複数のポート
      - "8080:80"
      - "8443:443"
      
      # IPアドレス指定
      - "127.0.0.1:8080:80"
      
      # UDPプロトコル
      - "53:53/udp"
      
      # 範囲指定
      - "3000-3005:3000-3005"

短縮形と長形式:

services:
  web:
    # 短縮形
    ports:
      - "8080:80"
    
    # 長形式(より詳細な制御)
    ports:
      - target: 80       # コンテナポート
        published: 8080   # ホストポート
        protocol: tcp
        mode: host

volumes(ボリューム)

データの永続化やホストとの共有を設定します。

services:
  db:
    volumes:
      # 名前付きボリューム
      - pgdata:/var/lib/postgresql/data
      
      # バインドマウント(相対パス)
      - ./data:/data
      
      # バインドマウント(絶対パス)
      - /host/path:/container/path
      
      # 読み取り専用
      - ./config:/etc/config:ro
      
      # 一時ボリューム
      - /tmp

# 名前付きボリュームの定義
volumes:
  pgdata:

長形式:

services:
  web:
    volumes:
      - type: volume
        source: mydata      # ボリューム名
        target: /data       # コンテナ内パス
      
      - type: bind
        source: ./app       # ホストパス
        target: /app        # コンテナ内パス
        read_only: true

environment(環境変数)

コンテナ内の環境変数を設定します。

services:
  web:
    environment:
      # キー=値の形式
      NODE_ENV: production
      API_KEY: your-api-key
      DEBUG: "true"
      PORT: 3000

配列形式:

services:
  web:
    environment:
      - NODE_ENV=production
      - API_KEY=your-api-key

.envファイルから読み込む:

services:
  web:
    env_file:
      - .env
      - .env.local

.env ファイルの例:

NODE_ENV=production
DATABASE_URL=postgresql://user:pass@db:5432/mydb
API_KEY=secret-key

注意: .envファイルは.gitignoreに追加してバージョン管理から除外しましょう。


depends_on(依存関係)

サービスの起動順序を制御します。

services:
  web:
    image: myapp:latest
    depends_on:
      - db
      - redis
  
  db:
    image: postgres:15
  
  redis:
    image: redis:alpine

起動順序:

  1. db と redis が起動
  2. その後、web が起動

注意: depends_onは起動順序のみを制御します。DBが準備完了になるまで待つわけではありません。

準備完了を待つ方法:

services:
  web:
    image: myapp:latest
    depends_on:
      db:
        condition: service_healthy
  
  db:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

command(コマンド)

コンテナ起動時のコマンドを上書きします。

services:
  web:
    image: node:18
    command: npm run dev
  
  db:
    image: postgres:15
    command: postgres -c max_connections=200

配列形式:

services:
  web:
    image: node:18
    command: ["npm", "run", "dev"]

restart(再起動ポリシー)

コンテナが停止した時の動作を指定します。

services:
  web:
    image: nginx
    restart: always

オプション:

説明
no 再起動しない(デフォルト)
always 常に再起動
on-failure エラー終了時のみ再起動
unless-stopped 手動停止以外は再起動

推奨:

  • 開発環境:no または on-failure
  • 本番環境:always または unless-stopped

networks(ネットワーク)

サービスが接続するネットワークを指定します。

services:
  web:
    image: nginx
    networks:
      - frontend
  
  api:
    image: myapi:latest
    networks:
      - frontend
      - backend
  
  db:
    image: postgres:15
    networks:
      - backend

networks:
  frontend:
  backend:

詳細設定:

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

container_name(コンテナ名)

コンテナに名前をつけます。

services:
  web:
    image: nginx
    container_name: my-nginx-server

注意: docker-compose up --scaleで複数起動する場合は、container_nameを指定しないでください。


healthcheck(ヘルスチェック)

コンテナの健全性をチェックします。

services:
  web:
    image: myapp:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s      # チェック間隔
      timeout: 10s       # タイムアウト
      retries: 3         # 失敗時のリトライ回数
      start_period: 40s  # 起動猶予期間

テストコマンドの例:

# HTTP チェック
test: ["CMD", "curl", "-f", "http://localhost/health"]

# TCP チェック
test: ["CMD-SHELL", "nc -z localhost 3000"]

# PostgreSQL
test: ["CMD-SHELL", "pg_isready -U postgres"]

# MySQL
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]

labels(ラベル)

メタデータをコンテナに付与します。

services:
  web:
    image: nginx
    labels:
      com.example.description: "My web server"
      com.example.version: "1.0.0"
      com.example.team: "backend-team"

6.4 実践:WebアプリとDBを連携させる

完全な実例を見ていきます。

ディレクトリ構成:

myproject/
├── docker-compose.yml
├── .env
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
│       └── server.js
└── db/
    └── init.sql

docker-compose.yml:

version: '3.8'

services:
  # データベース
  db:
    image: postgres:15-alpine
    container_name: myapp-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${DB_USER:-postgres}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-dbpass}
      POSTGRES_DB: ${DB_NAME:-myapp}
    volumes:
      # データの永続化
      - postgres-data:/var/lib/postgresql/data
      # 初期化スクリプト
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # バックエンドAPI
  api:
    build:
      context: ./backend
      dockerfile: Dockerfile
      args:
        NODE_VERSION: 18
    container_name: myapp-api
    restart: unless-stopped
    ports:
      - "${API_PORT:-3000}:3000"
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DATABASE_URL: postgresql://${DB_USER:-postgres}:${DB_PASSWORD:-dbpass}@db:5432/${DB_NAME:-myapp}
      PORT: 3000
    volumes:
      # 開発時はソースコードをマウント
      - ./backend/src:/app/src
    depends_on:
      db:
        condition: service_healthy
    networks:
      - backend
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # Nginx(リバースプロキシ)
  nginx:
    image: nginx:alpine
    container_name: myapp-nginx
    restart: unless-stopped
    ports:
      - "${NGINX_PORT:-80}:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - api
    networks:
      - backend

networks:
  backend:
    driver: bridge

volumes:
  postgres-data:
    driver: local

.env ファイル:

# データベース設定
DB_USER=myuser
DB_PASSWORD=mypassword
DB_NAME=myapp_db

# アプリケーション設定
NODE_ENV=development
API_PORT=3000
NGINX_PORT=8080

backend/Dockerfile:

FROM node:18-alpine

WORKDIR /app

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

# 開発用ツール
RUN npm install -g nodemon

# アプリケーションコード
COPY . .

EXPOSE 3000

# 開発モード
CMD ["nodemon", "--watch", "src", "src/server.js"]

db/init.sql:

-- 初期化スクリプト
CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(100) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- サンプルデータ
INSERT INTO users (username, email) VALUES
    ('alice', 'alice@example.com'),
    ('bob', 'bob@example.com')
ON CONFLICT DO NOTHING;

nginx/nginx.conf:

events {
    worker_connections 1024;
}

http {
    upstream api {
        server api:3000;
    }

    server {
        listen 80;
        
        location / {
            proxy_pass http://api;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

6.5 Docker Composeの基本コマンド

起動・停止

# バックグラウンドで起動
docker-compose up -d

# フォアグラウンドで起動(ログを表示)
docker-compose up

# 特定のサービスのみ起動
docker-compose up -d db

# ビルドしてから起動
docker-compose up -d --build

# 停止
docker-compose stop

# 停止して削除
docker-compose down

# ボリュームも削除
docker-compose down -v

ログ確認

# すべてのサービスのログ
docker-compose logs

# リアルタイムでログを表示
docker-compose logs -f

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

# 最新100行のみ
docker-compose logs --tail=100

サービス管理

# サービス一覧
docker-compose ps

# 詳細情報
docker-compose ps -a

# 実行中のプロセス
docker-compose top

# サービスの再起動
docker-compose restart api

# サービスの停止
docker-compose stop api

# サービスの開始
docker-compose start api

コンテナ内でコマンド実行

# コンテナ内でコマンド実行
docker-compose exec api npm install lodash

# コンテナ内に入る
docker-compose exec api sh

# 新しいコンテナでコマンド実行
docker-compose run api npm test

# 実行後に削除
docker-compose run --rm api npm test

ビルド

# イメージをビルド
docker-compose build

# キャッシュを使わずビルド
docker-compose build --no-cache

# 特定のサービスのみビルド
docker-compose build api

スケーリング

# サービスを複数起動
docker-compose up -d --scale api=3

# 確認
docker-compose ps

注意: container_nameを指定している場合はスケーリングできません。

設定の検証

# 設定ファイルの検証
docker-compose config

# 設定の表示(環境変数を展開)
docker-compose config --services

# 使用するボリュームを表示
docker-compose config --volumes

6.6 環境別の設定管理

開発環境と本番環境で設定を切り替える方法です。

ディレクトリ構成:

myproject/
├── docker-compose.yml          # 共通設定
├── docker-compose.dev.yml      # 開発環境用
├── docker-compose.prod.yml     # 本番環境用
├── .env.dev                    # 開発環境変数
└── .env.prod                   # 本番環境変数

docker-compose.yml(ベース):

version: '3.8'

services:
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}

  api:
    build: ./backend
    environment:
      DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}

docker-compose.dev.yml(開発環境の上書き):

version: '3.8'

services:
  db:
    ports:
      - "5432:5432"  # 開発時はDBを公開
    volumes:
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql

  api:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./backend/src:/app/src  # ホットリロード
    environment:
      NODE_ENV: development
      DEBUG: "true"
    command: npm run dev

docker-compose.prod.yml(本番環境の上書き):

version: '3.8'

services:
  db:
    restart: always
    volumes:
      - postgres-data:/var/lib/postgresql/data

  api:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod
    restart: always
    environment:
      NODE_ENV: production
    command: npm start

  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro

volumes:
  postgres-data:

使用方法:

# 開発環境で起動
docker-compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.dev up -d

# 本番環境で起動
docker-compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d

# エイリアスを作成すると便利
alias dc-dev='docker-compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.dev'
alias dc-prod='docker-compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod'

# 使用例
dc-dev up -d
dc-prod up -d

第7章:実践的な開発環境構築

7.1 典型的な構成パターン

パターン1:フロントエンド + バックエンド + DB

構成: React + Node.js (Express) + PostgreSQL

ディレクトリ構成:

fullstack-app/
├── docker-compose.yml
├── .env
├── frontend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
└── db/
    └── init.sql

docker-compose.yml:

version: '3.8'

services:
  # PostgreSQL データベース
  db:
    image: postgres:15-alpine
    container_name: fullstack-db
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Node.js バックエンドAPI
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: fullstack-api
    ports:
      - "3001:3001"
    environment:
      NODE_ENV: development
      PORT: 3001
      DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      CORS_ORIGIN: http://localhost:3000
    volumes:
      - ./backend/src:/app/src
      - /app/node_modules  # ホスト側のnode_modulesをマウントしない
    depends_on:
      db:
        condition: service_healthy
    networks:
      - backend
      - frontend

  # React フロントエンド
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    container_name: fullstack-web
    ports:
      - "3000:3000"
    environment:
      REACT_APP_API_URL: http://localhost:3001
    volumes:
      - ./frontend/src:/app/src
      - /app/node_modules
    depends_on:
      - backend
    networks:
      - frontend
    stdin_open: true
    tty: true

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

volumes:
  postgres-data:

backend/Dockerfile:

FROM node:18-alpine

WORKDIR /app

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

# nodemonをインストール(開発用)
RUN npm install -g nodemon

# アプリケーションコード
COPY . .

EXPOSE 3001

CMD ["nodemon", "src/server.js"]

frontend/Dockerfile:

FROM node:18-alpine

WORKDIR /app

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

# アプリケーションコード
COPY . .

EXPOSE 3000

CMD ["npm", "start"]

起動:

# すべてのサービスを起動
docker-compose up -d

# ログを確認
docker-compose logs -f

# 確認
# フロントエンド: http://localhost:3000
# バックエンドAPI: http://localhost:3001

パターン2:マイクロサービス構成

構成: API Gateway + 複数のマイクロサービス + Redis + DB

version: '3.8'

services:
  # Redis(キャッシュ・セッション管理)
  redis:
    image: redis:alpine
    container_name: microservices-redis
    networks:
      - backend

  # PostgreSQL
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: adminpass
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - backend

  # MongoDB
  mongodb:
    image: mongo:6
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: adminpass
    volumes:
      - mongo-data:/data/db
    networks:
      - backend

  # ユーザーサービス
  user-service:
    build: ./services/user-service
    environment:
      DATABASE_URL: postgresql://admin:adminpass@postgres:5432/users
      REDIS_URL: redis://redis:6379
    depends_on:
      - postgres
      - redis
    networks:
      - backend

  # 商品サービス
  product-service:
    build: ./services/product-service
    environment:
      MONGO_URL: mongodb://admin:adminpass@mongodb:27017/products
      REDIS_URL: redis://redis:6379
    depends_on:
      - mongodb
      - redis
    networks:
      - backend

  # 注文サービス
  order-service:
    build: ./services/order-service
    environment:
      DATABASE_URL: postgresql://admin:adminpass@postgres:5432/orders
      USER_SERVICE_URL: http://user-service:3000
      PRODUCT_SERVICE_URL: http://product-service:3000
    depends_on:
      - postgres
      - user-service
      - product-service
    networks:
      - backend

  # API Gateway
  api-gateway:
    build: ./api-gateway
    ports:
      - "8080:8080"
    environment:
      USER_SERVICE_URL: http://user-service:3000
      PRODUCT_SERVICE_URL: http://product-service:3000
      ORDER_SERVICE_URL: http://order-service:3000
    depends_on:
      - user-service
      - product-service
      - order-service
    networks:
      - backend

networks:
  backend:

volumes:
  postgres-data:
  mongo-data:

7.2 環境変数の管理(.envファイル)

.env(開発環境):

# データベース
DB_USER=devuser
DB_PASSWORD=devpass
DB_NAME=myapp_dev

# アプリケーション
NODE_ENV=development
API_PORT=3001
WEB_PORT=3000

# Redis
REDIS_HOST=redis
REDIS_PORT=6379

# JWT
JWT_SECRET=dev-secret-key
JWT_EXPIRY=1h

# 外部API
STRIPE_API_KEY=sk_test_xxxxx
SENDGRID_API_KEY=SG.xxxxx

.env.example(テンプレート):

# データベース
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_NAME=your_db_name

# アプリケーション
NODE_ENV=development
API_PORT=3001
WEB_PORT=3000

# 外部API(本番環境の値に置き換える)
STRIPE_API_KEY=your_stripe_key
SENDGRID_API_KEY=your_sendgrid_key

セキュリティのベストプラクティス:

  1. .envファイルを.gitignoreに追加
.env
.env.local
.env.*.local
  1. .env.exampleをバージョン管理
# 他の開発者が使用する際
cp .env.example .env
# .envファイルを編集して実際の値を設定
  1. 機密情報は別管理
# docker-compose.yml
services:
  api:
    environment:
      # 公開情報は.envから
      NODE_ENV: ${NODE_ENV}
      PORT: ${PORT}
    # 機密情報はsecretsで管理(Docker Swarm/Kubernetes)
    secrets:
      - db_password
      - api_key

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    file: ./secrets/api_key.txt

7.3 開発環境と本番環境の切り替え

マルチステージビルドを活用:

Dockerfile:

# ベースステージ
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./

# 開発環境
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

# ビルドステージ
FROM base AS builder
RUN npm ci --only=production
COPY . .
RUN npm run build

# 本番環境
FROM node:18-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER node
CMD ["node", "dist/server.js"]

docker-compose.dev.yml:

version: '3.8'

services:
  api:
    build:
      context: ./backend
      target: development
    volumes:
      - ./backend/src:/app/src
    environment:
      NODE_ENV: development
      DEBUG: "api:*"

docker-compose.prod.yml:

version: '3.8'

services:
  api:
    build:
      context: ./backend
      target: production
    restart: always
    environment:
      NODE_ENV: production

7.4 ホットリロード(コード変更の自動反映)設定

Node.js(nodemon)

package.json:

{
  "scripts": {
    "dev": "nodemon --watch src src/server.js",
    "start": "node src/server.js"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

docker-compose.yml:

services:
  api:
    build: ./backend
    volumes:
      - ./backend/src:/app/src
      - /app/node_modules  # node_modulesは除外
    command: npm run dev

nodemon.json(詳細設定):

{
  "watch": ["src"],
  "ext": "js,json",
  "ignore": ["src/**/*.test.js"],
  "delay": 1000
}

React(Create React App)

Reactは標準でホットリロードが有効です。

docker-compose.yml:

services:
  frontend:
    build: ./frontend
    volumes:
      - ./frontend/src:/app/src
      - ./frontend/public:/app/public
      - /app/node_modules
    environment:
      CHOKIDAR_USEPOLLING: "true"  # Windowsの場合必要
    stdin_open: true
    tty: true

注意(Windows): ファイル変更の検知にCHOKIDAR_USEPOLLING=trueが必要な場合があります。


Python(Flask)

docker-compose.yml:

services:
  api:
    build: ./backend
    volumes:
      - ./backend/app:/app
    environment:
      FLASK_ENV: development
      FLASK_DEBUG: 1
    command: flask run --host=0.0.0.0 --reload

または、watchdogを使用:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt
RUN pip install watchdog

COPY . .

CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--reload"]

第8章:デバッグとトラブルシューティング

8.1 よくあるエラーと対処法

エラー1:ポートが既に使用されている

エラーメッセージ:

Error starting userland proxy: listen tcp 0.0.0.0:3000: bind: address already in use

原因: ホストの3000番ポートが既に使用されている

対処法:

# Windows: ポートを使用しているプロセスを確認
netstat -ano | findstr :3000

# プロセスを終了
taskkill /PID <プロセスID> /F

# または、docker-compose.ymlでポート番号を変更
ports:
  - "3001:3000"  # ホスト側を3001に変更

エラー2:ボリュームのパーミッションエラー

エラーメッセージ:

Permission denied: '/var/lib/postgresql/data'

原因: コンテナ内のユーザーとボリュームの権限が合わない

対処法:

# Dockerfileでユーザーを明示
FROM postgres:15-alpine

# または
RUN chown -R postgres:postgres /var/lib/postgresql/data
# docker-compose.ymlでユーザーを指定
services:
  db:
    image: postgres:15-alpine
    user: "1000:1000"  # ホストのUIDに合わせる

エラー3:イメージのビルドに失敗

エラーメッセージ:

failed to solve with frontend dockerfile.v0: failed to build LLB

原因: Dockerfileの構文エラーまたはビルドコンテキストの問題

対処法:

# キャッシュを使わずにビルド
docker-compose build --no-cache

# ビルドログを詳細表示
docker-compose build --progress=plain api

# .dockerignoreを確認
# 必要なファイルが除外されていないか確認

エラー4:コンテナが immediately exits

エラーメッセージ:

Exited (0) 1 second ago

原因: CMDで指定したプロセスが即座に終了している

対処法:

# ログを確認
docker-compose logs api

# コンテナを対話モードで起動
docker-compose run api sh

# または、Dockerfileを修正
CMD ["tail", "-f", "/dev/null"]  # デバッグ用に無限待機

エラー5:ネットワーク接続エラー

エラーメッセージ:

Error: connect ECONNREFUSED 172.18.0.2:5432

原因: サービス名の解決失敗またはサービスの未起動

対処法:

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

# ネットワークを確認
docker network ls
docker network inspect <ネットワーク名>

# depends_onを追加
services:
  api:
    depends_on:
      db:
        condition: service_healthy

エラー6:環境変数が読み込まれない

原因: .envファイルのパスが正しくない

対処法:

# .envファイルの場所を確認
ls -la .env

# 明示的に指定
docker-compose --env-file .env up

# docker-compose.ymlで確認
docker-compose config

8.2 ログの確認方法

基本的なログ確認

# すべてのサービスのログ
docker-compose logs

# 特定のサービス
docker-compose logs api

# リアルタイムで追跡
docker-compose logs -f api

# 最新100行
docker-compose logs --tail=100 api

# タイムスタンプ付き
docker-compose logs -t api

# 複数サービス
docker-compose logs api db

ログのフィルタリング

# Windowsの場合
docker-compose logs api | findstr "ERROR"

# 特定の時間以降
docker-compose logs --since 2023-11-15T10:00:00

# 特定の時間まで
docker-compose logs --until 2023-11-15T11:00:00

ログドライバーの設定

docker-compose.yml:

services:
  api:
    image: myapi:latest
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

ログドライバーの種類:

ドライバー 説明
json-file デフォルト、JSON形式でファイルに保存
syslog Syslogサーバーに送信
journald systemd journalに送信
none ログを保存しない

8.3 コンテナの状態確認

実行状態の確認

# コンテナ一覧
docker-compose ps

# 詳細情報
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

# リソース使用状況
docker stats

# 特定のコンテナのリソース
docker stats <コンテナ名>

ヘルスチェックの確認

# コンテナの詳細情報
docker inspect <コンテナ名>

# ヘルスチェック結果のみ
docker inspect --format='{{json .State.Health}}' <コンテナ名>

プロセスの確認

# コンテナ内のプロセス
docker-compose top

# 特定のサービス
docker-compose top api

8.4 イメージ・コンテナの削除とクリーンアップ

基本的な削除

# 停止中のコンテナを削除
docker-compose down

# ボリュームも削除
docker-compose down -v

# イメージも削除
docker-compose down --rmi all

# すべて削除(ボリューム、イメージ、ネットワーク)
docker-compose down -v --rmi all --remove-orphans

個別削除

# 停止中のコンテナをすべて削除
docker container prune

# 未使用のイメージを削除
docker image prune

# 未使用のボリュームを削除
docker volume prune

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

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

# イメージも含めて削除
docker system prune -a

# ボリュームも含めて削除
docker system prune -a --volumes

ディスク使用量の確認

# Docker全体の使用量
docker system df

# 詳細表示
docker system df -v

特定のパターンで削除

# 名前パターンで削除(Windows PowerShell)
docker ps -a --filter "name=myapp" -q | ForEach-Object { docker rm $_ }

# 終了コードでフィルタ
docker ps -a --filter "exited=1" -q | ForEach-Object { docker rm $_ }

# 特定のイメージから作成されたコンテナを削除
docker ps -a --filter "ancestor=node:18" -q | ForEach-Object { docker rm $_ }

8.5 デバッグツールとテクニック

コンテナ内でのデバッグ

# コンテナに入る
docker-compose exec api sh

# 一時的にツールをインストール
apk add curl vim htop

# ネットワーク確認
ping db
nslookup db
curl http://db:5432

# ファイルシステム確認
ls -la /app
cat /app/config.json

# プロセス確認
ps aux
top

# 環境変数確認
env
echo $DATABASE_URL

ログレベルの調整

services:
  api:
    environment:
      LOG_LEVEL: debug
      DEBUG: "*"  # Node.jsのdebugパッケージ

Visual Studio Codeでのデバッグ

launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Docker: Attach to Node",
      "remoteRoot": "/app",
      "localRoot": "${workspaceFolder}/backend",
      "port": 9229,
      "restart": true
    }
  ]
}

docker-compose.yml:

services:
  api:
    build: ./backend
    command: node --inspect=0.0.0.0:9229 src/server.js
    ports:
      - "3000:3000"
      - "9229:9229"  # デバッグポート

第9章:デプロイの基礎

9.1 イメージのビルドとタグ付け

バージョン管理のベストプラクティス

# セマンティックバージョニング
docker build -t myapp:1.0.0 .
docker build -t myapp:1.0 .
docker build -t myapp:1 .
docker build -t myapp:latest .

# Git コミットハッシュを使用(PowerShell/Git Bash)
GIT_HASH=$(git rev-parse --short HEAD)
docker build -t myapp:${GIT_HASH} .

# Windowsコマンドプロンプトの場合
# for /f %i in ('git rev-parse --short HEAD') do set GIT_HASH=%i
# docker build -t myapp:%GIT_HASH% .

# ビルド日付を含める
BUILD_DATE=$(date +%Y%m%d)
docker build -t myapp:${BUILD_DATE} .

マルチアーキテクチャビルド

# buildxを使用(ARM、x86対応)
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.0.0 .

9.2 Docker Hubへのプッシュ

Docker Hubアカウントの準備

# ログイン
docker login

# ユーザー名を確認
docker info | findstr Username

イメージのプッシュ

# イメージにタグ付け(ユーザー名/イメージ名:タグ)
docker tag myapp:1.0.0 yourusername/myapp:1.0.0
docker tag myapp:1.0.0 yourusername/myapp:latest

# プッシュ
docker push yourusername/myapp:1.0.0
docker push yourusername/myapp:latest

プライベートレジストリの使用

# docker-compose.yml
services:
  api:
    image: registry.example.com/myapp:1.0.0
# プライベートレジストリにログイン
docker login registry.example.com

# プッシュ
docker push registry.example.com/myapp:1.0.0

9.3 本番環境での起動

本番用docker-compose.yml

version: '3.8'

services:
  db:
    image: postgres:15-alpine
    restart: always
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - backend
    secrets:
      - db_password

  api:
    image: yourusername/myapp:1.0.0
    restart: always
    environment:
      NODE_ENV: production
      DATABASE_URL_FILE: /run/secrets/db_url
    depends_on:
      - db
    networks:
      - backend
      - frontend
    secrets:
      - db_url
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - api
    networks:
      - frontend

networks:
  frontend:
  backend:

volumes:
  postgres-data:

secrets:
  db_password:
    file: ./secrets/db_password.txt
  db_url:
    file: ./secrets/db_url.txt

起動スクリプト

deploy.sh:

#!/bin/bash

# 環境変数の読み込み
export $(cat .env.prod | xargs)

# 最新イメージを取得
docker-compose -f docker-compose.prod.yml pull

# コンテナを再起動(ダウンタイムなし)
docker-compose -f docker-compose.prod.yml up -d --no-deps --build api

# 古いイメージを削除
docker image prune -f

# ヘルスチェック
sleep 10
curl -f http://localhost/health || exit 1

echo "デプロイ完了"

9.4 セキュリティの基本的な考え方

1. 最小権限の原則

# rootユーザーを避ける
FROM node:18-alpine

RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

WORKDIR /app
COPY --chown=nodejs:nodejs . .

USER nodejs

CMD ["node", "server.js"]

2. 機密情報の管理

# secretsを使用
services:
  api:
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

または環境変数(本番環境):

# サーバーの環境変数として設定
export DATABASE_PASSWORD="secure-password"

# docker-composeで参照
services:
  api:
    environment:
      DATABASE_PASSWORD: ${DATABASE_PASSWORD}

3. イメージの脆弱性スキャン

# Docker Desktopの機能を使用
docker scan myapp:latest

# または、Trivyを使用
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image myapp:latest

4. ネットワークの分離

networks:
  # 外部公開
  frontend:
  # 内部専用
  backend:
    internal: true

5. 読み取り専用ファイルシステム

services:
  api:
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

6. リソース制限

services:
  api:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

付録

A. よく使うコマンドチートシート

Docker基本コマンド

コマンド 説明
docker --version Dockerのバージョン確認
docker info Docker環境の詳細情報
docker login Docker Hubにログイン

イメージ操作

コマンド 説明
docker images イメージ一覧
docker pull <イメージ> イメージのダウンロード
docker build -t <名前> . イメージのビルド
docker rmi <イメージ> イメージの削除
docker image prune 未使用イメージの削除

コンテナ操作

コマンド 説明
docker ps 実行中のコンテナ一覧
docker ps -a すべてのコンテナ一覧
docker run -d <イメージ> コンテナをバックグラウンド起動
docker run -it <イメージ> コンテナを対話モードで起動
docker stop <コンテナ> コンテナの停止
docker start <コンテナ> コンテナの開始
docker restart <コンテナ> コンテナの再起動
docker rm <コンテナ> コンテナの削除
docker exec -it <コンテナ> bash 実行中のコンテナに入る
docker logs -f <コンテナ> ログをリアルタイム表示

Docker Composeコマンド

コマンド 説明
docker-compose up -d サービスをバックグラウンド起動
docker-compose down サービスを停止・削除
docker-compose ps サービスの状態確認
docker-compose logs -f ログをリアルタイム表示
docker-compose exec <サービス> sh サービスのコンテナに入る
docker-compose build イメージをビルド
docker-compose restart <サービス> サービスの再起動

クリーンアップコマンド

コマンド 説明
docker system prune 未使用リソースの削除
docker container prune 停止中のコンテナ削除
docker image prune 未使用イメージの削除
docker volume prune 未使用ボリュームの削除
docker network prune 未使用ネットワークの削除

B. docker-compose.yml サンプル集

1. WordPress環境

version: '3.8'

services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpresspass
    volumes:
      - db-data:/var/lib/mysql

  wordpress:
    image: wordpress:latest
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpresspass
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp-data:/var/www/html
    depends_on:
      - db

volumes:
  db-data:
  wp-data:

2. MERN Stack(MongoDB + Express + React + Node.js)

version: '3.8'

services:
  mongodb:
    image: mongo:6
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: adminpass
    volumes:
      - mongo-data:/data/db
    networks:
      - mern

  backend:
    build: ./backend
    ports:
      - "5000:5000"
    environment:
      MONGO_URI: mongodb://admin:adminpass@mongodb:27017/myapp?authSource=admin
      PORT: 5000
    depends_on:
      - mongodb
    networks:
      - mern

  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
    environment:
      REACT_APP_API_URL: http://localhost:5000
    depends_on:
      - backend
    networks:
      - mern

networks:
  mern:

volumes:
  mongo-data:

3. 開発環境(Python + PostgreSQL + Redis)

version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: devuser
      POSTGRES_PASSWORD: devpass
      POSTGRES_DB: devdb
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    networks:
      - backend

  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    networks:
      - backend

  api:
    build:
      context: ./api
      dockerfile: Dockerfile.dev
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://devuser:devpass@postgres:5432/devdb
      REDIS_URL: redis://redis:6379
      FLASK_ENV: development
    volumes:
      - ./api:/app
    depends_on:
      - postgres
      - redis
    networks:
      - backend
    command: flask run --host=0.0.0.0 --reload

networks:
  backend:

volumes:
  postgres-data:

C. さらに学ぶためのリソース

公式ドキュメント

学習サイト

ベストプラクティス

コミュニティ


おわりに

本資料では、Dockerの基礎から実践的な使い方まで解説しました。

学習のステップ:

  1. ✅ Dockerの基本概念を理解する
  2. ✅ 基本コマンドを使いこなす
  3. ✅ Dockerfileを書いて理解する
  4. ✅ Docker Composeで複数コンテナを管理する
  5. ✅ 開発環境を構築する
  6. ✅ トラブルシューティングができる
  7. ✅ デプロイの基礎を理解する

AWS コンテナサービスへの次のステップ

本資料で学んだDockerの知識は、AWSのコンテナサービスでそのまま活用できます。

Amazon ECR(Elastic Container Registry)

概要:

  • Dockerイメージを保存するプライベートレジストリ
  • Docker Hubと同じ役割
  • AWS環境との統合が簡単

本資料との対応:

  • 第9章「Docker Hubへのプッシュ」で学んだ内容がそのまま使える
  • docker pushコマンドの送信先がECRになるだけ

基本的な使い方:

# ECRにログイン
aws ecr get-login-password --region ap-northeast-1 | \
  docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com

# イメージにタグ付け
docker tag myapp:1.0.0 <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:1.0.0

# ECRにプッシュ
docker push <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:1.0.0

Amazon ECS(Elastic Container Service)

概要:

  • Dockerコンテナを本番環境で動かすサービス
  • サーバー管理不要のFargateモードが便利
  • オートスケーリング、ロードバランサー統合

本資料との対応:

本資料で学んだこと ECSでの対応
Dockerfile そのまま使用
docker-compose.yml ECSタスク定義に変換
ポートマッピング タスク定義のportMappings
環境変数 タスク定義のenvironment
ボリューム EFSマウント、タスクストレージ
ネットワーク VPC、セキュリティグループ

ECSタスク定義の例:

{
  "family": "myapp",
  "containerDefinitions": [
    {
      "name": "web",
      "image": "<ECRのイメージURL>",
      "portMappings": [
        {
          "containerPort": 3000,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "NODE_ENV",
          "value": "production"
        }
      ]
    }
  ]
}

学習の進め方

ステップ1:ECRの基本

  1. ✅ 本資料でDockerfileとイメージビルドを習得
  2. AWS CLIをインストール
  3. ECRリポジトリを作成
  4. ローカルでビルドしたイメージをECRにプッシュ

ステップ2:ECS Fargateでデプロイ

  1. ✅ 本資料でDocker Composeを習得
  2. ECSクラスタを作成(Fargateモード)
  3. タスク定義を作成(docker-compose.ymlを参考に)
  4. サービスを起動してアプリを公開

ステップ3:実践的な構成

  1. ALB(ロードバランサー)との統合
  2. RDS(データベース)との接続
  3. Auto Scalingの設定
  4. CI/CDパイプラインの構築

推奨リソース

公式ドキュメント:

ハンズオン:

ツール:

  • ecs-cli - Docker ComposeからECSへの変換ツール
  • AWS Copilot - ECS/Fargateの簡易デプロイツール

Docker → ECS移行のポイント

1. docker-compose.yml → ECSタスク定義の変換

本資料の第6章で学んだDocker Composeの知識がそのまま活かせます:

docker-compose.yml ECSタスク定義
services: containerDefinitions:
image: image:
ports: portMappings:
environment: environment:
volumes: mountPoints: + volumes:
depends_on: コンテナの起動順序

2. 環境変数の管理

{
  "environment": [
    {"name": "NODE_ENV", "value": "production"}
  ],
  "secrets": [
    {
      "name": "DB_PASSWORD",
      "valueFrom": "arn:aws:secretsmanager:..."
    }
  ]
}

3. ログの管理

{
  "logConfiguration": {
    "logDriver": "awslogs",
    "options": {
      "awslogs-group": "/ecs/myapp",
      "awslogs-region": "ap-northeast-1",
      "awslogs-stream-prefix": "ecs"
    }
  }
}

最後に

継続的に実践しながら学習を進めることが、Docker習得の近道です。

実践のステップ:

  1. 本資料の例を実際に動かす
  2. 自分のプロジェクトでDockerを使ってみる
  3. チームでDocker環境を共有する
  4. AWS ECS/ECRで本番デプロイに挑戦

実際のプロジェクトでDockerを使いながら、この資料を参照してください。


対象者: Docker初心者〜中級者
目標レベル: 実務で困らないDocker力
次のステップ: AWS ECS/ECRでのコンテナ運用

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?