+ 作る
図を読み込んでいます…
1 / 8
Dockerを理解しよう / 全3章

Docker を理解しよう:箱の正体、イメージとコンテナ

発行
かえで
版
第2版
改訂
分量
8コマ・3,012字

「自分の環境では動くのに」問題から出発して、コンテナという箱、ランタイム、イメージ(ひな型)とコンテナ(実体)の関係、docker run のオプション、ボリュームまで。Docker の土台になる言葉を順に押さえます。

なぜ Docker が要るのか:「自分の環境では動くのに」問題

アプリを動かすには、書いたコードだけでは足りません。コードを実行するランタイム、頼っているライブラリ、設定ファイルや環境変数がそろって初めて動きます。ところがそれらは PC ごと、サーバーごとに微妙に違います。手元では Node 22 と新しいライブラリで動いていたものが、本番では Node 18 と古いライブラリで落ちる。これが昔から開発者を悩ませてきた「自分の環境では動くのに」問題です。

Docker はこの問題への答えとして生まれました。このアニメモは3部構成の1つ目で、まず「箱(コンテナ)とは何か」「イメージとは何か」「docker run で何が起きるか」を順に追います。2つ目でイメージの作り方と配り方(Dockerfile・ビルド・レジストリ)、3つ目で複数の箱をまとめる docker compose を扱います。

  • 「自分の環境では動くのに」問題
  • ノートPC
  • サーバー
  • 手元では動く
  • アプリのコード
  • 本番で落ちる
  • 自分の PC
  • 環境(ランタイム・ライブラリ・設定)が マシンごとに違うから
  • 本番サーバー
  • Node 22 libfoo 1.2 設定 A
  • Node 18 libfoo 1.0 設定 B
  • OK
  • NG
  • コード以外の「環境」がマシンごとに違う。だから同じコードでも動いたり動かなかったりする

答え:必要なもの一式を箱に詰めて、そのまま運ぶ

Docker の発想は単純です。アプリのコードと、それが動くのに必要なランタイム・ライブラリ・設定を、まとめて1つの箱に閉じ込める。そして箱ごと運んで、どのマシンでも同じ箱を開ける。箱の中身は作った時点で固まっているので、手元でも本番でも同じ状態で動きます。この箱が「コンテナ」です。

箱の中にあるのは、ふだん「環境」と呼んでいるもの全部です。だから Docker を理解するには、「アプリのコード」と「それを動かす環境」を分けて考える癖が要ります。この先ずっと、この区別が効いてきます。

  • 答え:環境ごと箱に詰めて、箱ごと運ぶ
  • ノートPC
  • コンテナ(箱)
  • サーバー
  • アプリのコード
  • ランタイム(node 22)
  • 自分の PC
  • 同じ箱
  • 同じ箱
  • 本番サーバー
  • ライブラリ(libfoo 1.2)
  • 設定・環境変数
  • OK
  • OK
  • 中身は作った時点で固まっている → どこで開けても同じ
  • コード + ランタイム + ライブラリ + 設定 を1つの箱に。どのマシンでも同じ箱を開けるだけ

ランタイムとは何か

箱の中身でいちばん分かりにくいのが「ランタイム」です。あなたが書いた JavaScript や Python のファイルは、それ自体はただのテキストで、CPU は直接読めません。それを読み込んで解釈し、CPU に分かる命令に変えて実行するプログラムがランタイムです。JavaScript なら node、Python なら python、Java なら JVM がそれにあたります。

もう少し広く取ると、ランタイムには言語本体だけでなく、標準ライブラリや、OS が提供する基本ライブラリ(libc など)も含まれます。「コードを動かすための土台一式」と考えてください。Go や Rust のようにコンパイル時に必要なものを実行ファイルへ埋め込む言語では、ランタイムはほとんど要りません。

node:22 のようなイメージに入っているのは、まさにこの土台です。あなたのコードはまだ入っていません。

  • ランタイム = あなたのコードを実際に動かすプログラム
  • server.js const app = express() app.listen(3000)
  • ランタイム node / python / JVM
  • CPU
  • 書いたコード(ただのテキスト)
  • コードを読み、解釈し、 CPU に分かる命令にして実行する
  • CPU / OS
  • JavaScript → node / Python → python / Java → JVM / Ruby → ruby Go / Rust → 実行ファイルに埋め込むので、ほぼ不要 広い意味では、標準ライブラリや libc など OS の基本ライブラリも含む
  • node:22 のようなイメージに入っているのはこの土台。あなたのコードはまだ入っていない

