仕組みから学ぶ Web
応用読了目安 14#Docker#コンテナ#イメージ#デプロイ

コンテナ — 「手元では動く」を構造的に潰す

仮想マシンとの違いはどこか。イメージが層でできていることが、ビルド時間とデプロイの両方を決める。

この記事の進み方

What — 仮想マシンとの違い

Compare何を共有し、何を分離するか

ハードウェアを仮想化し、ゲスト OS を丸ごと動かす。

┌─────────┬─────────┐
│  アプリ  │  アプリ  │
│ ライブラリ│ライブラリ │
│ ゲスト OS │ ゲスト OS │  ← OS ごと入っている
├─────────┴─────────┤
│   ハイパーバイザ    │
│     ホスト OS      │
│    ハードウェア     │
└───────────────────┘
起動時間
数十秒〜数分
サイズ
数 GB
分離の強度
強い
別 OS の実行
できる
  • OS レベルで分離されるため、セキュリティ境界として信頼できる。
  • 異なるカーネル(Linux 上で Windows など)を動かせる。

コンテナは OS カーネルを共有し、ユーザー空間だけを分離する。この違いが、起動時間とサイズの差を生む。

イメージは層でできている

FigureDockerfile の各命令が層になる
FROM node:20-slim+ OS + Nodeベースイメージほぼ変わらない
COPY package*.json ./+ package.json依存関係の定義だけを先にコピー依存を追加したときだけ変わる
RUN npm ci+ node_modules依存をインストール最も重い工程
COPY . .+ アプリのコードソースコードをコピー毎回変わる
RUN npm run build+ 成果物ビルド毎回実行される
キャッシュが効く範囲
OS + Nodeデータ本体
1/5
FROM node:20-slim最も変わりにくい。一度取得すればキャッシュされ続ける。タグではなくダイジェストで固定すると、より再現性が高まる。

変更のあった層より下(後ろ)だけが再ビルドされる。だから、変わりにくいものを先に書くとビルドが速くなる。

この順序が逆だと、ソースを1行変えるたびに npm ci が走ります。 ビルドが5分から30秒になるかどうかは、この数行の順序で決まります。

マルチステージビルド

ビルドに必要なものと、実行に必要なものは違います。

# ① ビルド用ステージ(コンパイラや devDependencies が必要)
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ② 実行用ステージ(成果物だけを持ってくる)
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
USER node                      # root で動かさない
CMD ["node", "dist/main.js"]

最終イメージには、ビルドツールもソースコードも含まれません。 サイズが小さくなるだけでなく、攻撃対象も減ります。

イメージ
コンテナの雛形。読み取り専用の層の集まり。
コンテナ
イメージから起動した実行中のインスタンス。書き込み可能な層が1つ上に乗る。
レイヤー
Dockerfile の命令ごとに作られる差分。キャッシュの単位でもある。
ダイジェスト
イメージの内容から計算されるハッシュ(sha256:...)。タグと違い、絶対に変わらない。
タグ
イメージに付ける別名。上書きできるので、同じタグが別のイメージを指すことがある。
ボリューム
コンテナの外にデータを永続化する仕組み。コンテナを消してもデータは残る。
確認 — ここまで読めたか

image: myapp:latest を本番で使うと、何が起きますか?

まず選ぶ(解答例は a〜d の記号で説明します)

Why — なぜ「手元では動く」が起きなくなるのか

「手元では動く」の原因は、環境の差です。

  • OS のバージョンが違う
  • ライブラリのバージョンが違う
  • 環境変数が違う
  • そもそも入っているものが違う

コンテナは、アプリと、それが依存するもの全部を1つのイメージに固めることでこれを解消します。イメージが同じなら、どのホストで動かしても中身は同じです。

ただし、完全に同じにはなりません

  • ホストのカーネルバージョンは共有される
  • CPU アーキテクチャ(arm64 / amd64)は違いうる
  • 環境変数・シークレット・接続先は外から注入される
  • ボリュームでマウントされたデータは環境ごとに違う

**「差分をゼロにする」のではなく、「差分を環境変数とボリュームだけに絞る」**というのが正確な理解です。

コンテナは使い捨て

コンテナの設計思想は「いつ消えてもよい」です。

  • ログはファイルではなく標準出力に書く(コンテナが消えても収集済み)
  • 状態はコンテナ内に持たない(DB やオブジェクトストレージに置く)
  • 起動に時間をかけない(スケールアウトの速さが利点)
  • 停止シグナルを受けたら、処理中のリクエストを終えてから終了する(graceful shutdown)

この前提が守られていないと、オートスケールもローリングデプロイも安全に使えません。

確認 — ここまで読めたか

Dockerfile でアプリのコードを先にコピーし、その後に依存をインストールすると何が起きますか?

まず選ぶ(解答例は a〜d の記号で説明します)

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

この Dockerfile の問題点を、影響の大きい順に挙げてください

与えられた条件
  • FROM node:latest
  • WORKDIR /app
  • COPY . .
  • RUN npm install
  • RUN npm run build
  • ENV DB_PASSWORD=prod_secret_123
  • EXPOSE 3000
  • CMD ["npm", "start"]
  • 運用状況: ビルドに毎回 6 分かかる。イメージサイズは 1.4GB。デプロイは image: myapp:latest で指定している
この軸で考える
  • · 再現性を壊しているのはどこか
  • · ビルド時間とイメージサイズの原因はどこか
  • · セキュリティ上、最も重大な問題はどれか
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「切り戻せなかった」障害の振り返りで、何が原因だったかを説明してください

与えられた条件
  • マニフェストは latest を指定していた
  • ノードによって動いているバージョンが違った
  • 再発防止まで決めたい

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。

この知識を使う実践ケース