- 技術 💻
Dockerの落とし穴8選|レイヤ・latest・PID 1
Dockerって、動かすだけなら1日で覚えられるんですよね。
でも中身の理解は別で、「軽いVM」だと思ったまま使っている人が本当に多い。その誤解が、セキュリティ事故とビルド遅延の温床になります。今回は、「消したはずの秘密鍵が、レイヤに眠っている」話から、コンテナの正体が見えるものを8つ集めました。1つでも知らなかったら儲けものです。
① コンテナは、VMではなく「ただのプロセス」です
すべての出発点です。
コンテナの中でアプリを動かすと、Linuxのホストなら、psコマンドからそのプロセスが普通に見えます。仮想マシンのような「別のコンピュータ」は、どこにも存在しません。
コンテナの正体は、名前空間(見える世界の隔離)とcgroups(使える資源の制限)を着せられた、ホスト上の普通のプロセスです。
起動が一瞬なのも、オーバーヘッドが小さいのも、「隔離された起動済みOS」ではなく「隔離されたプロセス」だから。この一枚の絵が、以降の全部を説明します。
② 削除したファイルは、レイヤに残っています
セキュリティ事故に直結する話です。
COPY secret.key /app/
RUN build-with-secret
RUN rm /app/secret.key # 消した…つもり
イメージはレイヤ(差分)の積み重ねでできています。後のレイヤでrmしても、それは「削除した」という差分が積まれるだけで、secret.keyを含むレイヤは、イメージの中にそのまま残っています。
イメージを展開すれば取り出せる。つまり配布した瞬間、秘密は漏れています。
対策は、秘密をレイヤに入れないこと。ビルド時シークレットの仕組み(RUN --mount=type=secret)やマルチステージビルドを使うんです。
③ latestは、「最新」という機能ではありません
image:latest と書くと最新版が来る——半分誤解です。
latestは、ただのデフォルトのタグ名です。「タグを省略したときに使われる名前」というだけで、最新版を指す保証も、自動更新される魔法もありません。push側がlatestを付け忘れれば、古いままです。
さらに実務では、latest運用は「どの版が動いているか分からない」という再現性の敵になります。
本番は、バージョンやダイジェストでピン留めする。latestは名前であって、約束ではないんです。
④ コンテナの中に、カーネルはありません
「UbuntuのコンテナはUbuntuが動いている」——半分だけです。
コンテナに入っているのは、ライブラリとコマンド群だけ。カーネルはホストのものを共有しています。だからコンテナ内で uname -r を打つと、ホストのカーネルバージョンが出ます。
ちなみにMacやWindowsのDocker Desktopでは、裏で小さなLinuxの仮想マシンが動いていて、そのカーネルを共有しています。
これが、①の「別のコンピュータではない」の証拠です。そしてこれが、コンテナが軽い理由であり、「カーネルに依存する機能はホスト次第」という制約の理由でもあるんです。
⑤ PID 1問題——コンテナの中の「1番」は特別です
コンテナで最初に起動したプロセスは、PID 1になります。この番号、特別扱いなんです。
PID 1は、シグナルのデフォルト動作が無効(明示的にハンドリングしないとCtrl+CやSIGTERMで止まらない)で、さらに孤児プロセスの後始末(回収)役を背負います。
普通のアプリはこの役目を想定していないので、「docker stopが10秒待って強制killになる」「ゾンビプロセスが溜まる」という症状が出ます。
対策は、軽量なinit(tiniなど)をPID 1に立てること。docker runの--initオプション一発で解決する、知ってるだけ得な話です。
⑥ ビルドキャッシュは、「行の順番」で決まります
Dockerfileのビルドが遅い——順番を見直すだけで数分が数秒になることがあります。
ビルドは命令ごとに上からキャッシュされ、どこかの行が変わると、それ以降は全部作り直しです。だから定石はこうなります。
COPY package.json ./ # 変わりにくいものを先に
RUN npm install # ← ソース変更ではキャッシュが効く
COPY . . # 変わりやすいものは最後
ソースを1文字直すたびに依存の全インストールが走っていたら、それは順番の問題。「変わりにくい順に書く」がDockerfileの文法なんです。
⑦ コンテナ内のrootは、思ったより「本物」です
「コンテナの中だから、rootでも隔離されていて安全」——過信は禁物です。
既定の構成では、コンテナ内のrootは、ホストのrootと同じユーザーIDです。隔離の壁はありますが、ホストのディレクトリをマウントしていれば、そのファイルにはホストのroot権限で触れます。
だから定石は2つ。
- アプリは非rootユーザーで動かす(Dockerfileで
USER指定) - マウントは必要最小限に
「中だから大丈夫」ではなく「壁を薄くする設定をしていないか」で考えるんです。
⑧ コンテナは、「消える」前提で設計します
コンテナ内でファイルに書き込んだデータは、コンテナを消した瞬間に一緒に消えます。書き込み層は使い捨てです。
これは欠点ではなく思想で、「コンテナはいつでも作り直せる、状態を持たない実行単位」として扱うのが前提なんです。
残したいデータ(DBの実体、アップロードファイル、ログ)は、ボリュームとして外に出す。
「このコンテナ、今すぐ消して作り直しても大丈夫か?」——この問いにYESと言える設計が、コンテナらしい設計なんです。
番外編:手元で確かめられる小ネタ
レイヤの歴史を見る
docker history <image>
各レイヤが「どの命令で、何MB増えたか」の一覧が出ます。②の話も、肥大化の犯人も、ここで見つかります。
.dockerignoreを忘れずに
COPY . . の前に、.gitやnode_modulesを除外しておく。ビルドが速くなり、⑥のキャッシュも無駄に壊れなくなります。
「軽いイメージ」には流儀の違いがあります
超軽量イメージの代表Alpineは、標準Cライブラリが一般的なglibcではなくmuslです。軽さと引き換えに互換の罠があるので、「小さいから」だけで選ばないのが安全です。
RJCでも、コンテナと向き合っています
開発環境の再現性は、チーム開発の立ち上がり速度そのものなんですよね。⑧の「いつでも作り直せる」思想は、環境構築の属人化を消してくれます。
こういう仕組みの話を、Techチームで共有しながら開発しています。
よくいただくご質問
Q. まずどれから押さえるべきですか?
A. ②のレイヤかなと思います。秘密情報の扱いは、知らなかったでは済まない事故になるんですよね。
Q. VMとコンテナ、どう使い分ければいいですか?
A. ④が判断基準になります。カーネルごと分けたい(別OS・強い隔離)ならVM、同じカーネルでプロセスを隔離すれば足りるならコンテナ、ですね。
Q. イメージが肥大化します…
A. 番外編のdocker historyで犯人を特定して、⑥の順番整理とマルチステージビルドで絞るのが王道です。大きく削れることが多いです。
Q. 未経験からでも、分かるようになりますか?
A. なります。「コンテナ=衝立で仕切られた同じ部屋のプロセス」という絵が描ければ、挙動が全部その絵で説明できるんです。
「軽いVM」を卒業した日から、強くなります
今日の8つに共通していたのは、これでした。
コンテナの事故も遅さも、「別のコンピュータだと思っていた」誤解から生まれます。正体はプロセスで、イメージは差分の積み重ねで、カーネルは共有。
この3点が腑に落ちた人は、トラブルの原因を「Dockerの謎」ではなく「Linuxの仕組み」として調べられるようになるんですよね。
みなさんがコンテナで踏んだ事故、よかったら教えてください。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。