初心者向けDocker: コンテナをシンプルに

· 12分で読めます

目次

Dockerとは?

Dockerは、アプリケーションとその依存関係を軽量でポータブルなコンテナにパッケージ化できるプラットフォームです。コンテナは、アプリの実行に必要なすべてのもの(コード、ランタイム、ライブラリ、システムツール)を保持する小さな自己完結型のボックスと考えてください。もう「私のマシンでは動く」という問題はありません。

Docker以前は、ソフトウェアのデプロイには依存関係の手動インストール、サーバーの設定、そして何も競合しないことを祈ることが必要でした。Pythonのバージョン、Node.jsパッケージ、データベースドライバー、システムライブラリのセットアップに何時間も費やしていました。そして、ステージング環境と本番サーバーで同じことを繰り返し、設定が一致することを祈っていました。

Dockerは、アプリがどこでも同じように動作することを保証することで、この混乱を解消します — あなたのノートパソコン、チームメイトのマシン、ステージングサーバー、または本番環境で。コンテナがデプロイの単位となり、アプリケーションコード単体ではなくなります。

Dockerは現代のソフトウェア開発の標準ツールになっています。マイクロサービスの構築、CI/CDパイプラインのセットアップ、または単に一貫した開発環境が欲しい場合でも、Dockerは最小限のオーバーヘッドでそれを可能にします。Netflix、Spotify、PayPalのような企業は、毎日何百万ものコンテナを本番環境で実行しています。

クイックヒント: Dockerは本番デプロイだけのものではありません。多くの開発者は、異なる言語バージョン、データベース、ツールでローカルマシンを散らかさないためにDockerを使用しています。あるプロジェクトにはPostgreSQL、別のプロジェクトにはMySQLが必要ですか?競合なしで両方をコンテナで実行できます。

コンテナと仮想マシン

コンテナと仮想マシンは両方とも分離を提供しますが、内部では非常に異なる動作をします。この違いを理解することは、コンテナがなぜこれほど人気になったかを理解するために重要です。

仮想マシンは、ハイパーバイザー上で独自のカーネルを持つ完全なオペレーティングシステムを実行します。各VMには独自のOSインストールが必要で、ギガバイトのディスクスペースと大量のメモリを消費します。起動時間は分単位で測定されます。3つのVMを実行する場合、3つの完全なオペレーティングシステムを同時に実行していることになります。

コンテナは、ホストOSカーネルを共有し、アプリケーション層のみをパッケージ化します。サイズはメガバイト単位(ギガバイトではなく)で、秒単位で起動し(分単位ではなく)、1台のマシンで数十個を楽に実行できます。

# VMアプローチ: 各アプリが完全なOSを取得
アプリA → ゲストOS → ハイパーバイザー → ホストOS → ハードウェア
アプリB → ゲストOS → ハイパーバイザー → ホストOS → ハードウェア

# コンテナアプローチ: アプリがカーネルを共有
アプリA → コンテナランタイム → ホストOS → ハードウェア
アプリB → コンテナランタイム → ホストOS → ハードウェア

この軽量アーキテクチャにより、コンテナはマイクロサービスに理想的です。1つのモノリシックアプリケーションの代わりに、何百もの小さなサービスを実行する可能性があります。リソース効率は驚異的です — 10個のVMを実行するサーバーは、100個のコンテナを快適に実行できるかもしれません。

機能 仮想マシン コンテナ
起動時間 分単位 秒単位
ディスクスペース ギガバイト(完全なOS) メガバイト(アプリ層のみ)
パフォーマンス ネイティブに近い ネイティブ(ハイパーバイザーのオーバーヘッドなし)
分離 完全(別々のカーネル) プロセスレベル(共有カーネル)
ポータビリティ 限定的(ハイパーバイザー依存) 高い(Dockerが動作する場所ならどこでも)
リソース使用量 重い 軽量

