Docker 基礎 #1 コンテナとは — VM との違い、Docker エコシステム
このブログのバックエンドトラック(FastAPI、Django、Go 実践)は、最終話で全て同じ問いにぶつかります — 「これをどうデプロイするのか?」。答えはいつも Docker でした。だから Docker トラックを別立てで 4 シリーズ 24 編にまとめます。
このシリーズは Docker 基礎 6 編です。
- #1 コンテナとは — VM との違い、Docker エコシステム ← この記事
- #2 Dockerfile を初めて書く — RUN, COPY, CMD
- #3 イメージとコンテナ — build, run, ps, logs, exec
- #4 ボリュームとネットワーク
- #5 レジストリ — Docker Hub, GHCR, push/pull
- #6
.dockerignoreとビルドコンテキスト
この記事は コンテナとは何か、そして Docker がどんな道具の集まりか の二つを掴みます。docker run hello-world まで実際に走らせて締めくくります。
「私のマシンでは動くんですけど」 #
デプロイの話を始めると最初にぶつかる一文です。開発者のマシンではちゃんと動くのに、サーバーに乗せると壊れる、という状況です。原因を解きほぐすと、ほぼ毎回同じ絵になります。
- OS が違う (macOS / Ubuntu / Amazon Linux)
- Python・Node・Go のバージョンが違う
- システムに入っているライブラリ(
libssl、libpq、imagemagick)が違う - 環境変数、ファイル権限、タイムゾーンといった細かい部分が違う
「アプリ + そのアプリが依存する全て」を一つの塊にまとめて、どこでも同じように動かす方法が必要です。その方法が コンテナ です。
仮想マシンと何が違うのか #
コンテナ以前に同じ問題を解いていた道具が 仮想マシン (VM) です。VirtualBox、VMware、EC2 のようなクラウド VM。どちらも「隔離された環境でアプリを動かす」点は同じですが、隔離されるレイヤーが違います。
仮想マシン コンテナ
┌─────────────────────────┐ ┌─────────────────────────┐
│ App A │ App B │ │ App A │ App B │
├────────────┼────────────┤ ├────────────┼────────────┤
│ Bin/Lib │ Bin/Lib │ │ Bin/Lib │ Bin/Lib │
├────────────┼────────────┤ ├────────────┴────────────┤
│ Guest OS │ Guest OS │ │ Docker Engine │
├────────────┴────────────┤ ├─────────────────────────┤
│ Hypervisor │ │ Host OS │
├─────────────────────────┤ ├─────────────────────────┤
│ Host OS │ │ Hardware │
├─────────────────────────┤ └─────────────────────────┘
│ Hardware │
└─────────────────────────┘- VM はハイパーバイザの上に ゲスト OS 全体 を立ち上げます。カーネルまで別。起動に分単位かかり、空の Ubuntu 一台立ち上げるだけでもディスク数 GB・メモリ数百 MB が基本で消費されます。
- コンテナ はホスト OS の カーネルを共有 します。隔離は Linux カーネルの機能(namespace、cgroup)で。だからコンテナ一つは事実上「隔離されたプロセス」程度 — 起動が ms 単位、メモリオーバーヘッドもほぼなし。
軽い分、一台のホストに数十~数百個のコンテナを乗せられて、ビルド・デプロイのサイクルが大幅に短くなります。代わりに一つだけ制約 — ホストと違うカーネルは使えません。 macOS や Windows で Docker を使うと、内部的には小さな Linux VM が入って、その上でコンテナが動きます。(これが Docker Desktop の正体です。)
コンテナはどこから来たのか #
Docker がコンテナを発明したわけではありません。Linux カーネルにはずっと前から隔離機能がありました。
- chroot (1979) — プロセスのルートディレクトリを変えてファイルシステムを隔離
- cgroups (2007) — CPU・メモリ・IO といったリソースをグループ単位で制限
- namespaces (2002~) — PID、ネットワーク、マウント、ユーザーなどをプロセス別に隔離
- LXC (2008) — 上の機能をまとめた最初のコンテナツール
これらの部品は強力でしたが扱いにくかった。「アプリをコンテナにまとめる標準的な方法」 がなかった。人によって、会社によって違うやり方を使っていました。
2013 年に Docker が登場してそこが整理されます。
- Dockerfile という統一されたビルド定義
- イメージ という持ち運べる成果物
- レジストリ でイメージを共有
- Docker CLI で一気に扱う
コンテナ自体は古い技術ですが、包装紙と運搬規約 を作ったのが Docker の功績です。今は OCI (Open Container Initiative) という標準として定着していて、Docker 以外にも podman、containerd、Kubernetes の cri-o といった実装が同じイメージフォーマットを共有します。
イメージとコンテナ — 別物 #
Docker を使い始めるとこの二つの言葉が紛らわしくなります。短くまとめると:
| イメージ (image) | コンテナ (container) | |
|---|---|---|
| 例え | クラス / 設計図 | インスタンス / 実体 |
| 状態 | 読み取り専用、変わらない | 実行中・停止・削除可能 |
| 作られ方 | docker build (Dockerfile から) | docker run (イメージから) |
| 保管場所 | ホストのディスクとレジストリ (Hub、GHCR、ECR …) | ホストのディスク |
同じイメージからコンテナを 100 個立ち上げられて、その 100 個はお互いに影響を与えません。イメージが変わらないという点がコンテナシステムの 再現性 (reproducibility) を作ります。
Docker エコシステムを一周 #
「Docker」という名前の下には実は複数のツールが集まっています。最初に手をつける前に大きな絵を掴んでおくと記事が追いやすくなります。
┌────────────────────────────┐
│ Docker CLI │ ← 私たちが叩くコマンド
│ (docker run / build ...) │
└─────────────┬──────────────┘
│ REST API
┌─────────────▼──────────────┐
│ Docker daemon │ ← 実際の仕事をするプロセス
│ (dockerd → containerd) │
└──┬──────┬────────┬─────────┘
│ │ │
┌──────▼─┐ ┌─▼────┐ ┌▼────────────┐
│ Images │ │ Net │ │ Containers │
│ (cache)│ │ Vol │ │ (runtime) │
└────────┘ └──────┘ └─────────────┘- Docker Engine — コンテナを実際に立ち上げるデーモン (
dockerd) と、その下で動くcontainerdのような低レベルランタイム、それを扱う CLI (docker)。 - Docker CLI —
docker run、docker build、docker psのようなコマンド。実はこれらのコマンドはデーモンに REST API でリクエストを送る薄いクライアントです。 - Dockerfile — 「イメージをどう作るか」を書いたテキストファイル。(#2 で本格的に扱います。)
- Docker Compose — 複数のコンテナを一つの束として定義・実行するツール。
compose.yaml一つのファイルで web + db + cache を同時に立ち上げられます。(中級シリーズのテーマ) - Docker Hub / GHCR / ECR — イメージを保管・共有する レジストリ (registry)。GitHub と GitHub Container Registry の関係と同じです。(#5 で扱います。)
- Docker Desktop — macOS / Windows 用の統合パッケージ。内部に小さな Linux VM と上の道具を一緒にまとめたもの。
このシリーズは CLI + Dockerfile + レジストリ まで — つまり一つのコンテナをうまく作って動かすのに必要な要点だけ扱います。Compose と運用は次のシリーズです。
インストール — Docker Desktop #
macOS / Windows ユーザーは Docker Desktop を一度インストールすれば終わりです。
- ダウンロード: docker.com/products/docker-desktop
- macOS なら Apple Silicon / Intel のうち自分のチップに合うビルドを選んでください。
インストール後 Docker Desktop を起動しておけば、ターミナルで docker コマンドがすぐ使えます。別途の PATH 設定は不要です。
Linux (Ubuntu) なら Desktop の代わりに Engine を直接入れます。
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER # sudo なしで docker コマンドを使うならusermod の後は一度ログアウトしてから入り直さないとグループ変更が反映されません。
インストールが終わったらバージョンを叩いてみてください。
docker --version
# Docker version 27.x.x, build ...
docker info
# デーモン状態、コンテナ数、イメージ数、ストレージドライバなどdocker info でエラーが出たらデーモンが立ち上がっていません。macOS / Windows は Docker Desktop が起動しているか、Linux なら sudo systemctl start docker を確認してください。
最初のコンテナ — hello-world
#
Docker がちゃんと入ったか確かめる儀礼コマンドがあります。
docker run hello-world初めて実行すると出力はこう流れます。
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
...
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
...一行のコマンドの裏で起きたことを解きほぐすとこう。
dockerCLI がデーモンに「hello-worldイメージを実行して」とリクエスト- デーモンがローカルキャッシュを探す → なし
- Docker Hub から
hello-world:latestを取ってくる (Pulling from library/hello-world) - 取ってきたイメージでコンテナを作って実行
- コンテナの中のプログラムが「Hello from Docker!」を出力して終了
- コンテナは終了したけど消えはしない (まだディスクにある)
今作られたものを直接見てみましょう。
docker images
# REPOSITORY TAG IMAGE ID SIZE
# hello-world latest abc123... 13.3kB
docker ps -a
# CONTAINER ID IMAGE COMMAND STATUS NAMES
# d3f4... hello-world "/hello" Exited (0) 10 seconds ago bold_curiedocker imagesはホストのディスクにキャッシュされたイメージを表示します。docker psは 実行中の コンテナだけ、docker ps -aは終了したものまで全て表示します。
片付けるには:
docker rm bold_curie # コンテナ削除 (名前は自分のもので)
docker rmi hello-world # イメージ削除もう一歩 — docker run -it ubuntu
#
hello-world は一行出力して終わるコンテナなので感覚が掴みにくい。実際の Linux の中に入ってみると「これがそんなに軽かったの?」が体感できます。
docker run -it ubuntu:24.04 bash初めて実行すると Ubuntu 24.04 イメージを取ってきて (~80MB)、すぐにコンテナの中のシェルプロンプトが現れます。
root@a1b2c3d4e5f6:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
...
root@a1b2c3d4e5f6:/# uname -r
6.10.0-... # コンテナが共有しているカーネルのバージョン
root@a1b2c3d4e5f6:/# exitここで一つ面白い事実 — cat /etc/os-release は Ubuntu と表示されますが、uname -r は コンテナが共有しているカーネル を示します。ネイティブ Linux ならホストのカーネルそのものですが、Docker Desktop では内部 Linux VM のカーネルが見えることもあります。コンテナがホストカーネルを共有するという点がこうやって表れます。
-it フラグは二つの意味です。
-i(--interactive): コンテナの stdin を開けておく (入力を受けられるように)-t(--tty): 仮想ターミナルを付ける (シェルが自然に動くように)
対話型 (インタラクティブ) コンテナを立ち上げるときはほぼいつも一緒に来ます。exit で抜けるとシェルが終了して、シェルが終了するとコンテナのメインプロセスが終了したことになるのでコンテナも止まります。
よく使うコマンド一表 #
このシリーズで広く使うコマンドを先に一箇所にまとめます。覚える必要はなく、詰まったら戻ってきてください。
| コマンド | する仕事 |
|---|---|
docker run <image> | イメージでコンテナを作って実行 |
docker run -it <image> <cmd> | インタラクティブモード (シェル進入によく使う) |
docker run -d <image> | バックグラウンド実行 (detached) |
docker ps / docker ps -a | 実行中 / 全コンテナ |
docker images | キャッシュされたイメージ |
docker logs <container> | コンテナの stdout/stderr |
docker exec -it <container> bash | 実行中のコンテナにシェルで入る |
docker stop <container> | グレースフル終了 (SIGTERM → SIGKILL) |
docker rm <container> | 終了したコンテナ削除 |
docker rmi <image> | イメージ削除 |
docker build -t <name> . | 現在のディレクトリの Dockerfile でイメージビルド |
docker pull <image> | レジストリからイメージを取得 |
docker push <image> | レジストリへイメージをアップ |
まとめ #
この記事で掴んだ絵:
- コンテナは アプリと依存全体を一つの塊にまとめる持ち運び可能な単位。「私のマシンでは動く」問題を解く。
- 仮想マシンがゲスト OS まで仮想化するなら、コンテナは ホストカーネルを共有 して軽く隔離します。
- コンテナ自体は chroot/cgroups/namespaces のような古い技術の上に立っています。Docker がしたことは 標準ビルド・運搬規約 を作ったこと。
- イメージ = 変わらない設計図、コンテナ = その設計図で作った実行インスタンス。
- Docker は一つのツールではなく Engine + CLI + Compose + Hub の エコシステム。このシリーズはその中の CLI + Dockerfile + レジストリまで扱う。
docker run hello-world/docker run -it ubuntuで最初のコンテナを立ち上げてみた。
次の記事 (#2 Dockerfile を初めて書く) では他人が作ったイメージを受け取って使う段階を超えて、自分のアプリのためのイメージを直接作る方法 に進みます。FROM、RUN、COPY、CMD の四つのコマンドで最も単純な Dockerfile を書いてみて、docker build でイメージを焼き上げる流れを掴みます。