Docker 入門

Dockerfile の構文と構造

Dockerfileは、イメージを組み立てる際にコマンドラインで通常使用するすべてのコマンドが含まれたプレーンテキストファイルです。docker build コマンドを実行することで、DockerはDockerfile内のこれらの命令を自動的に順次実行し、新しいDockerイメージを迅速かつ自動的に構築することができます。

コンテナ化開発において、Dockerfileは不可欠な存在です。これはイメージ構築プロセスの「一貫性」と「再現性」を保証し、アプリケーションの実行環境と依存関係を定義する基盤となります。軽量で効率的、かつメンテナンスが容易なDockerイメージを構築するためには、Dockerfileの構文と構造を深く理解することが不可欠です。

1. Dockerfileの構造と構文

Dockerfileは、1行ずつの命令(Instruction)で構成されています。Dockerはこれらの命令を上から下へ1行ずつ実行してイメージを構築します。命令自体は大文字・小文字を区別しませんが、引数と区別しやすくするため、命令をすべて大文字で記述するのが業界の慣習的なルールです。

Dockerfileの命令の一般的な形式は次の通りです:
INSTRUCTION 引数(例:FROM ubuntu:20.04

1.1 基本構造

標準的なDockerfileは、通常以下の順序に従って記述されます:

  1. ベースイメージ (Base Image): FROM 命令で開始し、イメージの基盤となる既存のイメージを指定します。
  2. メタデータ (Metadata): LABEL 命令を使用して、バージョンや作者などの説明情報を追加します。
  3. コマンド実行 (Commands): ソフトウェアのインストール、ファイルのコピー、環境変数の設定などを行う一連の命令です。
  4. エントリポイント/デフォルトコマンド (Entrypoint/Command): コンテナ起動時にデフォルトで実行されるアプリケーションを定義します。

1.2 核心となる必須命令

Dockerfileを作成する際に最も頻繁に使用される命令は以下の通りです:

  • FROM: 以降の命令のためのベースイメージを設定します。イメージ全体の「土台」です。
    • 例:FROM ubuntu:20.04 (Ubuntu 20.04をベースにする)
    • 例:FROM node:16-alpine (軽量なAlpine LinuxベースのNode.js 16を使用する)
  • LABEL: キー・バリュー(key-value)形式でイメージにメタデータを追加します。
  • RUN: 現在のイメージの上に新しい「レイヤー(layer)」を作成してコマンドを実行し、結果をコミットします。主にパッケージのインストールやディレクトリ作成に使用されます。
    • 例:RUN apt-get update && apt-get install -y curl
  • COPY: ローカルファイルシステムのファイルやディレクトリ(ソース)を、イメージ内の指定したパス(ターゲット)にコピーします。
    • 例:COPY ./app /app
  • ADD: COPY と似ていますが、圧縮ファイルの自動解凍機能やURLからのダウンロード機能を備えています。推奨: 特殊な機能が必要ない限り、動作が透明な COPY を優先して使用してください。
  • WORKDIR: 作業ディレクトリを設定します。これ以降に出現する RUNCMDENTRYPOINTCOPYADD 命令に影響します。
    • 例:WORKDIR /app
  • EXPOSE: コンテナが実行時にリッスンするネットワークポートを宣言します。注意:これはあくまで「宣言」であり、ホスト側のポートを実際に開放するには、実行時に -p パラメータを使用する必要があります。
  • ENV: 環境変数を設定します。
    • 例:ENV APP_HOME /app
  • CMD: コンテナ起動時のデフォルトの実行コマンドを提供します。Dockerfile内に CMD は1つしか記述できません。複数ある場合は最後の一つのみが有効になります。
    • 例:CMD ["executable","param1","param2"] (推奨されるJSON配列形式)
  • ENTRYPOINT: コンテナを実行ファイルのように動作させるよう構成します。通常、メインコマンドを定義し、CMD と組み合わせて追加引数を受け取るために使用されます。
  • VOLUME: 指定した名前のマウントポイントを作成し、ホストや他のコンテナのディレクトリをマウントしてデータの永続化を実現します。
  • USER: イメージ実行時(および後続の RUNCMDENTRYPOINT)に使用するユーザー名またはUIDを指定します。
  • ARG: ビルド変数を定義します。docker build 時に --build-arg <変数名>=<値> で値を渡せます。

2. 命令の実行順序と複数行の記述

2.1 命令の順序とキャッシュメカニズム

Dockerfile内の命令の記述順序は、構築速度に決定的な影響を与えます。Dockerはキャッシュメカニズム (Caching) を使用して構築を高速化します。

Dockerfileの各命令は、イメージ内に新しい「レイヤー(Layer)」を作成します。次回の構築時、Dockerはある命令とその前のすべてのレイヤーに変更がないことを発見すると、その命令を再実行せずに以前のキャッシュレイヤーを直接再利用します。

ベストプラクティス:
キャッシュを最大限に活用するために、以下の順序で命令を並べるべきです:

  1. 変更が極めて少ない命令を前に置く(例:OSの基本パッケージのインストール)。
  2. 頻繁に変更される命令を後に置く(例:頻繁に修正するソースコードのコピー)。

これにより、Dockerはベースとなる大部分のレイヤーを再利用でき、コード変更があったレイヤーのみを再構築するため、ビルド時間を大幅に短縮できます。

2.2 複数行の記述

可読性を高めるために、1つのコマンドが長くなる場合はバックスラッシュ \ を使用して複数行に分割できます。

例:

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    curl \
    wget \
    vim

3. 実戦ケーススタディ

様々なシナリオでの実際のDockerfileの書き方を見てみましょう。

3.1 ケース1:シンプルなNode.jsアプリ

このDockerfileは、シンプルなNode.jsアプリケーションをパッケージ化するためのものです。

# 公式のNode.js環境をベースイメージとして使用
FROM node:16-alpine

# コンテナ内の作業ディレクトリを /app に設定
WORKDIR /app

# package.json と package-lock.json を作業ディレクトリにコピー
COPY package*.json ./

# 依存関係をインストール
RUN npm install

# カレントディレクトリのすべてのソースコードを作業ディレクトリにコピー
COPY . .

# 3000番ポートを公開することを宣言
EXPOSE 3000

# コンテナ起動時に実行するデフォルトコマンドを定義
CMD ["npm", "start"]

解説:

  • FROM node:16-alpine: Alpine Linuxベースの軽量イメージを使用することで、イメージサイズを劇的に削減します。
  • 先に COPY package*.json を行い RUN npm install してから COPY . . を行うのは、キャッシュを活用するためです。依存関係の定義はソースコードよりも更新頻度が低いため、コードの変更のみでライブラリを再ダウンロードするのを防げます。

3.2 ケース2:Python Flaskアプリ

Python Flaskアプリケーションをパッケージ化する例です。

# 公式のPython環境をベースイメージとして使用
FROM python:3.9-slim-buster

# 作業ディレクトリを /app に設定
WORKDIR /app

# 依存関係ファイルをコピー
COPY requirements.txt .

# 依存関係をインストール(キャッシュを保存しない設定)
RUN pip install --no-cache-dir -r requirements.txt

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

# 5000番ポートの公開を宣言
EXPOSE 5000

# Flask用の環境変数を設定
ENV FLASK_APP=app.py

# アプリ起動コマンドを定義
CMD ["flask", "run", "--host=0.0.0.0"]

解説:

  • --no-cache-dir: pipがダウンロードしたキャッシュを保存しないようにすることで、最終的なイメージサイズをさらに小さくします。
  • --host=0.0.0.0: Flaskがコンテナ内部だけでなく、外部からのリクエストも受け付けられるようにする設定です。

3.3 ケース3:Nginxによる静的サイトの構築

静的WebファイルをNginxサーバーでホストする例です。

# 公式のNginx環境をベースイメージとして使用
FROM nginx:stable-alpine

# デフォルトのNginx設定ファイルを削除
RUN rm -rf /etc/nginx/conf.d/*

# カスタムのNginx設定ファイルをコピー
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 静的サイトファイルをNginxのデフォルト公開ディレクトリにコピー
COPY html /usr/share/nginx/html

# 80番ポートの公開を宣言
EXPOSE 80

# Nginxイメージ自体に起動コマンドが含まれているため、CMDは不要

nginx.conf 設定ファイルの例:

上記のDockerfileと組み合わせるための nginx.conf の内容は以下のようになります:

server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }
}