「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ならレイヤーにシークレットが残らない
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
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:を使った場合はdocker inspectのEnvセクションに出てくるため、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 # シークレットを必ずローテーションすること(これが最重要)
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 composeのsecrets:または外部Secret Managerを使う |
ENVとARGの使い分けは「実行時に必要かどうか」が判断軸だ。そしてどちらにもシークレットは書かない。これがDockerの環境変数設計の基本ルールだ。BuildKit secretsを使いこなすことで、セキュアなDockerfileが書けるようになる。
次に読む記事
・Dockerのセキュリティ設計|非rootユーザー実行とrootlessモードでコンテナを堅牢化する方法
・DockerfileのENTRYPOINTとCMDを正しく使い分ける方法
・Dockerfileの書き方入門|自分のアプリをイメージ化する基礎と注意点
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、ENV・ARG・secretsのDocker環境変数設計から本番運用まで体系的に学べる講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 次のページへ:docker saveとdocker loadでイメージをオフライン移送する方法|閉域網サーバーへの持ち込み運用
- 前のページへ:docker buildxでマルチアーキテクチャイメージをビルドする方法|ARM・x86両対応のビルド設計
- この記事の属するカテゴリ:Dockerへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます