第3章:Dockerfile

はじめに

Dockerfileは、イメージをビルドするための命令書です。本章では、Dockerfileの記法とベストプラクティスを学びます。

---

1. Dockerfileの基本

1.1 構文

# コメント
命令 引数

# 例
FROM alpine:3.18
RUN apk update && apk add nginx
COPY nginx.conf /etc/nginx/
EXPOSE 443
CMD ["nginx", "-g", "daemon off;"]

1.2 主要な命令

# ベースイメージ
FROM <image>:<tag>

# 環境変数
ENV <key>=<value>

# 作業ディレクトリ
WORKDIR /path/to/dir

# ファイルコピー
COPY <src> <dest>
ADD <src> <dest>    # URL対応、tar展開

# コマンド実行
RUN <command>

# ポート公開
EXPOSE <port>

# ボリューム
VOLUME ["/data"]

# ユーザー
USER <user>

# 実行コマンド
CMD ["executable", "param1"]
ENTRYPOINT ["executable", "param1"]

---

2. FROM命令

2.1 ベースイメージの選択

# 公式イメージ
FROM nginx:1.25-alpine
FROM php:8.2-fpm-alpine
FROM mariadb:10.11

# Alpine Linux(軽量)
FROM alpine:3.18

# Debian(互換性重視)
FROM debian:bullseye-slim

# 注意: :latest は禁止(Inception要件)
FROM alpine    # NG: 暗黙の:latest
FROM alpine:3.18  # OK: バージョン指定

2.2 Alpine vs Debian

Alpine Linux:
+ サイズ: 約5MB
+ セキュリティ(musl libc)
+ パッケージマネージャ: apk
- 一部互換性問題(glibc依存)

Debian:
+ 互換性が高い(glibc)
+ 豊富なパッケージ
+ パッケージマネージャ: apt
- サイズ: 約100MB

Inception推奨:
- Alpine または Debian
- penultimate stable version

---

3. RUN命令

3.1 シェル形式 vs Exec形式

# シェル形式(シェル経由)
RUN apk update && apk add nginx

# Exec形式(直接実行)
RUN ["apk", "add", "nginx"]

# シェル形式の展開
RUN echo "Hello, $USER"  # 変数展開される
RUN ["echo", "Hello, $USER"]  # 文字列リテラル

3.2 レイヤー最適化

