Dockerって結局何? 「開発環境の差分を減らすやつ」くらいしか知らないので、 ちゃんと理解してみた

目次
Dockerって結局何?「開発環境の差分を減らすやつ」くらいしか知らないので、ちゃんと理解してみた
Dockerを使っていると、こんな説明をすることがあります。
「開発環境の差分を減らせるやつです。」
これは間違っていません。
Node.jsのバージョンが違う。 PHPの拡張が足りない。 ローカルにPostgreSQLが入っていない。 「自分のPCでは動くのに、別の人のPCでは動かない」。
Dockerは、こういう環境差分をかなり減らしてくれます。
ただ、自分も普段Dockerを使っているわりに、
Dockerfileとimageとcontainerって、結局どうつながってる?
volumeって何で必要?
Composeは何をまとめてる?
そもそもDockerの中では何が起きてる?
と聞かれると、少し説明が怪しいところがありました。
そこで今回は、Dockerを初めて触る人でも流れがつながるように、できるだけ難しい言葉を減らして整理してみます。
Dockerは「アプリが動く環境ごと持ち運びやすくする」ための仕組み
Docker公式では、Dockerをアプリケーションの開発・配布・実行のためのプラットフォームとして説明しています。
ポイントは、アプリケーションを container(コンテナ) と呼ばれる隔離された環境で動かせることです。
参考: https://docs.docker.com/get-started/docker-overview/(外部サイト)
たとえばNode.jsのアプリを動かすとして、普通にPCへ直接環境を作るなら、
Node.jsを入れる
必要なライブラリを入れる
環境変数を設定する
ポートを合わせる
DBも用意する
といった準備が必要です。
そして人によって、
「Node 22です」 「いや自分はNode 24です」 「そのライブラリ入ってません」 「そのポート、別のアプリが使ってます」
みたいなことが起きます。
Dockerでは、アプリを動かすために必要な環境をある程度まとめて定義し、その定義から同じような実行環境を作れるようにします。
だから「開発環境の差分を減らす」という理解は、かなり核心に近いです。
ただし、後で触れますが PCの差分を完全になくす魔法ではありません。
まず覚えたいのは Dockerfile → image → container の3つ
Dockerを理解しようとすると、いきなり用語が大量に出てきます。
でも最初は、この3つをつなげるだけでかなり分かりやすくなります。
Dockerfile
→ image
→ container
ここは、マンションを作る話で考えてみます。
Dockerfileは「この部屋こうしといて」という業者への指示書
Dockerfileは、imageを作るための手順を書いたテキストファイルです。
参考: https://docs.docker.com/reference/dockerfile/(外部サイト)
たとえば、
FROM node:24
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install
COPY . .
CMD ["pnpm", "dev"]なら、かなりざっくり言えば、
Node 24の部屋を用意して。
作業場所は /app にして。
必要なファイルを運び込んで。
pnpm installして。
最後はpnpm devで起動して。
という工事指示書です。
Dockerfile自体が動いている環境ではありません。
あくまで「こういう環境を作ってください」という定義です。
imageは、指示書どおり作った「モデルルームの型」
Dockerfileをもとに docker build を実行すると、imageを作れます。
Docker公式ではimageを、containerを作るための読み取り専用テンプレートとして説明しています。
つまり、まだ誰も住んでいない完成済みモデルルームの型みたいなものです。
Node 24が入っていて、必要なパッケージが入っていて、アプリのファイルも入っている。
この型があるから、同じimageから何個でもcontainerを作れます。
containerは、実際に人が入って動き始めた部屋
imageから実際に起動したものがcontainerです。
docker runを実行すると、imageをもとにcontainerが作られ、その中で指定されたプロセスが動きます。
つまり、
Dockerfile = 工事指示書
image = モデルルームの型
container = 実際に稼働している部屋
くらいで考えると、最初はかなり分かりやすいです。

