はじめに
Dockerfile は小さなテキストファイルですが、ここが雑だと運用は一気に不安定になります。たとえば、
latestを使ってしまい、ある日突然ビルドが壊れるapt-get updateの後に掃除がなくてイメージが肥大化する- root 実行のまま本番に入ってしまう
curl | shのような危険パターンが紛れ込む- 重要なルールが「人の記憶」や「レビュー担当の好み」に依存して揺れる
こういう事故は、個々人が注意深くなるだけでは防ぎきれません。人は忙しいし、レビュー観点は増えるし、知識差もあります。
そこで効くのが “ポリシー化” です。
- Hadolint:Dockerfile の静的解析(ベストプラクティス・危険パターン・スタイル)
- OPA/Conftest:組織ルール(例:
USER必須、latest禁止、特定ベースイメージのみ許可)を コードとして 強制
この2つを組み合わせると、Dockerfile レビューが「指摘大会」から「ルールに沿っているかの確認」になり、品質が安定します。
この記事では、“CIで落ちる仕組み” まで含めて、座学→ハンズオンで手を動かしながら導入していきます。
座学
1) Hadolint と OPA/Conftest は役割が違う
まずここが肝です。どちらも “チェック” をしますが、得意領域が違います。
Hadolint(Dockerfile専用Linter)
- Dockerfile の一般的ベストプラクティスを広くカバー
- ありがちなミス(
apt-get updateの扱い、sudo、危険なADD、ピン留め不足など)を早期に検出 - “良い書き方”を教えてくれる
OPA/Conftest(ポリシーエンジン)
- 会社・チームの規約を 明文化 して強制
- “一般論”ではなく、あなたの現場のルールに寄せられる
- 例:
FROMは社内ミラーのみ許可 - 例:本番は
USER必須、EXPOSEの番号制限 - 例:
apk addには--no-cache必須
- 例:
- 例外(許可リスト)や環境別ルールも表現できる
結論:
Hadolintで一般的な品質を底上げし、Conftestで組織ルールを“ブレなく”強制する、が最も運用しやすいです。
2) 「レビューで見るべきこと」を機械にやらせる
Dockerfile レビューで毎回見る観点は、だいたい固定です。
- 再現性:
latestを使っていないか、依存が固定されているか - 安全性:root 実行か、危険コマンドがないか、秘密情報を埋め込んでいないか
- サイズ:キャッシュ・掃除、不要ファイルの混入(
.dockerignore) - 保守性:意味のある
LABEL、分かりやすい順序、コメント
これを人が毎回 “目視” でやると、見落としと属人化が起きます。
CIで落ちるようにすると、レビューは「設計や意図」に集中できるようになります。
3) ポリシー化の現実解:厳しすぎると嫌われる
導入初期にありがちな失敗は、最初からガチガチにして PR が通らなくなることです。
おすすめは段階導入:
- 警告(Lintはレポートだけ):まず現状を可視化
- 新規・変更分だけ必須:既存資産を一気に直さず、将来の負債増加を止める
- 本番向けDockerfileだけ厳格化:dev 用は緩め、本番だけ強制
この順番でやると反発が少なく、ルールが定着します。
4) Conftestのイメージ:Dockerfileを“構造化”してから判定する
Conftest(OPA/Rego)は JSON/YAML など構造化データに対して強いです。
Dockerfileはテキストなので、そのままだと扱いにくいのですが、ここで使うのが dockerfile-parse(Python)などのパーサです。
流れはこうです:
- Dockerfile をパースして JSON を作る(命令一覧、FROM、USER、RUN など)
- Conftest が JSON に対して Rego ルールで判定する
- ルール違反なら CI を落とす
つまり、Dockerfileを“検査できる形”に変換してからポリシー適用するのが実務的です。
ハンズオン
ここでは最小構成で「DockerfileをチェックしてCIで落とす」ところまで作ります。
以下の構成を想定します。
repo/
Dockerfile
policy/
dockerfile.rego
scripts/
dockerfile_to_json.py
.github/
workflows/
dockerfile-policy.yml
0) 例として“あえて微妙な”Dockerfileを用意する
まず、違反を検出できるかを確認するためのDockerfileです。
Dockerfile
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y curl
CMD ["bash", "-lc", "echo hello"]
このDockerfileには問題が複数あります:
latest使用(再現性が揺れる)apt-get updateとapt-get installが分離(キャッシュ・整合性・セキュリティ的にも微妙)rm -rf /var/lib/apt/lists/*が無くて肥大化しやすいUSER指定なし(rootのまま動く)
1) Hadolint をローカルで回してみる(まずは可視化)
Hadolint はコンテナでも実行できます。ローカルで試すと理解が早いです。
docker run --rm -i hadolint/hadolint < Dockerfile
ここで出る指摘は“一般的ベストプラクティス”です。
ただし、あなたの現場の事情(社内ベースイメージ縛り等)はHadolintだけでは表現しづらい。そこで次が Conftest です。
2) Dockerfile を JSON に変換するスクリプトを用意する
Conftest は JSON/YAML を扱うのが得意なので、Dockerfile をパースして JSON にします。
ここでは Python の dockerfile-parse を使った最小スクリプト例を置きます。
scripts/dockerfile_to_json.py
import json
import sys
from dockerfile_parse import DockerfileParser
def main():
path = sys.argv[1] if len(sys.argv) > 1 else "Dockerfile"
dfp = DockerfileParser(path=path)
# 代表的に使いやすい形へ整形
instructions = []
for entry in dfp.structure:
# entry: {instruction, value, original, startline, endline, ...}
instructions.append({
"instruction": entry.get("instruction", "").upper(),
"value": entry.get("value", ""),
"original": entry.get("original", ""),
"startline": entry.get("startline"),
"endline": entry.get("endline"),
})
doc = {
"path": path,
"baseimage": dfp.baseimage, # FROM
"instructions": instructions,
}
print(json.dumps(doc, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
ローカルで実行するには(python環境がある場合):
pip install dockerfile-parse
python scripts/dockerfile_to_json.py Dockerfile > dockerfile.json
cat dockerfile.json
JSON になれば、Conftest 側で柔軟に判定できます。
3) Conftest(OPA/Rego)で“組織ルール”を定義する
次に、あなたのチームのルールを “コード” にします。ここでは例としてよく効く3つを入れます。
latestを禁止(FROMタグが latest ならNG)USERが必須(本番想定)apt-get updateとapt-get installは同一RUNで、かつ掃除を要求(基本のサイズ・安定性)
policy/dockerfile.rego
package dockerfile.policy
default deny = []
# 1) FROM latest 禁止
deny[msg] {
endswith(lower(input.baseimage), ":latest")
msg := "FROM で :latest を使用しています(再現性が揺れるため禁止)"
}
# 2) USER 必須(少なくとも1回はUSER命令が必要)
deny[msg] {
not has_user
msg := "USER が指定されていません(root実行を避けるため USER を必須にしてください)"
}
has_user {
some i
input.instructions[i].instruction == "USER"
}
# 3) apt-get update/install の分離禁止 + 掃除必須(簡易チェック)
deny[msg] {
some i
inst := input.instructions[i]
inst.instruction == "RUN"
contains(inst.original, "apt-get update")
not contains(inst.original, "apt-get install")
msg := "apt-get update が単独RUNです(updateとinstallは同一RUNにまとめてください)"
}
deny[msg] {
some i
inst := input.instructions[i]
inst.instruction == "RUN"
contains(inst.original, "apt-get install")
not contains(inst.original, "rm -rf /var/lib/apt/lists")
msg := "aptのキャッシュ掃除がありません(rm -rf /var/lib/apt/lists/* を同一RUNに含めてください)"
}
注:この例は“分かりやすさ優先”の簡易判定です。厳密にやるなら命令の分解や許可パターン(例外)も入れられます。まずは運用で回る最小から始めるのがコツです。
Conftest をローカル実行(Conftest が入っている場合):
conftest test dockerfile.json -p policy
違反があると deny が出て終了コードが非0になります。CIで落とせます。
4) GitHub Actionsで “Dockerfile変更時に必ず検査” する
最後にCIに繋ぎます。ここでは
- Hadolint はコンテナで実行
- Conftest は公式セットアップ+PythonでJSON生成
という形の例を出します。
.github/workflows/dockerfile-policy.yml
name: Dockerfile Policy
on:
pull_request:
paths:
- "Dockerfile"
- "policy/**"
- "scripts/**"
push:
paths:
- "Dockerfile"
- "policy/**"
- "scripts/**"
jobs:
lint-and-policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 1) Hadolint(Dockerfileの一般Lint)
- name: Hadolint
- run: |
docker run --rm -i hadolint/hadolint < Dockerfile
# 2) Python環境(Dockerfile→JSON)
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install parser
run: pip install dockerfile-parse
- name: Convert Dockerfile to JSON
run: python scripts/dockerfile_to_json.py Dockerfile > dockerfile.json
# 3) Conftest(OPA/Regoで組織ルールを強制)
- name: Setup Conftest
uses: instrumenta/conftest-action@v0.4.0
with:
version: "0.56.0"
- name: Conftest policy check
run: conftest test dockerfile.json -p policy
これで、PRで Dockerfile を触ったら 必ず lint/policy が走り、違反があれば落ちます。
レビュー担当が「言う/言わない」で揺れないのが最大の価値です。
5) ルール違反を直して“通る”Dockerfileにする
先ほどのDockerfileを、最低限ポリシーを満たす形に直します。
Dockerfile(修正版の例)
FROM ubuntu:24.04
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# 最小権限ユーザー(例)
RUN useradd -m -u 10001 appuser
USER appuser
CMD ["bash", "-lc", "echo hello"]
latestをやめて固定タグへ- apt は1RUNにまとめ、掃除を同じRUNで実施
USERを指定して root 実行を避ける
この時点で Hadolint/Conftest が通るようになり、ルールが仕組みとして機能していることが確認できます。
まとめ
Dockerfile の品質は、プロダクトの安定性・セキュリティ・開発体験に直結します。にもかかわらず、レビューだけに頼ると 属人化 と 見落とし が避けられません。
- Hadolintで一般的ベストプラクティスを網羅的に検出
- OPA/Conftestで組織ルールを “コード化” し、CIで強制
- Dockerfile を JSON にパースしてから判定する構成にすると、運用が現実的
- 導入は段階的(警告→変更分→本番のみ厳格)にすると定着しやすい
この仕組みが入ると、レビューは「ルール指摘」から「設計・意図の議論」へ移り、チームの生産性が上がります。
ポリシーは “縛る” ためではなく、事故の確率を下げるための自動化です。
コメントを残す