# NG: 複数レイヤー
RUN apk update
RUN apk add nginx
RUN apk add php
RUN rm -rf /var/cache/apk/*

# OK: 単一レイヤー
RUN apk update && \
    apk add --no-cache nginx php && \
    rm -rf /var/cache/apk/*

# 理由:
# - レイヤー数削減
# - キャッシュのクリーンアップ
# - イメージサイズ削減

3.3 キャッシュ活用

# 変更頻度の低いものを先に
FROM alpine:3.18

# システムパッケージ(変更少ない)
RUN apk add --no-cache nginx php

# 設定ファイル(時々変更)
COPY nginx.conf /etc/nginx/

# アプリケーションコード(頻繁に変更)
COPY app/ /var/www/html/

# キャッシュが有効な限り再ビルド不要

---

4. COPY vs ADD

4.1 違い

# COPY: 単純なファイルコピー
COPY local/file.txt /container/file.txt
COPY dir/ /container/dir/

# ADD: 追加機能あり
ADD https://example.com/file.tar.gz /tmp/  # URL対応
ADD archive.tar.gz /app/                    # 自動展開

# 推奨: 基本はCOPYを使用
# ADDはtar展開が必要な場合のみ

4.2 .dockerignore

# .dockerignore
.git
.gitignore
Dockerfile*
docker-compose*.yml
*.md
*.log
.env*
node_modules
__pycache__

# 効果:
# - ビルドコンテキスト削減
# - 不要ファイルの除外
# - ビルド高速化

---

5. CMD vs ENTRYPOINT

5.1 違い

# CMD: デフォルトコマンド(上書き可能)
CMD ["nginx", "-g", "daemon off;"]

# docker run image           → nginx -g daemon off;
# docker run image /bin/sh   → /bin/sh(上書き)

# ENTRYPOINT: 固定のエントリーポイント
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

# docker run image           → nginx -g daemon off;
# docker run image -v        → nginx -v(引数追加)

5.2 使い分け

# パターン1: 実行ファイルとして
ENTRYPOINT ["python", "app.py"]
# docker run image --port 8080

# パターン2: ラッパースクリプト
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

# パターン3: デフォルトコマンド
CMD ["nginx", "-g", "daemon off;"]
# 柔軟だが意図しない上書きに注意

---

6. Inceptionサービス用Dockerfile

6.1 Nginx

FROM alpine:3.18

# パッケージインストール
RUN apk update && \
    apk add --no-cache nginx openssl && \
    rm -rf /var/cache/apk/*

# SSL証明書生成
RUN mkdir -p /etc/nginx/ssl && \
    openssl req -x509 -nodes -days 365 \
    -newkey rsa:2048 \
    -keyout /etc/nginx/ssl/nginx.key \
    -out /etc/nginx/ssl/nginx.crt \
    -subj "/C=JP/ST=Tokyo/L=Tokyo/O=42/CN=login.42.fr"

# 設定ファイル
COPY conf/nginx.conf /etc/nginx/nginx.conf

# ポート公開
EXPOSE 443

# フォアグラウンドで実行
CMD ["nginx", "-g", "daemon off;"]

6.2 WordPress (PHP-FPM)

FROM alpine:3.18

# PHP と依存関係
RUN apk update && \
    apk add --no-cache \
    php82 \
    php82-fpm \
    php82-mysqli \
    php82-json \
    php82-curl \
    php82-dom \
    php82-exif \
    php82-fileinfo \
    php82-mbstring \
    php82-openssl \
    php82-xml \
    php82-zip \
    php82-phar \
    wget \
    mariadb-client && \
    rm -rf /var/cache/apk/*

# WP-CLI インストール
RUN wget https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
    chmod +x wp-cli.phar && \
    mv wp-cli.phar /usr/local/bin/wp

# PHP-FPM 設定
COPY conf/www.conf /etc/php82/php-fpm.d/www.conf

# セットアップスクリプト
COPY tools/setup.sh /setup.sh
RUN chmod +x /setup.sh

WORKDIR /var/www/html

EXPOSE 9000

ENTRYPOINT ["/setup.sh"]

6.3 MariaDB

FROM alpine:3.18

# MariaDB インストール
RUN apk update && \
    apk add --no-cache mariadb mariadb-client && \
    rm -rf /var/cache/apk/*

# 設定ファイル
COPY conf/my.cnf /etc/my.cnf.d/mariadb-server.cnf

# 初期化スクリプト
COPY tools/init.sh /init.sh
RUN chmod +x /init.sh

# データディレクトリ
RUN mkdir -p /var/lib/mysql && \
    chown -R mysql:mysql /var/lib/mysql

EXPOSE 3306

ENTRYPOINT ["/init.sh"]

---

7. 初期化スクリプト

7.1 WordPress setup.sh

#!/bin/sh

# WordPressがなければダウンロード
if [ ! -f /var/www/html/wp-config.php ]; then
    # ダウンロード
    wp core download --allow-root

    # wp-config.php 生成
    wp config create \
        --dbname="${MYSQL_DATABASE}" \
        --dbuser="${MYSQL_USER}" \
        --dbpass="${MYSQL_PASSWORD}" \
        --dbhost="${WORDPRESS_DB_HOST}" \
        --allow-root

    # データベース接続待機
    while ! mariadb -h"${WORDPRESS_DB_HOST}" -u"${MYSQL_USER}" \
           -p"${MYSQL_PASSWORD}" -e "SELECT 1" > /dev/null 2>&1; do
        echo "Waiting for MariaDB..."
        sleep 2
    done

    # WordPress インストール
    wp core install \
        --url="${DOMAIN_NAME}" \
        --title="${WP_TITLE}" \
        --admin_user="${WP_ADMIN_USER}" \
        --admin_password="${WP_ADMIN_PASSWORD}" \
        --admin_email="${WP_ADMIN_EMAIL}" \
        --allow-root

    # 追加ユーザー作成
    wp user create "${WP_USER}" "${WP_USER_EMAIL}" \
        --user_pass="${WP_USER_PASSWORD}" \
        --role=author \
        --allow-root
fi

# PHP-FPM 起動
exec php-fpm82 -F

7.2 MariaDB init.sh

#!/bin/sh

# 初回起動時のみ初期化
if [ ! -d "/var/lib/mysql/mysql" ]; then
    # データベース初期化
    mysql_install_db --user=mysql --datadir=/var/lib/mysql

    # 一時的にサーバー起動
    mysqld --user=mysql --datadir=/var/lib/mysql &
    pid=$!

    # 起動待機
    while ! mysqladmin ping --silent; do
        sleep 1
    done

    # データベースとユーザー作成
    mysql -u root <<EOF
CREATE DATABASE IF NOT EXISTS ${MYSQL_DATABASE};
CREATE USER IF NOT EXISTS '${MYSQL_USER}'@'%' IDENTIFIED BY '${MYSQL_PASSWORD}';
GRANT ALL PRIVILEGES ON ${MYSQL_DATABASE}.* TO '${MYSQL_USER}'@'%';
ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}';
FLUSH PRIVILEGES;
EOF

    # サーバー停止
    mysqladmin -u root -p"${MYSQL_ROOT_PASSWORD}" shutdown
fi

# 通常起動
exec mysqld --user=mysql --datadir=/var/lib/mysql

---

8. マルチステージビルド

8.1 概念

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

# 実行ステージ
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

# 結果:
# - ビルドツール不要
# - 最終イメージが軽量
# - セキュリティ向上

8.2 Inceptionでの応用

# ビルドステージ(ツール準備)
FROM alpine:3.18 AS builder
RUN apk add --no-cache curl
RUN curl -O https://wordpress.org/latest.tar.gz
RUN tar xzf latest.tar.gz

# 実行ステージ
FROM alpine:3.18
RUN apk add --no-cache php82 php82-fpm
COPY --from=builder /wordpress /var/www/html

---

まとめ

本章で学んだこと:

  • 基本命令: FROM, RUN, COPY, CMD
  • ベースイメージ: Alpine vs Debian
  • レイヤー最適化: キャッシュ活用
  • COPY vs ADD: 使い分け
  • CMD vs ENTRYPOINT: 実行形式
  • サービス別Dockerfile: Nginx, WordPress, MariaDB
  • 初期化スクリプト: エントリーポイント
  • マルチステージビルド: イメージ最適化

次章では、Docker Composeを学びます。