とはいえ、VMは時代遅れではありません。各VMが独自のカーネルを持つため、より強力な分離を提供します。セキュリティが重要なワークロードや、異なるオペレーティングシステムを実行する必要がある場合、VMは依然としてより良い選択です。多くの組織は両方を使用しています:インフラストラクチャの分離にはVM、アプリケーションのデプロイにはコンテナを使用します。

Dockerの中核概念

コマンドやDockerfileに飛び込む前に、理解する必要があるいくつかの重要な概念があります。これらの構成要素は、Dockerがどのように機能するかの基礎を形成します。

イメージ

Dockerイメージは、アプリケーションコード、ランタイム、ライブラリ、依存関係を含む読み取り専用のテンプレートです。スナップショットまたは設計図と考えてください。イメージはDockerfileの指示から構築され、Docker Hubのようなレジストリに保存されます。

イメージはレイヤーで構成されています。Dockerfileの各指示は新しいレイヤーを作成します。Dockerはこれらのレイヤーをキャッシュするため、イメージを再構築して最後のレイヤーのみが変更された場合、Dockerはキャッシュされたレイヤーを再利用します。これによりビルドが非常に高速になります。

コンテナ

コンテナは、イメージの実行中のインスタンスです。同じイメージから複数のコンテナを作成でき、それぞれが分離して実行されます。コンテナを停止すると、明示的に保存するかボリュームを使用しない限り、内部で行われた変更は失われます。

コンテナは設計上一時的です。この不変性は機能であり、バグではありません — 一貫性を保証し、スケーリングを簡単にします。

Dockerfile

Dockerfileは、Dockerイメージを構築するための指示を含むテキストファイルです。ベースイメージを指定し、コードをコピーし、依存関係をインストールし、アプリケーションの実行方法を定義します。Dockerfileについては後のセクションで詳しく説明します。

Dockerレジストリ

レジストリは、Dockerイメージのストレージおよび配布システムです。Docker Hubはデフォルトのパブリックレジストリで、何百万ものイメージをホストしています。独自のアプリケーション用にプライベートレジストリを実行することもできます。docker pull nginxを実行すると、DockerはDocker Hubからnginxイメージをダウンロードします。

ボリューム

ボリュームは、データを永続化するためのDockerのメカニズムです。コンテナは一時的なので、コンテナ内に書き込まれたデータは停止すると消えます。ボリュームを使用すると、コンテナファイルシステムの外部にデータを保存でき、コンテナの再起動や削除後も存続します。

プロのヒント: すべてのフラグとオプションを覚えることなく、複雑なDockerコマンドを素早く作成するには、Dockerコマンドジェネレーターを使用してください。

必須のDockerコマンド

毎日使用するDockerコマンドを見ていきましょう。これらは、イメージのプルからリソースのクリーンアップまで、コンテナのライフサイクル全体をカバーしています。

イメージの操作

# Docker Hubからイメージをプル
docker pull ubuntu:22.04

# すべてのローカルイメージをリスト
docker images

# イメージを削除
docker rmi ubuntu:22.04

# Dockerfileからイメージをビルド
docker build -t myapp:1.0 .

# レジストリにプッシュするためにイメージにタグ付け
docker tag myapp:1.0 username/myapp:1.0

# レジストリにイメージをプッシュ
docker push username/myapp:1.0

コンテナの実行

# フォアグラウンドでコンテナを実行
docker run ubuntu:22.04 echo "Hello Docker"

# デタッチモード(バックグラウンド)でコンテナを実行
docker run -d nginx

# カスタム名で実行
docker run -d --name my-nginx nginx

# ポートマッピングで実行(ホスト:コンテナ)
docker run -d -p 8080:80 nginx

# 環境変数で実行
docker run -d -e POSTGRES_PASSWORD=secret postgres

# ボリュームマウントで実行
docker run -d -v /host/path:/container/path nginx

# シェルで対話的に実行
docker run -it ubuntu:22.04 /bin/bash

コンテナの管理

# 実行中のコンテナをリスト
docker ps

# すべてのコンテナをリスト(停止中を含む)
docker ps -a

