DockerのENV・ARG・env_fileを正しく使う設計|ビルド時と実行時の値の渡し方とsecrets

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Docker > DockerのENV・ARG・env_fileを正しく使う設計|ビルド時と実行時の値の渡し方とsecrets
「DockerfileにAPIキーを直書きしたら、git logにずっと残ってしまった」
「ENVとARGの違いがよくわからず、とりあえずENVを使っているが本当に安全なのか不安だ」

コンテナ開発を進める中で、環境変数とシークレットの扱いに迷うエンジニアは多い。ENVとARGは書き方が似ているが、値が「どのタイミングで」「どこに」残るかがまったく異なる。誤った使い方をすると、APIキーやパスワードがイメージレイヤーに焼き込まれ、docker historyコマンド一発で誰にでも見えてしまう。

この記事では、DockerのENV・ARG・env_fileの正しい使い分けと、BuildKit secretsを使ったシークレット管理を実例で解説する。docker composeでの設計パターンとよくあるミスへの対策も含め、RHEL 9.4 / Ubuntu 24.04 LTSで動作確認済みだ。

この記事のポイント

・ENVは実行時の変数、ARGはビルド時限定でイメージに残らない
・ARGにシークレットを渡すとdocker historyで漏洩する危険がある
・env_fileで.envを外部化し必ず.gitignoreに追加する
・BuildKit --mount=type=secretならレイヤーにシークレットが残らない


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

ENVとARGの根本的な違い

DockerfileにはENVとARGという2つの変数宣言命令があるが、用途がまったく異なる。混同すると、意図せずシークレットがイメージに焼き込まれる事故を招く。

命令 有効なタイミング イメージへの残留 実行時の上書き
ENV ビルド時 + 実行時 残る(docker inspectで確認可) docker run -e KEY=val image で可能
ARG ビルド時のみ 残らない(最終イメージ外) 不可(実行時には存在しない)

重要なのは「ARGはイメージに残らないからシークレットを渡せる」という考え方が誤りだという点だ。ARGの値はビルドキャッシュの中間レイヤーに記録されており、docker history --no-truncを実行すると値が丸見えになる。つまり、ARGもENVも、シークレットを直接渡す用途には使えない

DockerfileでのENV・ARGの正しい書き方

1. ENVの書き方と実行時の上書き

ENVはコンテナの実行中も使われる変数に使う。アプリのポート番号・ログレベル・言語設定など、環境によって変えたい設定値が主な用途だ。

# Dockerfile FROM node:20-slim # 実行時も使われる変数 ENV PORT=3000 ENV NODE_ENV=production ENV LOG_LEVEL=info WORKDIR /app COPY . . RUN npm ci --omit=dev CMD ["node", "server.js"]

実行時に値を変えたい場合は-eオプションを使う。

# 実行時にLOG_LEVELをdebugに上書き $ docker run -e LOG_LEVEL=debug myapp:latest # 動作確認(コンテナ内の環境変数を確認) $ docker exec printenv LOG_LEVEL debug

2. ARGの書き方とデフォルト値

ARGはビルド時にだけ必要な値に使う。ベースイメージのバージョンや、ビルド中に使うツールのバージョン指定が典型的な用途だ。

# Dockerfile ARG NODE_VERSION=20 FROM node:${NODE_VERSION}-slim ARG APP_VERSION=1.0.0 LABEL version=${APP_VERSION} WORKDIR /app COPY . . RUN npm ci --omit=dev CMD ["node", "server.js"]

# ビルド時にARGを渡す $ docker build --build-arg NODE_VERSION=22 --build-arg APP_VERSION=2.1.0 -t myapp:2.1.0 .

3. ARGをENVに引き渡すパターン

ビルド時に値を決め、実行時にも使いたい場合はARGで受け取ってENVに代入する。ただしこのパターンはENVに値が入った時点でイメージに残るため、シークレットには絶対に使わないこと。

# シークレット以外の値に限り有効なパターン ARG APP_ENV=production ENV NODE_ENV=${APP_ENV}

env_fileで環境変数を外部ファイルに切り出す

1. .envファイルの書き方

接続先ホスト名やポートなど、秘密度は低いが環境ごとに変わる設定は.envファイルにまとめて管理すると便利だ。

# .env(シークレットは書かない) DB_HOST=db.internal DB_PORT=5432 APP_TIMEZONE=Asia/Tokyo

必ず.gitignore.envを追加すること。

# .gitignore .env .env.* !.env.example

2. docker run --env-fileの使い方

$ docker run --env-file .env myapp:latest # 実際の出力例(環境変数が渡っていることを確認) $ docker run --rm --env-file .env myapp:latest printenv DB_HOST db.internal

env_fileで渡した変数はコンテナのプロセス環境変数として展開されるだけで、Dockerfile内のENVには影響しない点に注意。Docker Compose経由でenv_file:を使った場合はdocker inspectEnvセクションに出てくるため、Compose環境でも秘密情報はsecretsキーで渡すべきだ。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、DockerのENV設計からBuildKit secrets・Composeでのシークレット管理まで実務で使えるスキルを短期間で習得できる講座を用意しています。
Dockerマスター講座の詳細はこちら >>

BuildKit secretsでシークレットを守る(推奨)

1. なぜARGではシークレットを渡せないか

よくあるミスが「ARGはイメージに残らないからシークレットを渡せる」という誤解だ。実際にはdocker history --no-truncでビルド時のARG値が確認できてしまう。