仮想マシンとの違い:OS を共有して、プロセスだけ隔離する

「環境ごと箱に詰める」なら仮想マシン(VM)でもできます。違いは箱の重さです。VM はハードウェアを丸ごと模倣して、その上にゲスト OS をもう1つ起動します。OS が二重に動くので、起動に分単位かかり、メモリも GB 単位で食います。

コンテナは OS を起動しません。ホストの Linux カーネルをそのまま使い、「このプロセスからは自分のファイルとネットワークしか見えない」という隔離をカーネルの機能で作ります。中から見ると独立した小さな Linux に見えますが、実体はホスト上の普通のプロセスです。だから起動は一瞬で、何十個も並べられます。

Windows や Mac で Docker を使うときは、裏で小さな Linux VM が1つだけ動いていて、その中で全コンテナが動いています。

  • 仮想マシン(VM)とコンテナの違い
  • 仮想マシン(VM)
  • コンテナ(Docker)
  • アプリ A
  • アプリ B
  • コンテナ A アプリ + 環境一式
  • コンテナ B アプリ + 環境一式
  • ゲスト OS
  • ゲスト OS
  • ハイパーバイザ(ハードウェアを模倣)
  • Docker エンジン(隔離を作る)
  • ホスト OS
  • ホスト OS(Linux カーネルを共有)
  • ハードウェア
  • ハードウェア
  • OS が二重に動く → 起動に分単位、メモリ GB 単位
  • OS は起動しない。普通のプロセスを隔離 → 一瞬で起動、軽い
  • Windows / Mac では、裏で小さな Linux VM が1つ動き、その中で全コンテナが動く
  • VM は OS ごと起動、コンテナはカーネルを共有してプロセスだけ隔離。だから軽くて速い

イメージ:箱の「ひな型」

箱の中身を、起動前の状態で固めてファイルにしたものが「イメージ」です。中身は、ファイルシステムの丸ごとのスナップショットと、「起動したら何を実行するか」の指定。たとえば postgres:16 というイメージには、Debian の最小限のファイル群と PostgreSQL 16 のバイナリ、起動スクリプトが入っていて、「インストール済みで、あとは電源を入れるだけの小さな Linux のディスク」と考えるのが近いです。

イメージは読み取り専用で、層(レイヤー)の積み重ねでできています。土台の OS、その上にランタイム、その上にライブラリ、その上にアプリのコード、最後に起動コマンド。同じ土台を使うイメージが複数あっても、土台のレイヤーは1回だけダウンロードされて共有されます。

「イメージを取ってくる」とは、ソフトの本体だけでなく、ソフトが動く環境ごと取ってくることです。インストーラーが「あなたの環境にソフトを組み込む手順」なのに対して、イメージは「組み込み終わった状態そのもの」です。

  • イメージ = 読み取り専用のひな型(レイヤーの積み重ね)
  • イメージ myapp:1.0
  • 読み取り専用。起動しても中身は変わらない
  • 起動コマンド: node server.js
  • 下から順に積んだレイヤーの重ね合わせ
  • アプリのコード
  • 同じ土台レイヤーは、別のイメージと共有される (1回ダウンロードすれば済む)
  • ライブラリ(npm ci の結果)
  • ランタイム node 22
  • docker pull postgres:16 で取ってくるのもこれ。 ソフト本体だけでなく、動く環境ごと入っている
  • Debian の最小ファイル群(土台)
  • インストーラー = 組み込む手順 イメージ = 組み込み終わった状態そのもの
  • イメージは「インストール済みで、あとは起動するだけ」の小さな Linux のディスク

コンテナ:イメージから起動した「実体」

イメージを元に起動した、動いている箱がコンテナです。プログラミングの言葉で言えば、イメージがクラスでコンテナがインスタンス。1つのイメージから何個でもコンテナを作れますし、それぞれは独立しています。

コンテナを起動すると、読み取り専用のイメージの上に、薄い「書き込み可能な層」が1枚乗ります。コンテナの中で作ったファイルや書き換えた設定は、すべてこの薄い層に入ります。イメージ本体は変わりません。