# コンテナを停止
docker stop my-nginx

# 停止したコンテナを起動
docker start my-nginx

# コンテナを再起動
docker restart my-nginx

# コンテナを削除
docker rm my-nginx

# 実行中のコンテナを削除(強制)
docker rm -f my-nginx

# コンテナログを表示
docker logs my-nginx

# リアルタイムでログをフォロー
docker logs -f my-nginx

# 実行中のコンテナでコマンドを実行
docker exec my-nginx ls /usr/share/nginx/html

# 実行中のコンテナでシェルを開く
docker exec -it my-nginx /bin/bash

# コンテナのリソース使用状況を表示
docker stats

# コンテナの詳細を検査
docker inspect my-nginx

クリーンアップコマンド

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

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

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

# 未使用のすべてを削除(コンテナ、イメージ、ネットワーク、ボリューム)
docker system prune -a

-itフラグは特別な言及に値します。-iはSTDINを開いたままにし(対話的)、-tは疑似TTY(ターミナル)を割り当てます。一緒に使用すると、通常のシェルセッションのようにコンテナと対話できます。

クイックヒント: docker ps -qを使用してコンテナIDのみを取得します。これはスクリプト作成に便利です。例えば、docker stop $(docker ps -q)はすべての実行中のコンテナを停止します。

Dockerfileの書き方

Dockerfileは魔法が起こる場所です。これはDockerイメージを構築するためのレシピで、コンテナに何が入るかを正確に指定します。最も重要な指示とベストプラクティスを分解してみましょう。

基本的なDockerfile構造

# ベースイメージから開始
FROM node:18-alpine

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

# パッケージファイルをコピー
COPY package*.json ./

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

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

# アプリが実行されるポートを公開
EXPOSE 3000

# アプリを実行するコマンドを定義
CMD ["node", "server.js"]

主要なDockerfile指示

FROMはベースイメージを指定します。再現可能なビルドを保証するために、latestの代わりに常に特定のバージョンタグ(node:18-alpineなど)を使用してください。Alpineバリアントはより小さく、より安全です。

WORKDIRは後続の指示の作業ディレクトリを設定します。ディレクトリが存在しない場合は作成します。これはRUN cd /appを使用するよりもクリーンです。

COPYはホストマシンからイメージにファイルをコピーします。構文はCOPY ソース 宛先です。すべてをコピーするにはCOPY . .を使用しますが、何を含めるかに注意してください(.dockerignoreを使用)。

RUNはビルドプロセス中にコマンドを実行します。各RUN指示は新しいレイヤーを作成します。レイヤーを減らすために&&でコマンドをチェーンします:

# 悪い例: 3つのレイヤーを作成
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# 良い例: 1つのレイヤーを作成
RUN apt-get update && \
    apt-get install -y curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

EXPOSEはアプリケーションがリッスンするポートを文書化します。実際にポートを公開するわけではありません — それはdocker run -pで行われます。

CMDはコンテナ起動時に実行するデフォルトコマンドを指定します。シェル処理の問題を避けるために、JSON配列形式(["executable", "param1"])を使用してください。

ENTRYPOINTはCMDに似ていますが、オーバーライドが難しくなります。コンテナを実行可能ファイルのように動作させたい場合に使用します。柔軟なデフォルトのためにENTRYPOINTとCMDを組み合わせることができます。

マルチステージビルド

マルチステージビルドを使用すると、1つのDockerfileで複数のFROMステートメントを使用できます。これは、ビルドツールを分離したまま、小さな本番イメージを作成するために非常に強力です。

# ビルドステージ
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
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]

本番イメージには、コンパイルされたコードとランタイム依存関係のみが含まれ、ビルドツールは含まれません。これにより、イメージサイズを70%以上削減できます。

.dockerignoreファイル

.dockerignoreファイルを作成して、ビルドコンテキストからファイルを除外します。これによりビルドが高速化され、イメージサイズが削減されます。

node_modules
npm-debug.log
.git
.env
*.md
.DS_Store
coverage
.vscode