Docker をゼロからプロレベルまで: Dockerfile、Compose、CI/CD の 100 の例

Docker をゼロからプロレベルまで: Dockerfile、Compose、CI/CD の 100 の例

Docker はアプリケーションと必要なものすべてをコンテナーに詰め込み、同じソフトウェアをノートパソコン、サーバー、クラウドで同じように動かします。この記事では最初のコンテナーから、本番環境を意識した構成まで 100 の例で進みます。Docker CLI、イメージとレジストリ、マルチステージビルドを使った Dockerfile、ボリュームとネットワーク、複数コンテナーのアプリのための Docker Compose、設定とシークレット、リソース制限とヘルスチェック、コンテナーのセキュリティ、デバッグ、CI/CD パイプライン、そして Kubernetes と Swarm の入門を扱います。正直に伝えると、この記事を準備したコンピューターには Docker がインストールされていなかったため、100 の例はここでは一つも実行していません。すべてのコマンドとファイルは公式の Docker の構文に合わせて書き、読んで確認しています。信頼する前に、必ずご自身で実行してください。

必要なもの

Windows または macOS の Docker Desktop、または Linux の Docker Engine と Docker Compose プラグイン(docker compose version が動けば十分です)。Docker 拡張機能を入れた Visual Studio Code などのエディターがあると便利です。始めるだけならコマンドラインの基本知識で足ります。一部の例ではサンプルの Python または Node.js プロジェクトを使い、プライベートレジストリを使う例もあります。registry.example.com のようなプレースホルダーを使い、本物のパスワードをコマンドに貼り付けないでください。

例の使い方

bash の例は Docker CLI と Docker Compose を使います。Dockerfile の例はプロジェクトのフォルダーの Dockerfile という名前のファイルに書きます。YAML の例は compose.yaml か、本文で指定された Kubernetes のファイルに書きます。

トピック 1: Docker の基本

コンテナーは、独自のファイルシステムとネットワークを持つ隔離されたプロセスで、イメージから作られます。中心となる考え方を学び、インストールを確認し、最初のコンテナーを実行します。

イメージ、コンテナー、レジストリ

イメージはレイヤーで構成された読み取り専用のテンプレートです。コンテナーはイメージの実行中のインスタンスです。Docker Hub や Azure Container Registry などのレジストリは、他のマシンがダウンロードできるようイメージを保存します。

コンテナーが役立つ理由

コンテナーはアプリをライブラリと一緒にまとめるため、どのマシンでも同じように動きます。数秒で起動し、ホストのカーネルを共有するので、仮想マシンよりはるかに少ないメモリで済みます。

インストールの確認

docker version を実行すると、クライアントとサーバーの両方が表示されます。サーバーの行がない場合は Docker Desktop または Docker サービスが動いていないので、コンテナーのコマンドを実行する前に起動してください。

図解

Docker が Dockerfile からアプリケーションを実行中のコンテナーにまで運ぶ流れ: Dockerfile はイメージにビルドされ、イメージはレジストリに push され、ホストがそれを pull してコンテナーとして実行します
Docker が Dockerfile からアプリケーションを実行中のコンテナーにまで運ぶ流れ: Dockerfile はイメージにビルドされ、イメージはレジストリに push され、ホストがそれを pull してコンテナーとして実行します

例 1: Docker がインストールされているか確認

クライアントが Docker エンジンに接続できるかを確かめます。

docker --version
docker info --format "server: {{.ServerVersion}}"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 2: hello-world を実行

最初のコンテナを、1 つのコマンドでダウンロードして実行します。

docker run hello-world

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 3: イメージをダウンロード

コンテナを起動せずにイメージだけを取得します。

docker pull nginx:1.27

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 4: ローカルのイメージを一覧表示

このコンピューターに保存されているイメージを確認します。

docker images
docker image ls --filter "reference=nginx"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 5: イメージを調べる

イメージのアーキテクチャと OS を読み取ります。

docker image inspect nginx:1.27 --format "arch={{.Architecture}} os={{.Os}}"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 6: ディスク使用量を確認

イメージ、コンテナ、ボリュームが使う容量を確認します。

docker system df

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 2: コンテナーの実行と管理

docker run コマンドはイメージからコンテナーを作ります。ポートを公開し、ログを読み、内部でコマンドを実行し、不要になったコンテナーを片付けます。

デタッチモードと対話モード

-d はコンテナーをバックグラウンドで実行し、-it はターミナルを接続します。--rm を付けると停止時にコンテナーを削除するので、短い試行に向いています。

ポートと名前

-p ホスト:コンテナー でポートを公開します。8080:80 はホストの 8080 番をコンテナーの 80 番へ送ります。--name は後のコマンドで使える分かりやすい名前を付けます。

ライフサイクルと片付け

停止したコンテナーは削除するまでデータを残します。docker ps -a で確認し、docker rm で削除します。ビルドを頻繁に行うマシンでは定期的に prune してください。

例 7: コンテナをバックグラウンドで実行

デタッチモードでコンテナを起動して一覧表示します。

docker run -d --name web nginx:1.27
docker ps

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 8: ポートを公開

ホストからコンテナ内の Web サーバーにアクセスします。

docker run -d --name web-8080 -p 8080:80 nginx:1.27
curl http://localhost:8080

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 9: 一時的なシェルを起動

--rm を使い、終了時にコンテナを削除します。

docker run -it --rm alpine:3.20 sh

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 10: コンテナを一覧表示

実行中と停止したコンテナを表示します。

docker ps
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 11: コンテナのログを読む

最後の数行を表示するか、出力をリアルタイムで追います。

docker logs --tail 50 web
docker logs -f web

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 12: コンテナ内でコマンドを実行

実行中のコンテナでシェルを開くか、1 つのコマンドを実行します。

docker exec -it web sh
docker exec web cat /etc/nginx/nginx.conf

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 13: 停止、起動、再起動

コンテナのライフサイクルを制御します。

docker stop web
docker start web
docker restart web

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 14: コンテナを削除

不要になったコンテナを削除します。

docker rm -f web web-8080
docker container prune -f

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 3: イメージとレジストリ

イメージには名前、タグ、ダイジェストがあります。タグは変わりますが、ダイジェストは変わりません。ソフトウェアを正確にタグ付け、共有、再現するには両方が必要です。

タグとダイジェスト

latest は単なるタグで、発行者が更新するたびに移動します。sha256 で始まる長いハッシュのダイジェストは、まったく同じ一つのイメージを特定します。これが再現可能なデプロイの方法です。

レジストリへの push

ログインし、レジストリのフルアドレスを付けてイメージにタグを付け、push します。他のマシンは同じイメージを名前で pull できます。秘密情報を含むイメージは決して push しないでください。

イメージの履歴を読む

docker history は各レイヤー、それを作った命令、サイズを表示します。大きすぎるレイヤーを見つけたり、パスワードがイメージに書き込まれていないか確認したりするのに役立ちます。

例 15: イメージにタグを付ける

自分のイメージに名前とバージョンを付けます。

docker tag nginx:1.27 mycompany/nginx:1.0.0
docker images mycompany/nginx

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 16: レジストリにプッシュ

他のマシンが取得できるようにイメージをアップロードします。

docker login registry.example.com
docker tag mycompany/nginx:1.0.0 registry.example.com/team/nginx:1.0.0
docker push registry.example.com/team/nginx:1.0.0

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 17: Azure Container Registry にプッシュ

Azure CLI でログインし、レジストリにプッシュします。

az acr login --name <registry-name>
docker tag mycompany/nginx:1.0.0 <registry-name>.azurecr.io/nginx:1.0.0
docker push <registry-name>.azurecr.io/nginx:1.0.0

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 18: ローカルのレジストリを起動

テスト用に自分のコンピューターでプライベートレジストリを起動します。

docker run -d -p 5000:5000 --restart unless-stopped --name registry registry:2
docker tag nginx:1.27 localhost:5000/nginx:1.27
docker push localhost:5000/nginx:1.27

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 19: ダイジェストでイメージを固定

ダイジェストは変わらない指紋で、タグとは違います。

docker pull nginx:1.27
docker image inspect nginx:1.27 --format "{{index .RepoDigests 0}}"
docker run -d nginx@sha256:<digest-from-the-line-above>

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 20: イメージのレイヤーを読む

どの命令がどのレイヤーを作り、どれだけ大きいかを確認します。

docker history nginx:1.27
docker history --no-trunc --format "{{.Size}}\t{{.CreatedBy}}" nginx:1.27

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 4: Dockerfile を書く

Dockerfile はテキストのレシピです。命令ごとにレイヤーが作られるため、命令の順序と内容が、サイズ、再ビルドの速さ、コンテナー起動時に動くものを決めます。

基本の命令

FROM はベースイメージを選びます。COPY と RUN はファイルとコマンドを追加します。WORKDIR は後続の命令のフォルダーを設定します。CMD と ENTRYPOINT はコンテナーが何を実行するかを定義します。

ARG と ENV の違い

ARG はイメージのビルド中だけ存在します。ENV はイメージに残り、実行中のコンテナーから見えます。ビルド時の選択には ARG を、プログラムが読む設定には ENV を使います。

ビルドとタグ付け

docker build -t 名前:タグ . はフォルダーをビルダーに渡します。Dockerfile がコピーしたファイルだけが使われるため、.dockerignore でビルドコンテキストを小さく保ちます。

例 21: 最小の Dockerfile

最小限の有用なレシピ: ベースイメージと 1 つのコマンド。