# 危険な例(ARGでAPIキーを渡すと漏洩する) ARG API_KEY RUN curl -H "Authorization: Bearer ${API_KEY}" https://api.example.com/data $ docker build --build-arg API_KEY=sk-xxxx -t myapp . # historyでAPIキーが見えてしまう $ docker history --no-trunc myapp | grep API_KEY sha256:abc123... /bin/sh -c #(nop) ARG API_KEY=sk-xxxx

2. --mount=type=secretの使い方

BuildKit secretsを使えば、シークレットはビルド中のRUN命令にだけ一時的にマウントされ、最終イメージのどのレイヤーにも残らない

# Dockerfile(BuildKit secrets使用) # syntax=docker/dockerfile:1 FROM ubuntu:24.04 RUN --mount=type=secret,id=api_key API_KEY=$(cat /run/secrets/api_key) && curl -H "Authorization: Bearer ${API_KEY}" https://api.example.com/data -o /app/data.json

# ファイルからシークレットを渡す $ echo "sk-xxxx" > api_key.txt $ docker build --secret id=api_key,src=api_key.txt -t myapp . # 環境変数から渡す(CI環境で便利) $ docker build --secret id=api_key,env=API_KEY -t myapp . # docker historyでシークレットは出てこない(漏洩しない) $ docker history --no-trunc myapp | grep api_key (出力なし)

3. 実行時のシークレット管理(Compose secrets)

実行時にシークレットが必要なケースでは、Docker Compose secretsを使う。

# compose.yml services: app: image: myapp:latest secrets: - db_password environment: DB_HOST: db.internal secrets: db_password: file: ./secrets/db_password.txt

シークレットは/run/secrets/db_passwordとしてコンテナ内にファイルでマウントされる。アプリ側でこのファイルを読み込む実装にすることで、環境変数に平文のパスワードを渡さずに済む。

docker-composeでの環境変数設計

1. environmentキーとenv_fileキーの使い分け

# compose.yml services: app: image: myapp:latest environment: # 固定値や非秘密の設定 NODE_ENV: production PORT: 3000 env_file: # 環境依存の変数を外部ファイルで管理 - .env

2. 本番・開発環境の設定分離

# .env.dev(開発環境用) DB_HOST=localhost LOG_LEVEL=debug # .env.prod(本番環境用・CI/CDの秘密変数から生成) DB_HOST=prod-db.internal LOG_LEVEL=warn

# 開発環境で起動 $ docker compose --env-file .env.dev up # 本番環境で起動 $ docker compose --env-file .env.prod up

compose.yml自体は環境差分を持たず、.env.*ファイルで差分を吸収する設計がシンプルで管理しやすい。

よくあるエラーと対処法

1. docker history でAPIキーが見える

ARGにシークレットを渡した場合、docker history --no-truncでビルド引数が丸見えになる。対策はBuildKit secretsへの移行だ。 既存のDockerfileでARGを使っていたら、--mount=type=secretに置き換える。

2. .envをコミットしてしまった場合

誤って.envをコミット・プッシュしてしまった場合の対処は以下の手順で行う。

# .gitignoreに追加(これ以降のコミットで無視される) echo ".env" >> .gitignore # git履歴から.envを削除(破壊的操作。チーム全員への周知が必要) git filter-branch --force --index-filter "git rm --cached --ignore-unmatch .env" HEAD # シークレットを必ずローテーションすること(これが最重要)

注意: git履歴から削除してもリモートリポジトリへのプッシュ済みシークレットは漏洩している可能性がある。必ずAPIキーやパスワードのローテーションを行うこと。

3. docker compose env_fileで変数が反映されない

env_file:を指定したのに変数が反映されない場合、ファイルの書き方に問題があることが多い。

# NG(=の前後にスペースがあるとパースエラー) DB_HOST = localhost # OK(スペースなし) DB_HOST=localhost # NG(クォートが不要) DB_PASSWORD="mypassword" # OK(クォートなし) DB_PASSWORD=mypassword

まとめ

やりたいこと 正しい方法
実行時に変わる設定値 ENV KEY=valでDockerfileに定義、docker run -e KEY=val imageで上書き
ビルド時だけ使う値 ARG KEY=defaultでDockerfileに定義、docker build --build-arg KEY=valで渡す
環境依存の非秘密変数 docker run --env-file .env myapp:latestまたはCompose env_file:
ビルド時のシークレット(APIキー等) docker build --secret id=key,env=KEY myappでBuildKit secretsを使う
実行時のシークレット(DBパスワード等) docker composesecrets:または外部Secret Managerを使う

ENVとARGの使い分けは「実行時に必要かどうか」が判断軸だ。そしてどちらにもシークレットは書かない。これがDockerの環境変数設計の基本ルールだ。BuildKit secretsを使いこなすことで、セキュアなDockerfileが書けるようになる。

次に読む記事
Dockerのセキュリティ設計|非rootユーザー実行とrootlessモードでコンテナを堅牢化する方法
DockerfileのENTRYPOINTとCMDを正しく使い分ける方法
Dockerfileの書き方入門|自分のアプリをイメージ化する基礎と注意点

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、ENV・ARG・secretsのDocker環境変数設計から本番運用まで体系的に学べる講座を用意しています。
Dockerマスター講座の詳細はこちら >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。