開発者のためのDocker:イメージ、コンテナ、Compose

DevOps2026-10-12TryQuickToolBox

プロジェクトをクローンしてセットアップ手順を実行したのに、システムライブラリの不足、データベースの起動失敗、Nodeバージョンの不一致などで行き詰まった経験はありませんか。Dockerは、そうした問題を根本から解消するために存在します。サービスをマシンに直接インストールする代わりに、一度定義すれば、どこでも同じように動作する隔離されたコンテナで実行できます。

このガイドでは、本当に必要な3つの概念、イメージ、コンテナ、Composeについて説明します。読みながら実行できるコマンドを交えた実践的な内容です。

イメージとコンテナ:メンタルモデル

イメージは読み取り専用のテンプレートです。ファイルシステムのスナップショットと、デフォルトコマンドや公開ポートなどのメタデータが含まれます。コンテナはそのイメージの実行中(または停止中)のインスタンスで、上に薄い書き込み可能なレイヤーがあります。

オブジェクト指向プログラミングの経験があれば、類推はほぼそのままです。イメージはクラス、コンテナはオブジェクトです。1つのイメージから多数のコンテナを起動でき、コンテナ内の変更はイメージを変更しません。

概念内容典型的なコマンド
イメージ不変のテンプレートdocker build, docker pull
コンテナ実行中のインスタンスdocker run, docker ps
ボリューム永続ストレージdocker volume create
Composeマルチコンテナ定義docker compose up

Dockerfileで最初のイメージを作る

Dockerfileはレシピです。各命令がレイヤーを作成し、Dockerはレイヤーをキャッシュするため、変更がなければ再ビルドは高速です。以下は小さなNode.jsの例です:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

ここで重要な習慣が2つあります。まず、依存関係のマニフェストをソースの残りより先にコピーして、アプリケーションコードだけが変更されたときにnpm ciレイヤーがキャッシュされるようにします。次に、alpineのようなスリムなベースイメージを選び、サイズと攻撃対象領域を減らします。

ビルドして実行します:

docker build -t myapp:1.0 .
docker run --rm -p 3000:3000 myapp:1.0

-p 3000:3000フラグは、ホストのポート3000をコンテナのポート3000にマッピングします。--rmはコンテナ終了時に削除するので、開発中にマシンをきれいに保てます。

日常的なコンテナコマンド

これらで日常作業のほとんどをカバーできます:

コンテナがすぐに終了する場合は、まずログを確認します。よくある原因は、CMDの誤り、環境変数の欠落、コンテナ内でプロセスが0.0.0.0ではなく127.0.0.1にバインドしていることです。

Composeが存在する理由

実際のアプリケーションは通常、複数のプロセスを必要とします:API、データベース、場合によってはキャッシュ。長いフラグリストを持つ3つのdocker runコマンドを実行するのはエラーが起きやすいです。Docker Composeを使えば、すべてのサービスを1つのYAMLファイルで宣言し、まとめて起動できます。

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/app
    depends_on:
      - db
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:

APIがlocalhostではなくdbに接続していることに注目してください。Composeはプライベートネットワークを作成し、各サービスはサービス名で到達可能です。これは初心者が最も混乱しやすい点です。

すべてを起動するにはdocker compose up、バックグラウンドで実行するにはdocker compose up -dを使います。コード変更後はdocker compose up --buildで再ビルドします。停止するにはdocker compose down、名前付きボリュームも削除したい場合は-vを追加します。

ボリューム、バインドマウント、ライブリロード

コンテナは一時的で、内部のデータは削除時に消えます。これを解決する2つの仕組みがあります:

  1. 名前付きボリューム — Dockerが管理し、データベースファイルに最適。コンテナ再起動後も永続化され、volumes:セクションで宣言します。
  2. バインドマウント — ホストディレクトリをコンテナにマッピングし、開発中のソースコードに最適。
services:
  api:
    build: .
    volumes:
      - ./src:/app/src

バインドマウントを使うと、ホストでファイルを編集するとコンテナ内に反映されます。ファイルウォッチャー(nodemon、uvicorn --reload、air)と組み合わせれば、イメージを再ビルドせずにライブリロードが得られます。

実践的な開発ワークフロー

  1. アプリ用のDockerfileを書き、最小限に保つ。
  2. アプリと依存関係を含むdocker-compose.ymlを追加する。
  3. ソースコードにはバインドマウント、データには名前付きボリュームを使う。
  4. シークレットはファイルに含めず、.envファイルを使い${VAR}で変数を参照する。
  5. ビルドを高速化するため、node_modules、.git、ビルド出力を含む.dockerignoreを追加する。
  6. docker compose up --buildを実行して反復する。

サービスが後で確認したいログを生成する場合、コンテナログは単なるテキストであることを覚えておいてください。nginxログアナライザーのようなサーバーログを解析するツールは、ホストに何もインストールせずにログをパイプまたはコピーできるため、コンテナと相性が良いです。

よくある落とし穴

FAQ

イメージとコンテナの違いは何ですか?

イメージはファイルシステムとメタデータを含む不変のテンプレートです。コンテナはそのイメージの実行インスタンスで、書き込み可能なレイヤーが追加されています。1つのイメージから多数のコンテナを起動できます。

単一のサービスにDocker Composeは必要ですか?

いいえ。1つのコンテナにはdocker runコマンド1つで十分です。Composeは複数のサービス、共有ネットワーク、または1か所で宣言したい環境変数がある場合に価値を発揮します。

コンテナ同士はどうやって通信しますか?

Composeはすべてのサービスを共有ネットワークに配置し、各サービスはサービス名で到達可能です。その名前をホスト名として使います。例えばlocalhostの代わりにdb:5432です。

Dockerは少しの初期投資で報われます。イメージ、コンテナ、Composeが理解できれば、チームメイトのオンボーディングやマシンの切り替えはセットアップの儀式ではなく、1つのコマンドになります。