docker buildとdocker runで何が起きているのか
ここまで分かると、いつも打っているコマンドの意味も見えてきます。
docker build
docker build -t my-app .これは、
このDockerfileを読んで、my-appというimageを作ってください
という操作です。
Dockerfileの各命令を順番に処理して、実行環境の型であるimageを組み立てます。
Dockerのimageはレイヤー構造になっているので、変更されていない部分はビルド時に再利用されることがあります。
だからDockerfileの書き方によって、ビルド速度が大きく変わることもあります。
docker run
docker run -p 3000:3000 my-appこれは、my-app imageからcontainerを作って起動する操作です。
公式Docsの説明では、docker runを実行すると、必要ならimageを取得し、containerを作成し、書き込み可能なファイルシステムやネットワークを用意して、最後にcontainer内のプロセスを起動します。
「runだから何となく起動している」ではなく、
imageから実行可能なcontainerを作って、その中のプロセスを動かしている
と考えると理解しやすいです。
containerと仮想マシンは似ているようで違う
ここもDockerで混乱しやすいところです。
「隔離された環境なら、仮想マシンと同じでは?」
と思います。
かなり大きな違いは、containerごとに丸ごと別のOSを持つわけではないことです。
Linux上のDocker Engineでは、Linuxカーネルのnamespaceなどの仕組みを使って、プロセスやネットワークなどを隔離します。
そのため、一般的な仮想マシンより軽量に扱いやすいのが特徴です。
一方で、MacやWindowsでDocker Desktopを使う場合は少し事情が違います。
Docker Desktopでは、Linux containerを動かすためのLinux環境として、軽量なLinux VMの中でDocker Engineが動きます。
参考: https://docs.docker.com/desktop/features/networking/(外部サイト)
つまり、
containerそのものが毎回VMを1台持っているわけではない。
でもDocker Desktopでは、そのcontainerを動かす土台としてLinux VMが使われている。
という理解が近いです。
ここを知らないと、
「DockerはVMじゃないって聞いたのに、Docker DesktopはVM使ってるじゃん」
となります。
ここは、Dockerを学び始めたときに混乱しやすいポイントです。
containerを消したらデータも消える。そこでvolumeが出てくる
Dockerを使い始めると、次に出てくるのがvolumeです。
containerには書き込み可能な領域があります。
ただし、containerを削除すると、そのcontainer内だけに保存していたデータも基本的には消えます。
Webアプリだけなら作り直せばいいこともありますが、DBのデータまで毎回消えたら困ります。
そこで使うのがvolumeです。
Docker公式では、volumeをDockerが管理する永続データの保存先として説明しています。
参考: https://docs.docker.com/engine/storage/volumes/(外部サイト)
マンションの例でいうと、
container = 部屋
volume = 部屋の外にある貸倉庫
みたいな感じです。
部屋を壊して新しくしても、倉庫に置いた荷物は残ります。
たとえばPostgreSQL containerのデータをvolumeへ保存しておけば、containerを作り直してもDBデータを残せます。
bind mountとは何が違う?
開発環境ではbind mountもよく使います。
bind mountは、ホストPC側の実際のフォルダをcontainer内へ直接見せる仕組みです。
参考: https://docs.docker.com/engine/storage/bind-mounts/(外部サイト)
たとえばソースコードをbind mountしておけば、
PC側でコードを書く
→ container側からも同じファイルが見える
という開発環境を作れます。
ざっくり分けると、
volume = Dockerに管理してもらう倉庫
bind mount = 自分のPCの部屋とcontainerに直通のドアを付ける
くらいで考えると分かりやすいです。
networkはcontainer同士の「内線電話」
アプリをDocker化すると、containerが1個とは限りません。
たとえば、
Webアプリ
API
PostgreSQL
Redis
を別々のcontainerで動かすことがあります。
そこで必要になるのがnetworkです。
Dockerのnetworkに接続されたcontainer同士は、設定に応じて通信できます。
Composeでは、特に指定しなくてもアプリ用のデフォルトnetworkが作られ、同じnetworkに参加したservice同士はservice名で見つけられます。
参考: https://docs.docker.com/compose/how-tos/networking/(外部サイト)
たとえばComposeに、
services:
app:
build: .
depends_on:
- db
db:
image: postgresとあれば、app側からdbというservice名を使って接続できる構成にできます。
マンションでいうなら、
network = container同士だけで使える内線電話
みたいなものです。
同じ建物にいるからといって勝手に全部つながるのではなく、どのnetworkへ参加させるかで通信範囲を整理できます。
Docker Composeは「複数の部屋をまとめて開ける管理表」
ここまで来るとComposeがかなり分かりやすくなります。
Docker Composeは、複数containerで構成されるアプリを定義してまとめて動かすための仕組みです。
参考: https://docs.docker.com/compose/(外部サイト)
たとえば、
services:
app:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
db:
image: postgres
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:と書いて、
docker compose upを実行すると、Composeファイルをもとに必要なservice、network、volumeなどをまとめて扱えます。
つまり、
appの部屋を開ける
DBの部屋も開ける
2つを内線でつなぐ
DB用の倉庫も付ける
みたいな準備をまとめてやってくれます。
Dockerを使っていて、
「docker runを何個も覚えるよりComposeの方が楽」
となる理由はここです。