FROM alpine:3.20
CMD ["echo", "Hello from a container"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 22: イメージをビルドして実行

Dockerfile からイメージを作り、起動します。

docker build -t hello-image:1.0 .
docker run --rm hello-image:1.0

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 23: Python アプリの Dockerfile

依存関係をインストールし、コードを軽量イメージにコピーします。

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 24: Node.js アプリの Dockerfile

本番用パッケージをインストールし、ポートを公開します。

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

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 25: ARG と ENV

ビルド時の値と実行時の環境変数を設定します。

FROM alpine:3.20
ARG APP_VERSION=dev
ENV APP_VERSION=$APP_VERSION
ENV APP_PORT=8080
CMD ["sh", "-c", "echo version=$APP_VERSION port=$APP_PORT"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 26: ビルド引数を渡す

ファイルを編集せずにビルド時にバージョンを変えます。

docker build --build-arg APP_VERSION=1.4.2 -t vars-image:1.4.2 .
docker run --rm -e APP_PORT=9000 vars-image:1.4.2

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 27: WORKDIR と COPY

作業フォルダーを決め、ファイルをイメージにコピーします。

FROM alpine:3.20
WORKDIR /srv/site
COPY ./site/ ./
# ADD can unpack archives, but COPY is clearer for plain files
RUN ls -la /srv/site

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 28: ENTRYPOINT と CMD

固定のプログラムと既定の引数を選びます。

FROM alpine:3.20
RUN apk add --no-cache curl
ENTRYPOINT ["curl", "-s"]
CMD ["https://example.com"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 5: イメージのベストプラクティス

良いイメージは小さく、再現可能で、速くビルドでき、安全に実行できます。マルチステージビルド、レイヤーの順序、固定したバージョン、root 以外のユーザー、ヘルスチェックが主な実践です。

マルチステージビルド

ビルドステージにはコンパイラーとツールを置き、最終ステージには結果だけをコピーします。最終イメージは小さく保たれ、攻撃者に使われうるソースコードやビルドツールを含みません。

レイヤーのキャッシュ

Docker は命令と入力が変わらなければレイヤーを再利用し、最初の変更以降はすべて再ビルドします。ソースコードをコピーする前に、依存関係のファイルをコピーしてインストールしてください。

ユーザー、バージョン、チェック

数値の root 以外のユーザーで実行し、ベースイメージのバージョンを固定し、所有者とバージョンのラベルを付け、HEALTHCHECK を使って Docker がアプリの実際の動作を報告できるようにします。

例 29: .dockerignore で除外

秘密情報、Git データ、依存関係をビルドに含めません。

cat > .dockerignore <<'EOF'
.git
node_modules
*.log
.env
EOF
docker build -t app:1.0 .

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 30: Go 向けマルチステージビルド

大きなイメージでコンパイルし、実行ファイルだけを配布します。

FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod ./
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app .

FROM gcr.io/distroless/static-debian12
COPY --from=build /out/app /app
ENTRYPOINT ["/app"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 31: フロントエンド向けマルチステージビルド

アセットをビルドし、Nginx で配信します。

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 32: root 以外のユーザーで実行

攻撃を受けた場合の被害を抑えます。

FROM python:3.12-slim
RUN useradd --create-home --uid 10001 appuser
WORKDIR /home/appuser/app
COPY --chown=appuser:appuser . .
USER appuser
CMD ["python", "app.py"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 33: ベースイメージのバージョンを固定

変わり続ける latest タグによる予期せぬ変化を避けます。

# Avoid "latest": it changes without warning
FROM python:3.12.5-slim-bookworm
# Pin system packages when the build must be reproducible
RUN apt-get update && apt-get install -y --no-install-recommends curl=8.* \
 && rm -rf /var/lib/apt/lists/*

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 34: キャッシュが効くようにレイヤーを並べる

依存関係のファイルを先にコピーして、ビルドを速くします。

FROM node:20-alpine
WORKDIR /app
# Dependencies change less often than source code, so copy them first
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["npm", "start"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 35: イメージにラベルを追加

タイトル、バージョン、所有者をイメージ内に記録します。

FROM alpine:3.20
LABEL org.opencontainers.image.title="sample-api" \
      org.opencontainers.image.version="1.4.2" \
      org.opencontainers.image.maintainer="[email protected]"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 36: HEALTHCHECK

アプリが正常に動いているかを Docker に伝えます。

FROM nginx:1.27-alpine
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget -qO- http://localhost/ || exit 1

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 6: ボリュームと永続データ

コンテナーは使い捨てですが、データベースやアップロードされたファイルは使い捨てにできません。ボリュームとバインドマウントは、データをコンテナーの書き込み可能レイヤーの外に保つため、再起動やアップグレードの後も残ります。

名前付きボリューム

名前付きボリュームは Docker が管理し、コンテナーの外に保存されます。データベースに適しています。コンテナーを削除してもデータは残り、ヘルパーコンテナーでバックアップできます。

バインドマウント

バインドマウントは、コンピューター上のフォルダーをコンテナーに接続します。コードの変更がすぐ反映されるので開発に便利ですが、コンテナーがホストのファイルへ直接アクセスできるため、読み取りだけなら :ro を使ってください。

バックアップと一時データ

ボリュームをマウントして tar ファイルを書き出す小さなコンテナーを実行して、バックアップします。メモリに置き、コンテナーの停止時に消えるべき作業ファイルには tmpfs を使います。

例 37: 名前付きボリュームを作成

コンテナを削除しても残すべきデータを保存します。

docker volume create app-data
docker volume ls
docker volume inspect app-data

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 38: データベースで名前付きボリュームを使う

コンテナを再起動しても PostgreSQL のデータを安全に保ちます。

docker run -d --name db -e POSTGRES_PASSWORD=<strong-password> \
  -v app-data:/var/lib/postgresql/data postgres:16
docker exec db psql -U postgres -c "select 1"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 39: ホストのフォルダーをバインドマウント

開発中にコンピューターのフォルダーを共有します。

docker run --rm -it -v "$(pwd)/site:/usr/share/nginx/html:ro" -p 8081:80 nginx:1.27-alpine

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 40: 読み取り専用のマウント

設定の読み取りは許可し、変更はさせません。

docker run --rm -v /srv/config:/etc/app:ro alpine:3.20 ls -l /etc/app

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 41: ボリュームをバックアップ

ヘルパーコンテナでボリュームのデータを tar ファイルに保存します。

docker run --rm -v app-data:/data -v "$(pwd):/backup" alpine:3.20 \
  tar czf /backup/app-data-backup.tgz -C /data .

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 42: ボリュームを復元

バックアップしたデータを新しいボリュームに戻します。

docker volume create app-data-restored
docker run --rm -v app-data-restored:/data -v "$(pwd):/backup" alpine:3.20 \
  tar xzf /backup/app-data-backup.tgz -C /data

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 43: 一時データに tmpfs を使う

一時ファイルをメモリに置き、停止時に消えるようにします。

docker run --rm --tmpfs /tmp:rw,size=64m alpine:3.20 sh -c "echo scratch > /tmp/x && cat /tmp/x"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 7: コンテナーのネットワーク

Docker はコンテナーに独自のネットワークを与えます。ユーザー定義ネットワークでは名前でコンテナー同士を見つけられ、ポートの公開で外部から届くサービスを決めます。

ブリッジとユーザー定義ネットワーク

既定のブリッジネットワークは、コンテナーに IP アドレスだけを与えます。ユーザー定義のブリッジネットワークは DNS も提供するので、http://api や redis://cache のように、あるコンテナーが別のコンテナーを名前で呼べます。

安全なポートの公開

既定ではポートはすべてのインターフェースに公開されます。データベースをご自身のコンピューターだけに閉じるには 127.0.0.1 にバインドします。EXPOSE は文書化するだけで、ポートを開くことはありません。

複数のネットワーク

一つのコンテナーは複数のネットワークに参加できます。データベースは back ネットワークに、Web プロキシは front ネットワークに置くと、データベースが必要なサービスだけが接続できます。

例 44: ネットワークを一覧表示と確認

Docker が作ったネットワークと IP アドレス範囲を確認します。

docker network ls
docker network inspect bridge --format "{{range .IPAM.Config}}{{.Subnet}}{{end}}"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 45: ユーザー定義ネットワークを作成

関連するコンテナに専用のプライベートネットワークを与えます。

docker network create app-net
docker run -d --name api --network app-net nginx:1.27
docker run --rm --network app-net alpine:3.20 wget -qO- http://api

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 46: 名前でコンテナを見つける

ネットワーク上でコンテナ名をホスト名として使います。

docker run -d --name cache --network app-net redis:7-alpine
docker run --rm --network app-net redis:7-alpine redis-cli -h cache ping

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 47: コンテナを 2 つ目のネットワークに接続

1 つのサービスから 2 つの別々のネットワークに届くようにします。

docker network create back-net
docker network connect back-net api
docker inspect api --format "{{json .NetworkSettings.Networks}}"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 48: 1 つのインターフェースだけに公開

データベースをこのコンピューターからだけ接続できるようにします。

docker run -d -p 127.0.0.1:5432:5432 --name pg -e POSTGRES_PASSWORD=<strong-password> postgres:16

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 49: EXPOSE と publish の違い

EXPOSE はドキュメントにすぎず、-p でホストから届くようになります。

FROM node:20-alpine
# EXPOSE only documents the port; it does not open it to the host
EXPOSE 3000
CMD ["node", "server.js"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 50: ホストのネットワークを使う

特別な場合に限り、Linux でホストのネットワークを共有します。

docker run --rm --network host alpine:3.20 sh -c "ip addr show eth0 | head -3"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 8: Docker Compose の基本

Compose は複数コンテナーのアプリを 1 つの YAML ファイルで記述します。1 つのコマンドで、プロジェクト内のすべてのサービス、ネットワーク、ボリュームを起動、停止、確認できます。

compose ファイル

services キーは各コンテナーを並べます。各サービスは image または build、そして ports、environment、volumes、networks を持ちます。Compose はプロジェクト用の共有ネットワークを自動で作ります。

コマンド

docker compose up -d でプロジェクトを開始し、docker compose ps で状態を表示、docker compose logs -f で出力を追い、docker compose down で停止してコンテナーと既定のネットワークを削除します。

override ファイル

compose.yaml は共通の設定を、compose.override.yaml は自動で統合される設定を持ちます。ローカルの変更は override に書き、メインのファイルをどの環境でも同じに保ってください。

例 51: 最小の Compose ファイル

1 つの YAML ファイルで Web サービスを定義します。

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

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 52: Compose プロジェクトの開始と停止

複数コンテナーのアプリ全体を起動し、停止します。

docker compose up -d
docker compose ps
docker compose down

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 53: Compose 内でイメージをビルド

Dockerfile から Compose にイメージを作らせます。

services:
  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    ports:
      - "3000:3000"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 54: Compose の環境変数

ファイル内で各サービスの設定を指定します。

services:
  api:
    build: ./api
    environment:
      APP_ENV: development
      LOG_LEVEL: info

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 55: 別のサービスの起動を待つ

depends_on で起動順序を制御します。

services:
  api:
    build: ./api
    depends_on:
      - db
  db:
    image: postgres:16

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 56: Compose の名前付きボリューム

プロジェクトを再起動してもデータベースのファイルを保持します。

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: <strong-password>
    volumes:
      - db-data:/var/lib/postgresql/data
volumes:
  db-data:

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 57: ログ、exec、一度だけのコマンド

プロジェクト内でテストやツールを実行します。

docker compose logs -f api
docker compose exec db psql -U postgres
docker compose run --rm api npm test

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 58: 開発用の override ファイル

本番の設定とローカルの変更を分けて管理します。

# compose.yaml holds the shared settings
services:
  api:
    build: ./api
# compose.override.yaml is merged automatically on "docker compose up"
services:
  api:
    volumes:
      - ./api:/app
    environment:
      DEBUG: "1"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 9: 実際のアプリのための Compose

実際のアプリには Web 層、データベース、キャッシュ、プロキシがあります。Compose はそれらをつなぎ、準備ができるまで待たせ、容量が必要な部分だけを拡張できるようにします。

Web、データベース、キャッシュ

各サービスに一つの役割を持たせ、データベースのファイルは名前付きボリュームに置き、各サービスには名前でアクセスします。ホスト名を直接書き込まず、接続文字列は環境変数で渡します。

起動順序と健全性

depends_on はコンテナーの起動だけを待ちます。データベースに healthcheck を追加し、condition: service_healthy を使うと、データベースが接続を受け付けてからアプリが起動します。

スケールと分離

docker compose up --scale は、状態を持たないサービスのコピー数を変えます。公開プロキシが API には届くが、データベースには直接届かないようにネットワークを分けます。

例 59: 任意のサービス用のプロファイル

管理ツールは必要なときだけ起動します。

services:
  api:
    build: ./api
  pgadmin:
    image: dpage/pgadmin4
    profiles: ["tools"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 60: プロファイルを開始

コマンドラインから任意のサービスを有効にします。

docker compose up -d
docker compose --profile tools up -d

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 61: PostgreSQL を使う Web アプリ

データベースと永続データを備えた完全なアプリです。

services:
  web:
    build: ./web
    ports:
      - "8000:8000"
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: shop
      POSTGRES_USER: shop
      POSTGRES_PASSWORD: <strong-password>
    volumes:
      - pg-data:/var/lib/postgresql/data
volumes:
  pg-data:

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 62: キャッシュとして Redis を追加

サービス名で高速なキーバリューストアに接続します。

services:
  web:
    build: ./web
    environment:
      REDIS_URL: redis://cache:6379
  cache:
    image: redis:7-alpine
    command: ["redis-server", "--save", "", "--appendonly", "no"]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 63: リバースプロキシとしての Nginx

1 つの入口から Web トラフィックをアプリへ送ります。

services:
  proxy:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
    volumes:
      - ./proxy/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app
  app:
    build: ./app

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 64: データベースが正常になるまで待つ

データベースがクエリを受け付けてからアプリを起動します。

services:
  api:
    build: ./api
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: <strong-password>
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 10

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 10: 設定とシークレット

設定は環境ごとに変わり、シークレットは決してイメージや Git に入ってはいけません。環境変数、env ファイル、シークレットファイルを使い、プログラムで安全に読み込みます。

環境変数と .env ファイル

Compose は compose ファイル内の変数の置換のために .env ファイルを読みます。.env を .gitignore に加え、プレースホルダーの値を入れた .env.example をコミットして、他の開発者が何を設定すべきか分かるようにします。

ファイルとしてのシークレット

ファイルベースのシークレットはコンテナー内の /run/secrets 以下にマウントされます。ファイルから読むと、値が docker inspect の出力やプロセス一覧に現れません。環境変数はそこから漏れることがあります。

シークレットをイメージに焼き込まない

レイヤーにコピーされた値は、後の手順で削除してもイメージの履歴に残ります。シークレットは実行時に渡し、docker history で履歴を確認し、一度でも push したシークレットは必ずローテーションしてください。

例 65: サービスをスケールする

ワーカーを複数のコピーで実行します。

docker compose up -d --scale worker=3
docker compose ps worker

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 66: フロントエンドとバックエンドのネットワーク

データベースを公開ネットワークから隔離します。

services:
  proxy:
    image: nginx:1.27-alpine
    networks: [front]
    ports: ["80:80"]
  api:
    build: ./api
    networks: [front, back]
  db:
    image: postgres:16
    networks: [back]
networks:
  front:
  back:

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 67: .env から変数を読み込む

ローカルの値を Compose ファイルの外に置きます。

cat > .env <<'EOF'
POSTGRES_PASSWORD=<strong-password>
APP_PORT=8000
EOF
docker compose config

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 68: env_file を使う

設定のまとまりをファイルから読み込みます。

services:
  api:
    build: ./api
    env_file:
      - ./config/api.env

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 69: Compose のシークレットファイル

パスワードをイメージに入れずにサービスへ渡します。

services:
  api:
    build: ./api
    secrets:
      - db_password
secrets:
  db_password:
    file: ./secrets/db_password.txt

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 70: Python で設定を読む

既定値を使い、環境変数の文字列を数値に変換します。

import os

db_host = os.environ.get("DB_HOST", "localhost")
db_port = int(os.environ.get("DB_PORT", "5432"))
print(f"connect to {db_host}:{db_port}")

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 71: Python でシークレットファイルを読む

コンテナー内のシークレットのパスからパスワードを読み込みます。

from pathlib import Path

secret_file = Path("/run/secrets/db_password")
password = secret_file.read_text(encoding="utf-8").strip()
print("password length:", len(password))

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 72: 実行時の設定

1 回の実行で値を上書きし、イメージの履歴を確認します。

docker run --rm -e LOG_LEVEL=debug -e DB_HOST=db.internal app:1.0
docker history --no-trunc app:1.0 | grep -i password || echo "no password in the layers"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 11: リソース、ヘルス、ログ

制限を設けないと、コンテナーはホストのメモリと CPU をすべて使えてしまいます。制限、再起動ポリシー、ヘルスチェック、ログのローテーションで、サービスを安定させ、ディスクが埋まるのを防ぎます。

メモリと CPU の制限

--memory は RAM を使いすぎるコンテナーを止め、--cpus は CPU 時間の取り分を制限します。推測ではなく、docker stats で測定した使用量から制限を決めてください。

再起動ポリシー

unless-stopped はクラッシュやホストの再起動の後にコンテナーを再起動しますが、自分で停止した場合は再起動しません。長時間動くサービスに使い、繰り返すクラッシュは再起動に頼らず原因を直してください。

ログとヘルス

Docker はコンテナーの出力をログドライバーに保存します。json-file のサイズは max-size と max-file で制限します。ヘルスの状態は starting、healthy、unhealthy を示し、ロードバランサーやオーケストレーターが使えます。

例 73: メモリの上限

暴走したプロセスが RAM を使い切らないようにします。

docker run -d --name api-mem --memory 256m --memory-swap 256m nginx:1.27-alpine
docker stats --no-stream api-mem

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 74: CPU の上限

コンテナーに CPU コア半分を割り当てます。

docker run -d --name api-cpu --cpus 0.5 nginx:1.27-alpine

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 75: 再起動ポリシー

クラッシュや再起動の後にサービスを起動し直します。

docker run -d --name worker --restart unless-stopped app:1.0
docker inspect worker --format "{{.HostConfig.RestartPolicy.Name}}"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 76: ログサイズの上限

ログファイルでディスクが埋まらないようにします。

docker run -d --name api-logs \
  --log-driver json-file --log-opt max-size=10m --log-opt max-file=3 nginx:1.27-alpine

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 77: ヘルスステータスを読む

コンテナーが正常か、起動中か、異常かを確認します。

docker ps --format "table {{.Names}}\t{{.Status}}"
docker inspect --format "{{.State.Health.Status}}" api

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 78: 未使用のオブジェクトを整理

停止したコンテナーと未使用のイメージを安全に削除します。

docker container prune -f
docker image prune -f
docker volume ls -qf dangling=true

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 12: コンテナーのセキュリティ

コンテナーはホストのカーネルを共有するため、不注意な設定で攻撃者に意図以上の権限を与えてしまうことがあります。最小権限、読み取り専用のファイルシステム、スキャン、最新のベースイメージが、そのリスクを減らします。

最小権限

root 以外のユーザーで実行し、不要な Linux の capability は --cap-drop ALL で落とし、必要なものだけを戻します。no-new-privileges を使い、プロセスが権限を得られないようにします。

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

--read-only は、コンテナーが自分のファイルを変更するのを防ぎます。書き込みが必要な少数のディレクトリ、例えば /tmp には tmpfs のマウントを使います。

スキャンと更新

Docker Scout などでイメージをスキャンし、修正済みのベースイメージを取り込むため定期的に再ビルドし、重大な脆弱性が見つかったらビルドを失敗させます。明確な理由がない限り --privileged でコンテナーを実行しないでください。

例 79: Linux の capability を削除

アプリに不要な権限を取り除きます。

docker run -d --name locked --cap-drop ALL --cap-add NET_BIND_SERVICE -p 80:80 nginx:1.27-alpine

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 80: 読み取り専用のルートファイルシステム

コンテナーが自身のシステムファイルに書き込めないようにします。

docker run -d --name ro-app --read-only --tmpfs /tmp --tmpfs /var/run nginx:1.27-alpine

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 81: 数値のユーザーで実行

名前ではなくユーザー ID を指定します。

docker run --rm --user 10001:10001 alpine:3.20 id

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 82: 権限昇格を防ぐ

プロセスが後から権限を得られないようにします。

docker run -d --name hardened --security-opt no-new-privileges:true nginx:1.27-alpine

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 83: イメージの脆弱性をスキャン

イメージ内のパッケージにある既知の CVE を探します。

docker scout cves nginx:1.27-alpine
docker scout quickview nginx:1.27-alpine

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 84: Compose でのセキュリティ設定

強化の設定を 1 つのサービスにまとめます。

services:
  api:
    build: ./api
    user: "10001:10001"
    read_only: true
    cap_drop: [ALL]
    security_opt: ["no-new-privileges:true"]
    tmpfs: [/tmp]

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 13: デバッグとトラブルシューティング

コンテナーが失敗したときの手がかりは、そのログ、inspect データ、イベント、プロセスにあります。系統立てた手順で、ほとんどの問題は数分で見つかります。

ログ、イベント、inspect

まず docker logs を読みます。docker events は Docker が何をいつしたかを示し、docker inspect は終了コード、環境変数、マウント、ネットワークを示します。実行中のコンテナーと期待する状態を比べられます。

リソースとプロセスの確認

docker stats はコンテナーごとの CPU とメモリを示し、docker top はプロセスを一覧にします。終了コード 137 は、多くの場合カーネルがメモリの使いすぎでプロセスを止めたことを意味し、OOMKilled で確認できます。

ネットワークのデバッグ

--network container:名前 で同じネットワーク名前空間にツールのイメージを起動し、内側から curl、dig、ping を使います。コンテナーが実際にどうネットワークを見ているかを、何も変えずに確認できます。

例 85: Docker のイベントを追う

コンテナーの起動、停止、失敗をリアルタイムで見ます。

docker events --filter type=container --since 10m

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 86: リソース使用量を監視

コンテナーごとの CPU とメモリを見ます。

docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 87: コンテナー内のプロセスを一覧表示

シェルを開かずに内部で動いているものを確認します。

docker top web

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 88: inspect から 1 つの項目を読む

終了コードと、カーネルがプロセスを止めたかを確認します。

docker inspect web --format "{{.State.ExitCode}} {{.State.OOMKilled}}"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 89: ファイルのコピーの出し入れ

設定ファイルを取り出し、修正をコンテナーに入れます。

docker cp web:/etc/nginx/nginx.conf ./nginx.conf
docker cp ./nginx.conf web:/etc/nginx/nginx.conf

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 90: ネットワークツールのイメージでデバッグ

コンテナーのネットワークを共有し、curl と dig で確認します。

docker run --rm -it --network container:web nicolaka/netshoot
# inside the tools shell: curl -v http://localhost:80

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 14: イメージのための CI/CD パイプライン

パイプラインは、コードが変わるたびにイメージをビルドし、テストし、スキャンし、push します。自動化によって、各イメージをコミットまでたどれるようになり、リリースは毎回同じになります。

CI でのビルドと push

パイプラインは有効期間の短い認証情報でログインし、イメージをビルドし、テストを実行し、コミットを含むタグを付けて push します。同じ手順は GitHub Actions と Azure Pipelines で動きます。

タグ付け戦略

追跡のために各イメージに Git のコミットでタグを付け、リリースが承認されたときだけバージョンのタグを移動します。本番で latest に頼ると、どのコードが動いているか分からなくなるので避けてください。

ランナーの衛生管理

CI ランナーにはレイヤーやボリュームが溜まります。ビルドキャッシュは定期的に prune し、キャッシュのサイズ上限を決めて、ビルドマシンのディスクが埋まって予測できない失敗をしないようにします。

例 91: GitHub Actions でビルドとプッシュ

main へのプッシュごとにイメージのビルドを自動化します。

name: build-image
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/example/app:${{ github.sha }}

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 92: Azure Pipelines でイメージをビルド

サービス接続を使って Docker タスクを実行します。

trigger:
  - main
pool:
  vmImage: ubuntu-latest
steps:
  - task: Docker@2
    inputs:
      command: buildAndPush
      containerRegistry: my-acr-connection
      repository: sample-api
      tags: |
        $(Build.BuildId)

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 93: Git のコミットでイメージにタグ付け

各イメージを正確なソースコードまでたどれるようにします。

SHA=$(git rev-parse --short HEAD)
docker build -t app:$SHA .
docker tag app:$SHA app:latest
echo "built app:$SHA"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 94: CI ランナーのビルドキャッシュを掃除

ビルド用マシンのディスクが不足しないようにします。

docker builder prune --filter until=72h -f
docker image prune -a --filter until=168h -f

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

トピック 15: 本番運用とオーケストレーション

小さなサービスなら 1 台のホストで十分です。複数のマシン、自己修復、ローリングアップデートが必要になったら、Kubernetes や Docker Swarm などのオーケストレーターがコンテナーを代わりに動かします。

本番のチェックリスト

イメージのバージョンを固定し、メモリの制限を設定し、再起動ポリシーを決め、ヘルスチェックを追加し、意図したインターフェースにポートをバインドし、秘密情報をイメージの外に置き、ログを一か所に集めます。

Kubernetes の基本

Deployment は指定した数のレプリカを動かし続け、ローリングアップデートを行います。Service はレプリカ全体に一つの安定したアドレスを与えます。同じコンテナーイメージは Compose でも Kubernetes でも変更なしに動きます。

Swarm と使いどころ

Docker Swarm は Compose に似たファイルを stack として使い、複数のマシンにまたがってサービスを動かします。Kubernetes はエコシステムが大きく、マネージドなクラウドサービスでよく使われるので、チームが運用できるツールを選んでください。

例 95: 本番向けの Compose 設定

バージョンを固定し、メモリを制限し、再起動と健全性を確認します。

services:
  api:
    image: registry.example.com/team/api:1.4.2
    restart: unless-stopped
    env_file: ./config/api.env
    ports:
      - "127.0.0.1:8000:8000"
    deploy:
      resources:
        limits:
          memory: 512M
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 3s
      retries: 3

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 96: Kubernetes の Deployment

同じイメージを 3 つのコピーでクラスターに実行します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/team/api:1.4.2
          ports:
            - containerPort: 8000
          resources:
            limits:
              memory: 512Mi
              cpu: "500m"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 97: Kubernetes の Service

コピーすべてに、クラスター内で固定のアドレスを与えます。

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 8000
  type: ClusterIP

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 98: Docker Swarm のスタックをデプロイ

同じサービスを複数のマシンで実行します。

docker swarm init
docker stack deploy -c stack.yml shop
docker service ls

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 99: Docker SDK でコンテナーを確認

Python のコードからコンテナーの状態を読み取ります。

import docker

client = docker.from_env()
for c in client.containers.list():
    state = c.attrs["State"].get("Health", {}).get("Status", "no healthcheck")
    print(c.name, state)

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

例 100: リリース前チェックのスクリプト

root で実行されるか重大な CVE があれば、リリースを止めます。

#!/usr/bin/env bash
set -euo pipefail
IMAGE="$1"
docker image inspect "$IMAGE" > /dev/null
USER_ID=$(docker run --rm --entrypoint id "$IMAGE" -u)
[ "$USER_ID" != "0" ] || { echo "image runs as root"; exit 1; }
docker scout cves --exit-code --only-severity critical "$IMAGE"
echo "release checks passed for $IMAGE"

*この例は、この記事を準備したコンピューターに Docker がインストールされていなかったため、ここでは実行していません。*

ラボ: 小さな Web アプリをビルド、実行、リリースする

記事の内容を 1 つのプロジェクトにまとめます。小さな Web アプリ、マルチステージビルドの Dockerfile、データベースとボリュームを持つ Compose ファイル、ヘルスチェック、リリース前のチェックです。

1. 小さなアプリと requirements.txt を含むフォルダーを作り、root 以外のユーザーで実行し HEALTHCHECK を持つ Dockerfile を書きます。

2. .dockerignore を追加し、バージョンのタグを付けてイメージをビルドします。例: docker build -t lab-web:1.0.0 .

3. Web サービスと、名前付きボリュームおよび healthcheck を使う PostgreSQL サービスを持つ compose.yaml を書き、docker compose up -d で起動します。

4. docker compose ps でヘルスの状態を確認し、docker compose logs -f web でログを読みます。

5. イメージに対して docker scout cves を実行し、ベースイメージを更新できるものは直して再ビルドします。

6. 例 100 のリリースチェックリストをイメージに対して実行し、失敗したらリリースを止めます。

7. docker compose down でプロジェクトを停止し、データが不要になってから名前付きボリュームを削除します。

次のステップ

ラボの後は、選んだランタイムの公式 Docker ドキュメントを読み、別のプロジェクトで練習し、サービスが複数のマシンを必要とするときに一つのオーケストレーターを深く学んでください。この記事の例はリポジトリに保存しておくと、Docker を更新するたびに再実行できます。

まとめ

Docker は、アプリケーションをどこでも同じように動くイメージに変えます。コンテナーの実行と管理、タグとダイジェストによるイメージの固定、マルチステージビルドによる効率的な Dockerfile の作成、ボリュームへのデータ保存、ネットワークによるサービスの接続、Compose によるアプリ全体の記述、設定とシークレットの安全な扱い、リソースの制限、コンテナーの強化、系統立てたデバッグ、CI/CD によるビルドの自動化、そして本番オーケストレーションへの準備を学びました。