ここに落とし穴があります。コンテナを消すと、この書き込み層も一緒に消えます。データベースをコンテナで動かして、コンテナを作り直したらデータが全部消えていた、というのは初心者が必ず一度は踏む罠です。解決策が次に出てくる「ボリューム」です。

  • コンテナ = イメージから起動した実体
  • イメージ myapp:1.0
  • 起動コマンド: node server.js
  • コンテナ 1 書き込み可能な薄い層 (起動後の変更はここ) イメージの層(共有)
  • コンテナ 2 書き込み可能な薄い層 (起動後の変更はここ) イメージの層(共有)
  • コンテナ 3 書き込み可能な薄い層 (起動後の変更はここ) イメージの層(共有)
  • アプリのコード
  • ライブラリ(npm ci の結果)
  • docker run
  • イメージ = クラス(型)、コンテナ = インスタンス(実体) 1つのイメージから何個でも起動でき、それぞれ独立
  • ランタイム node 22
  • Debian の最小ファイル群(土台)
  • コンテナを消すと、書き込み層も一緒に消える → 消えて困るデータはボリュームへ(次のページ)
  • イメージは変わらない。変更はコンテナの薄い書き込み層に入り、コンテナと一緒に消える

docker run:イメージからコンテナを1つ起動する

図のコマンドは PostgreSQL を1つ起動しています。長く見えますが、構造は「docker run + オプション + イメージ名」だけです。

最後の postgres:16 が使うイメージで、手元に無ければ自動で取ってきます。-d は画面を占有せず裏で動かす指定。--name は人間が呼ぶための名前。-p 5432:5432 はポートの公開で、コンテナは隔離されているため何もしなければ外から繋げません。「ホストの 5432 番に来た通信をコンテナの 5432 番へ流す」と穴を開けるのがこの指定で、左がホスト側、右がコンテナ側です。-e は環境変数で、イメージを作った人が「この変数で初期パスワードを受け取る」と決めているので、それに従って渡します。-v がボリュームで、コンテナ内のデータ置き場を、Docker が管理する外部の保存領域に結びつけます。

つまり docker run は、固まったイメージに「口・設定・保存先」を外から与えて、動く箱にする操作です。

  • docker run -d --name mydb -p 5432:5432 -e POSTGRES_PASSWORD=secret \ -v pgdata:/var/lib/postgresql/data postgres:16
  • -d:裏で動かす --name mydb:コンテナの名前 -p 5432:5432:ホストの口 : コンテナの口 -e KEY=VALUE:環境変数(設定の受け口) -v 名前:パス:ボリューム(保存先) postgres:16:使うイメージ(無ければ pull)
  • ホスト(あなたの PC)
  • コンテナ mydb(postgres:16)
  • 外から
  • 5432
  • -p
  • 5432
  • -e → POSTGRES_PASSWORD=secret
  • -v → /var/lib/postgresql/data
  • -v
  • ボリューム pgdata
  • コンテナの外。消えない
  • run = イメージ + 起動オプション → コンテナ。口(-p)・設定(-e)・保存先(-v)を外から与える

ボリューム:箱を捨てても、データは残す

ボリュームは Docker が管理する保存領域で、コンテナとは別に生きています。-v pgdata:/var/lib/postgresql/data と指定すると、コンテナの中のそのディレクトリへの読み書きが、すべて pgdata というボリュームに向かいます。コンテナの書き込み層には入りません。

だからコンテナを消しても pgdata は残り、同じイメージから新しいコンテナを起動して同じ -v を付ければ、前のデータがそのまま見えます。データベースのデータ、アップロードされたファイル、ログなど「消えたら困るもの」は必ずボリュームへ逃がす、というのが鉄則です。

ここまでで「箱(コンテナ)」「ひな型(イメージ)」「起動(run)」「保存(ボリューム)」がそろいました。次のアニメモでは、そのイメージがどこから来るのか、つまり Dockerfile でのビルドと、レジストリからの取得を追います。

  • docker rm mydb docker run -d --name mydb2 … -v pgdata:/var/lib/postgresql/data postgres:16
  • コンテナの書き込み層は使い捨て ボリュームは Docker が管理する保存領域で、 コンテナとは別に生きる DB のデータ・アップロードされたファイル・ ログは必ずボリュームへ
  • ホスト(あなたの PC)
  • 旧コンテナ mydb (消した)
  • 新コンテナ mydb2 同じイメージから起動
  • 同じ -v
  • ボリューム pgdata
  • データはそのまま残っている
  • コンテナは消しても、ボリュームのデータは残る。次は「イメージはどこから来るのか」

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]