Dockerは起動するだけじゃない。知ると便利な機能がかなりある
Dockerを「環境を作って起動するだけのもの」と思っていたのですが、実際にはデバッグや確認でもかなり便利です。
docker logs:まず中で何が起きたか見る
containerが起動しない、APIが500になる、処理が止まる。
そんなときはログを見ることが多いです。
docker logs my-app追いかけながら見るなら、
docker logs -f my-appのようにできます。
「動かない → 何となく再起動」より、まずログを見るだけで原因が見つかることがあります。
docker exec:動いているcontainerの中へ入って確認する
docker execは、動いているcontainerの中で別のコマンドを実行できます。
公式Docsでも、running container内で新しいコマンドを実行するものとして説明されています。
参考: https://docs.docker.com/reference/cli/docker/container/exec/(外部サイト)
docker exec -it my-app shこれで、
ファイルが本当にあるか
環境変数が入っているか
コマンドが使えるか
設定ファイルがどうなっているか
などをcontainer内から確認できます。
自分のPCを見ているつもりで調べていたら、問題が起きていたのはcontainerの中だった、という切り分けにも便利です。
healthcheck:「起動している」と「使える」は別
containerがrunningになっていても、アプリが正常に応答できるとは限りません。
たとえばプロセス自体は動いているけど、内部で固まっていることもあります。
DockerfileのHEALTHCHECKを使うと、containerが正常に動作しているかをチェックし、healthy / unhealthyといった状態を持たせられます。
参考: https://docs.docker.com/reference/dockerfile/#healthcheck(外部サイト)
ここで大事なのは、healthcheckを書いただけで自動的に全部復旧するわけではないことです。
まず「起動しているように見えるけど、実は壊れている」を検知しやすくする仕組みです。
Docker Desktopの画面も意外と便利
Docker Desktopでは、container、image、volume、ログ、リソース使用量などをGUIから確認できます。
参考: https://docs.docker.com/desktop/use-desktop/(外部サイト)
CLIですべて操作できるとしても、
「今どのcontainerが生きてる?」
「このvolume何だっけ?」
「メモリ食ってるcontainerある?」
みたいな確認はGUIの方が早いこともあります。
Docker=黒い画面だけ、と考えず、確認内容に応じてGUIも使い分けられます。
「DockerならどのPCでも完全に同じ」は少し言いすぎ
ここまで見ると、
Dockerを使えば環境差分は全部なくなるのでは?
と思いたくなります。
でも、そこまではいきません。
たとえば、
IntelとArmなどCPUアーキテクチャの違い
Mac / Windows / LinuxのホストOS差
bind mountしたファイルシステムの違い
ファイル権限
ポート競合
Docker Desktopへ割り当てたCPU・メモリ
.envなど外から渡す設定値
接続先APIやDBの状態
imageのタグや更新タイミング
などは差分になり得ます。
Dockerが揃えやすくしてくれるのは、アプリを動かす環境の大きな部分です。
「PCそのものを完全コピーする」のではありません。
だから最初の説明も、
「PCの差分をなくす」
より、
「開発・実行環境の差分をかなり減らし、同じ構成を再現しやすくする」
の方が正確です。
Dockerを使う意味は「環境構築を楽にする」だけではなかった
今回整理してみて、Dockerの見え方が少し変わりました。
最初は、
環境差分を減らすための便利ツール
くらいの認識でした。
でも実際には、
Dockerfileで環境を定義する
→ imageとして同じ型を作る
→ containerとして実行する
→ volumeでデータを外へ逃がす
→ networkでcontainer同士をつなぐ
→ Composeでアプリ全体をまとめる
という、一連の仕組みになっています。
これが分かると、Dockerfileやcompose.yamlを見たときも、
「よく分からない呪文」
ではなく、
このアプリは、どんな環境を作って、何個のcontainerを動かして、どこにデータを残して、どう通信しているのか
という設計図に見えてきます。
Dockerを使えることと、Dockerを説明できることはやっぱり別でした。
でも最初からnamespaceやcgroupを全部覚える必要はないと思います。
まずは、
Dockerfile → image → container
をつなげる。
次に、
volume / network / Compose
を足す。
そして困ったら、
logs / exec / healthcheck
を見る。
この順番なら、何となく使っていたDockerがかなり理解しやすくなりました。
次にDockerを触るときは、compose upが通ったことだけで安心せず、
「今、どのimageから何のcontainerが動いて、データはどこに残ってる?」
くらいは一度見てみると、Dockerの理解が一気につながると思います。

