コンテナとイメージの違い
コンテナを学ぶ最初の関門が「イメージ」と「コンテナ」の区別です。イメージはアプリと実行に必要なファイル一式を固めた読み取り専用の雛形で、コンテナはそのイメージから起動した実行中の実体です。1つのイメージから同じコンテナを何個でも作れます。クラスと インスタンス、あるいは料理のレシピと完成した料理の関係に例えると分かりやすいです。コンテナを消してもイメージは残るので、また同じものを起動できます。
コンテナの学び始めで、多くの人が同じところでつまずく。「イメージ」と「コンテナ」という、よく似た2つの言葉だ。
この2つは密接に関係しているのに、まったく別のものを指している。だから混ざりやすい。
ここを曖昧にしたまま進むと、コマンドの出力が読めなくなる。「消したはずのものがまだある」「同じものが何個も出てくる」といった混乱にぶつかる。
逆に、ここさえ腑に落ちれば楽になる。以降の起動・ビルド・削除は、すべて同じ理屈の上に乗っているからだ。まずは、この2つをはっきり分けるところから始める。
イメージとは、アプリケーション本体と、それが動くのに必要なライブラリや設定ファイル一式をまとめて固めた、読み取り専用の雛形だ。それ自体は動いていない。ディスクの上に静かに置かれているだけの素材だと考えてほしい。
コンテナは、そのイメージをもとに起動した、実行中(または停止中)の実体だ。イメージという型から、コンテナという実物を作り出す、という関係になっている。
ここで効いてくる大事な性質がある。1つのイメージから同じコンテナを何個でも作れる、という点だ。
同じ素材から、まったく同じ環境の実物を必要なだけ複製できる。これがコンテナの再現性の高さの源だ。なぜそんなことが可能なのかは、のちのレイヤの話で答え合わせをする。
🍳 クラスとインスタンス、レシピと料理
この型と実物の関係は、身近なものに置き換えると分かりやすい。
プログラミングの経験がある人なら、クラスとインスタンスの関係がそのまま当てはまる。イメージがクラス(設計図)、コンテナがそこから生成されたインスタンス(実体)だ。1つのクラスから複数のインスタンスを作れるのと同じように、1つのイメージから複数のコンテナが作れる。
プログラミングになじみがなければ、料理のレシピと完成した料理を思い浮かべてほしい。レシピ(イメージ)は1枚あれば十分で、それを見ながら同じ料理(コンテナ)を何皿でも作れる。料理を食べてしまっても、レシピは手元に残る。
この食べても残るという感覚が、そのまま実務の挙動につながる。コンテナを削除しても、もとになったイメージはディスクに残り続ける。
だから、間違って起動したコンテナを消してしまっても慌てる必要はない。同じイメージからまた起動し直せばよい。
逆に言えば、本当に容量を空けたいときは、コンテナを消すだけでは足りない。イメージ自体も消す必要がある。
🧱 イメージは何でできているのか
もう一段踏み込む。イメージは1枚岩ではない。レイヤと呼ばれる薄い層の積み重ねでできている。
たとえば「土台となる最小のOS部分」という層の上に、「必要なソフトを入れた」層、さらに「自分のアプリを配置した」層、という具合に重なっている。変更のたびに新しい層が上へ積まれていく。
各レイヤは読み取り専用だ。コンテナとして起動するときだけ、その一番上に「書き込み用の薄い層」が1枚だけかぶさる。
コンテナの中でファイルを作ったり書き換えたりした内容は、すべてこの最上層に記録される。この最上層がのちのデータ消失の話の鍵になるので、頭の隅に置いておきたい。
では、わざわざ層に分ける意味は何か。実用上のうれしさが2つある。
1つは容量の節約だ。複数のイメージが共通の土台レイヤを持つ場合、その層はディスク上で1つだけ保持して共有される。
もう1つは作り直しの速さだ。後で扱う Dockerfile からイメージを作り直すとき、変更のなかった層はそのまま再利用される(キャッシュが効く)。だから作り直しが速く済む。
そして、さきほど保留にした「なぜ1つのイメージから何個もコンテナを軽く作れるのか」の答えもここにある。コンテナを起動するたびに変わるのは最上層の書き込み層だけだ。下に積まれた読み取り専用のレイヤ群は、すべてのコンテナで共有される。だから実体を量産しても軽い。
🖥 その区別は、コマンドの出力にどう表れるか
言葉の区別は頭で分かっても、本当に身につくのは画面で見たときだ。実際のコマンドの出力に、この区別はそのまま表れる。
手元にあるイメージの一覧は docker images で、起動中のコンテナの一覧は docker ps で確認する。両者はまったく別の一覧で、出てくる列も違う。
docker images は REPOSITORY(イメージ名)・TAG・IMAGE ID・SIZE といったイメージの属性を並べる。docker ps は CONTAINER ID・IMAGE(どのイメージから作ったか)・STATUS・NAMES といったコンテナの属性を並べる。
たとえば docker run nginx を3回実行すると、イメージ nginx は1つのままだ。だがコンテナは3つ作られ、docker ps -a には3行が並ぶ。「イメージ一覧には1個なのにコンテナ一覧には3個」という状態こそ、1対多の関係を目に見える形にしたものだ。
削除コマンドが2系統に分かれているのも、この区別がそのまま反映された結果だ。コンテナ(実体)を消すのは docker rm コンテナ名 で、イメージ(雛形)を消すのは docker rmi イメージ名 だ。
名前がよく似ていて取り違えやすい。rm はコンテナ、rmi(remove image)はイメージ、と対応で覚えてほしい。
しかも、あるイメージから作ったコンテナが1つでも残っていると、そのイメージは docker rmi で消そうとしても「使用中で消せない」と断られる。雛形を片付けるには先に実体を片付ける、という順序になっている。ここからも「イメージの上にコンテナがぶら下がっている」という関係が読み取れる。
🚚 よくある不安と、配るべきはどちらか
初学者がよく口にするのが「コンテナを消したらアプリごと無くなった気がする」という不安だ。だが消えたのは実行中の実体(コンテナ)だけで、雛形(イメージ)は残っている。同じ docker run でまた起動できるので心配は要らない。
逆に「ディスクがいっぱいになった」ときは、停止済みのコンテナと、もう使わないイメージの両方を片付ける必要がある。
そして実務でいちばん効いてくるのが、配布したいのはイメージのほうだ、という点だ。開発した環境をイメージとして固めてレジストリに置けば、別のサーバではそのイメージを取得して docker run するだけで、まったく同じコンテナを再現できる。
「自分の環境では動いたのに本番では動かない」という昔ながらの悩みを、イメージという再現可能な雛形を配ることで解消する。これがコンテナの最大の値打ちだ。
つまずいたら、まずここ。『イメージは動いていない雛形、コンテナは動いている実体、1つの雛形から実体は何個でも』という対応関係を、繰り返し手を動かして体に入れておこう。