Dockerfile の書き方入門|CMD と ENTRYPOINT の違い・秘密の渡し方まで

PR
Dockerfile の書き方入門|CMD と ENTRYPOINT の違い・秘密の渡し方まで
この記事は約48分で読めます。

Dockerfile は、Docker のイメージ(コンテナを作るためのひな形)を組み立てるために、コマンドラインで打つコマンドを全部書いたテキストの文書です。Docker はこの文書を読んで、イメージを自動で作ります。

この記事は、Docker を触り始めて、Dockerfile を読み書きできるようになりたい人向けです。最小の Dockerfile から、よく使う命令、CMD と ENTRYPOINT の違い、.dockerignore、パスワードや API キーのような秘密の渡し方までを、公式のドキュメントと手元の出力で整理します(2026年10月時点)。

手元の出力は、Docker Engine 28.3.2(Mac の Docker Desktop 4.43.2)と、ベースイメージ alpine:3.24(中身は Alpine 3.24.2)で取りました。Docker Engine の最新は 29.9.0(2026年10月8日)です。警告やエラーの文面、docker image ls の見た目は、版によって違うことがあります。

この記事でわかること
  • Dockerfile・イメージ・コンテナの関係と、最小の Dockerfile を build して run する流れ
  • FROM・WORKDIR・COPY・RUN・ENV・EXPOSE など、よく使う命令の役割
  • CMD と ENTRYPOINT の違い、exec form と shell form の違い
  • コンテナが起動しない・すぐ止まるときの見方
  • .dockerignore の書き方と、秘密を ARG や ENV ではなく RUN --mount=type=secret で渡す理由
  1. Dockerfile・イメージ・コンテナの関係は?
  2. 最小の Dockerfile を書いて build と run をする
    1. ビルドコンテキストと docker build
    2. Dockerfile の形
  3. よく使う命令の書き方は?
    1. FROM: ベースイメージを選ぶ
    2. WORKDIR: 作業ディレクトリを決める
    3. COPY と ADD: ファイルを入れる
    4. RUN: ビルドのときにコマンドを動かす
    5. ビルドのキャッシュ
    6. ENV: 環境変数を設定する
    7. ARG: ビルドのときだけの変数
    8. EXPOSE: ポートを書いておく
    9. USER と LABEL
    10. VOLUME
  4. CMD と ENTRYPOINT の違いは?
    1. exec form と shell form の違い
    2. CMD: 既定のコマンド
    3. ENTRYPOINT: コンテナを 1 つの実行ファイルのように動かす
    4. CMD と ENTRYPOINT を組み合わせる
    5. どう使い分ける?
  5. コンテナが起動しない・すぐ止まるときは?
    1. すぐ止まる: 主なプロセスが終わった
    2. 起動しない: docker run の終了コード
  6. .dockerignore でイメージに入れないファイルを決めるには?
    1. 手元で確かめた動き
  7. 秘密の値を Dockerfile に書かずに渡すには?
    1. ARG と ENV に秘密を書いてはいけない理由
    2. RUN --mount=type=secret で渡す
    3. 渡し忘れても build が成功する(required=true)
    4. SSH mount と、動かすときの秘密
    5. compose.yaml から Dockerfile を使うには?
  8. よくある質問
    1. RUN と CMD の違いは?
    2. CMD は複数書ける?
    3. ARG と ENV の違いは?
    4. .dockerignore と .gitignore の違いは?
    5. MAINTAINER は使っていい?
  9. まとめ
  10. 参考資料

Dockerfile・イメージ・コンテナの関係は?

3 つの言葉を、公式のドキュメントの言い方で整理します。

  • Dockerfile: イメージを作るためのテキストの文書です。実行するコマンド、コピーするファイル、起動のコマンドなどを、イメージを組み立てる側(ビルダー)に指示します。
  • イメージ: Docker のコンテナを作るための指示が入った、読み取り専用のひな形です。コンテナを動かすのに要るファイル・バイナリ・ライブラリ・設定を全部入れた、決まった形の包みでもあります。
  • コンテナ: イメージを動かせる形にした実体です。Docker の API や CLI(コマンド)で、作る・起動する・止める・動かす・消すができます。

つなぐコマンドは 2 つです。Dockerfile からイメージを作るのが docker build、イメージからコンテナを動かすのが docker run です。

もの何か次に進むコマンド
Dockerfileイメージの作り方を書いた文書docker build でイメージにする
イメージ読み取り専用のひな形docker run でコンテナにする
コンテナイメージを動かした実体止める・消す

イメージには 2 つの原則があります。1 つ目は、変えられないことです。作ったあとは変更できず、新しいイメージを作るか、上に変更を重ねるだけです。2 つ目は、層(レイヤー)でできていることです。各層は、ファイルの追加・削除・変更の集まりです。Dockerfile の命令ごとにイメージの層ができ、Dockerfile を変えて作り直すと、変わった層だけが作り直されます。

コンテナのほうは、アプリの部品ごとに隔離して動かすプロセスです。複数のコンテナを動かすときは、同じカーネル(OS の中心部分)を共有します。コンテナを消すと、永続的な置き場に入れていない変更は消えます。データの残し方は、Docker の volume と bind mount の違いの記事で説明しています。

次は、Docker Engine 28.3.2(Mac・Docker Desktop)の出力です。ひとつのイメージ blogcheck-df-hello から、コンテナを 2 つ作りました(イメージは次の節で作るものです)。止まったコンテナも含めて一覧すると、同じイメージの行が 2 つ並びます。使われているイメージを消そうとするとエラーになり、先にコンテナを消すと、イメージは残っていました。

$ docker run --name blogcheck-df-c1 blogcheck-df-hello
Hello from Dockerfile
$ docker run --name blogcheck-df-c2 blogcheck-df-hello
Hello from Dockerfile
$ docker ps -a --filter name=blogcheck-df- --format "table {{.Names}}\t{{.Image}}\t{{.Command}}\t{{.Status}}"
NAMES             IMAGE                COMMAND         STATUS
blogcheck-df-c2   blogcheck-df-hello   "sh hello.sh"   Exited (0) Less than a second ago
blogcheck-df-c1   blogcheck-df-hello   "sh hello.sh"   Exited (0) 1 second ago
$ docker image rm blogcheck-df-hello
Error response from daemon: conflict: unable to remove repository reference "blogcheck-df-hello" (must force) - container 1f8a8be4d005 is using its referenced image 872ebb63e620
$ docker rm blogcheck-df-c1 blogcheck-df-c2
blogcheck-df-c1
blogcheck-df-c2
$ docker image ls blogcheck-df-hello --format "{{.Repository}} {{.Tag}} {{.ID}}"
blogcheck-df-hello latest 872ebb63e620

docker と docker compose の違い、イメージ・Dockerfile・イメージのビルドの基本は、docker?docker compose?違いと使い分けの記事でも説明しています。

最小の Dockerfile を書いて build と run をする

まず、4 行の Dockerfile を作ります。同じフォルダに、hello.sh(Hello from Dockerfile と表示するだけのシェルスクリプト)を置きます。

echo "Hello from Dockerfile"

これが hello.sh の中身です。Dockerfile は次のとおりです。

FROM alpine:3.24
WORKDIR /app
COPY hello.sh .
CMD ["sh", "hello.sh"]
  • FROM alpine:3.24: ベースイメージを決めます。ここから新しいビルドの段が始まります
  • WORKDIR /app: これ以降の命令の作業ディレクトリ(作業するフォルダ)を /app にします。無ければ作られます
  • COPY hello.sh .: ビルドコンテキストの hello.sh を、イメージの中の作業ディレクトリにコピーします
  • CMD ["sh", "hello.sh"]: コンテナを動かしたときの、既定のコマンドを決めます。ビルドのときには実行されません

docker build でイメージを作り、docker run でコンテナを動かします。-t は、イメージに名前とタグ(版の札)を付けます。-t blogcheck-df-hello のようにタグを省くと、タグは latest になりました。最後の . は、ビルドコンテキストです。

$ docker build -t blogcheck-df-hello .
…(途中の表示は省略)
#6 [2/3] WORKDIR /app
#6 DONE 0.1s

#7 [3/3] COPY hello.sh .
#7 DONE 0.1s

#8 exporting to image
…
#8 naming to docker.io/library/blogcheck-df-hello 0.0s done
#8 DONE 0.1s
$ docker run --rm blogcheck-df-hello
Hello from Dockerfile
$ docker run --rm blogcheck-df-hello pwd
/app
$ docker image ls blogcheck-df-hello
REPOSITORY           TAG       IMAGE ID       CREATED                  SIZE
blogcheck-df-hello   latest    872ebb63e620   Less than a second ago   8.66MB

--rm は、終わったらコンテナを消すオプションです。pwd を渡すと、CMD の代わりに pwd が動き、作業ディレクトリが /app になっていることが分かります(docker run のイメージ名の後ろに書いた引数が CMD を上書きすることは、あとの節で説明します)。

docker image ls の見た目は、Docker Engine 28.3.2 のものです。Engine 29.0.0(2025年11月10日)から、新しく入れた環境では containerd image store が既定になり、docker image ls は新しい表示が既定になりました。名前(タグ)の無いイメージは、--all を付けないと出なくなりました。29 系では、上の出力と見た目が違うことがあります。

ビルドコンテキストと docker build

ビルドコンテキストは、ビルドが触れられるファイルの集まりです。docker build の最後の引数で決めます。docker build . は今のディレクトリです。COPY や ADD は、コンテキストの中のファイルを参照できます。コピー元のパスは、コンテキストの根からの相対です。COPY ../something /something のようにコンテキストの外へ出るパスは、../ が取り除かれます。

docker build は docker buildx build の別名で、BuildKit(ビルドの仕組み)でビルドを始めます。BuildKit は、Docker Desktop と Docker Engine の既定のビルダーです。手元の docker build --help の Usage も、docker buildx build [OPTIONS] PATH | URL | - と出ました。

Dockerfile の形

  • ファイル名: Dockerfile です。拡張子は付けません。エディターが勝手に付けることがあるので気をつけます。-f を付けなければ、ビルドコンテキストの根にある Dockerfile が使われます。-f path/to/Dockerfile で、別の場所や名前を指定できます
  • 書き方: # Comment と INSTRUCTION arguments の形です。命令は大文字・小文字を区別しませんが、引数と見分けやすいよう大文字で書くのが慣習です。# で始まる行はコメントで、行の途中の # は引数として扱われます
  • 最初の行: 命令は上から順に実行されます。Dockerfile は FROM で始めます。前に置けるのは、パーサー指示(# syntax= のような特別なコメント)、コメント、全体で使う ARG だけです
  • 1 行目の # syntax=docker/dockerfile:1: 書くと、build の前に 1 系の最新の安定版の構文を取ってきます。書かなければ、BuildKit に同梱の版を使います。手元では、build の最初に resolve image config for docker-image://docker.io/docker/dockerfile:1 の段が出ました。Dockerfile の構文の最新は 1.28.0(2026年10月7日公開。日本時間では 10月8日)です

Dockerfile が無いフォルダで docker build . したときと、FROM が無いときの、手元の出力です。どちらも終了コードは 1 でした。

$ docker build -t blogcheck-df-nofile .
ERROR: failed to build: failed to solve: failed to read dockerfile: open Dockerfile: no such file or directory
$ docker build -t blogcheck-df-nofrom -f nofrom.Dockerfile .
ERROR: failed to build: failed to solve: no build stage in current context

小文字の from alpine:3.24 と cmd ["echo", "lowercase works"] でも、手元では動きました。

よく使う命令の書き方は?

公式の命令の一覧から、よく使うものを並べます。入門ガイドは、よく使う命令として FROM・WORKDIR・COPY・RUN・ENV・EXPOSE・USER・CMD を挙げています。

命令役割(公式の表の説明)
FROMベースイメージから新しい段を始める
WORKDIR作業ディレクトリを変える
COPYファイルやディレクトリをコピーする
ADD手元やリモートのファイルやディレクトリを足す
RUNビルドのコマンドを実行する
ENV環境変数を設定する
ARGビルドのときの変数を使う
EXPOSEアプリが聞いているポートを書いておく
USERユーザーとグループの ID を決める
LABELイメージにメタデータを足す
VOLUMEvolume のマウントを作る
CMD既定のコマンドを決める
ENTRYPOINT既定の実行ファイルを決める

入門ガイドは、Dockerfile を書く手順の型を、次の 4 つで説明しています。

  1. ベースイメージを決める
  2. 依存(アプリが使う部品)を入れる
  3. ソースやバイナリをコピーする
  4. 最後のイメージを設定する

FROM: ベースイメージを選ぶ

FROM は、新しいビルドの段を始め、ベースイメージを決めます。タグもダイジェスト(イメージの中身から決まる識別子)も省くと、latest として扱われます。タグが見つからなければ、エラーになります。

公式は、ベースイメージは、信頼できる元から作られた小さいものを選ぶように勧めています。Docker Official Images は、よく整備され、定期的に更新される集まりです。

タグは書き換えられます。同じタグが、新しい版を指すようになることがあります。たとえば FROM alpine:3.21 の 3.21 は、3.21 の最新のパッチ版を指します。この記事の alpine:3.24 は、2026年10月時点で Alpine 3.24.2 でした。毎回同じ版にしたいときは、ダイジェストで固定できます。ただし、更新の手間が増え、自動のセキュリティ修正も入らなくなります。

WORKDIR: 作業ディレクトリを決める

WORKDIR は、そのあとの RUN・CMD・ENTRYPOINT・COPY・ADD の作業ディレクトリを決めます。無ければ作られます。相対パスで書くと、前の WORKDIR からの相対になります。

指定しなければ、作業ディレクトリは / です。ただし、ベースイメージが決めていることが多いです。思わぬ場所で動かさないよう、WORKDIR ははっきり書くのがよい、と公式は書いています。また、WORKDIR は絶対パスで書き、RUN cd … && do-something を重ねる代わりに WORKDIR を使います。

手元では、WORKDIR /a、WORKDIR b、WORKDIR c と続けると、pwd は /a/b/c になりました。

COPY と ADD: ファイルを入れる

COPY は、ビルドコンテキスト・別のビルドの段・名前つきコンテキスト・イメージから、ファイルやディレクトリを、イメージの中にコピーします。

  • コピー元がディレクトリなら、ディレクトリそのものではなく、中身がコピーされます
  • コピー先の末尾の / には意味があります。COPY test.txt /abs は /abs というファイルを、COPY test.txt /abs/ は /abs/test.txt を作ります
  • / で始まらないコピー先は、WORKDIR からの相対です。無いディレクトリは作られます
  • COPY --chmod で、コピーしたファイルの権限を決められます(Dockerfile 1.2 から)

ADD と COPY は似ています。COPY は基本のコピーです。ADD は、リモートの HTTPS や Git の URL から取ってくる、手元の tar ファイルを自動で展開する、もできます。

RUN: ビルドのときにコマンドを動かす

RUN は、コマンドを実行して、今のイメージの上に新しい層を作ります。その層は、Dockerfile の次の段で使われます。長い RUN は、\ で行を分けて、&& でつなぐと読みやすくなります。ヒアドキュメント(RUN <<EOF … EOF)でも書けます。

RUN と CMD は別のものです。RUN はビルドのときにコマンドを動かして、結果を残します。CMD はビルドのときには何も実行せず、イメージの既定のコマンドを決めるだけです。

ビルドのキャッシュ

Dockerfile を変えて作り直すと、変わった層だけが作り直されます。次は、FROM alpine:3.24・WORKDIR /www・COPY www/ . の段で、3 回 build した結果です。2 回目は何も変えずに、3 回目は www/index.html を書き換えてから build しました。

(1 回目)
#6 [2/3] WORKDIR /www
#6 DONE 0.0s
#7 [3/3] COPY www/ .
#7 DONE 0.0s

(2 回目。何も変えない)
#6 [2/3] WORKDIR /www
#6 CACHED
#7 [3/3] COPY www/ .
#7 CACHED

(3 回目。www/index.html を書き換えた)
#6 [2/3] WORKDIR /www
#6 CACHED
#7 [3/3] COPY www/ .

2 回目は、WORKDIR も COPY も CACHED(前の結果を使った)と出ました。3 回目は、WORKDIR は CACHED のままで、COPY の段は作り直されました。

RUN のキャッシュは、次の build で自動では捨てられません。docker build --no-cache で捨てられます。ADD と COPY で捨てられることもあります。

ENV: 環境変数を設定する

ENV は環境変数を設定します。その段のあとの命令で使え、イメージから動かしたコンテナにも残ります。docker inspect で見え、docker run --env <key>=<value> で変えられます。

書き方は ENV <key>=<value> にします。= を書かない ENV MY_VAR my-value の形は、後方互換のために残っていますが、勧められておらず、将来なくなるかもしれない、と公式は書いています。入門ガイドには = の無い形で書かれた箇所がありますが、この記事の例は = のある形で書きます。

ENV の行ごとに層ができ、あとの層で unset しても、その層に値が残って取り出せます。秘密の値を ENV に書く話は、あとの「秘密の値を Dockerfile に書かずに渡すには?」で説明します。

ARG: ビルドのときだけの変数

ARG は、docker build --build-arg <varname>=<value> で渡せる、ビルドのときの変数です。あとの RUN には、環境変数として渡ります。ENV と違い、最後のコンテナの中では使えません。ただし、ARG に秘密を渡すのは勧められません。理由は、あとの節で出力つきで説明します。

EXPOSE: ポートを書いておく

EXPOSE は、コンテナが聞いているポートを知らせるだけです。ポートを公開するわけではありません。イメージを作る人と、コンテナを動かす人の間の、メモのような働きです。ポートを公開するのは、docker run の -p(または、公開するポートを全部公開する -P)です。

次は、EXPOSE 8080 を書いたイメージを、-p なしと -p 18080:8080 ありで動かしたときの、手元の出力です。-p なしは、PORTS に 8080/tcp としか出ません。-p を付けたほうは、Mac から curl で届きました。

$ docker ps --filter name=blogcheck-df-web --format "table {{.Names}}\t{{.Ports}}\t{{.Status}}"
NAMES                     PORTS                                           STATUS
blogcheck-df-web-port     0.0.0.0:18080->8080/tcp, [::]:18080->8080/tcp   Up 2 seconds
blogcheck-df-web-noport   8080/tcp                                        Up 3 seconds
$ curl -s --max-time 5 http://localhost:18080/
hello from container

USER と LABEL

USER は、その段の残りで使うユーザー(とグループ)を決めます。RUN と、動かすときの ENTRYPOINT・CMD が、そのユーザーで動きます。権限の要らないサービスなら、USER で root ではないユーザーに切り替えるのがよい、と公式は書いています。入門ガイドの例は RUN useradd app と USER app です。

LABEL は、イメージにメタデータ(キーと値)を足します。ラベルは docker image inspect で見られます。

VOLUME

VOLUME は、マウントポイントを作り、外から volume を付ける場所として印を付けます。ホストのディレクトリは Dockerfile では指定できず、コンテナを作る・動かすときに決めます。volume の使い方は、Docker の volume と bind mount の違いの記事にまとめています。

CMD と ENTRYPOINT の違いは?

どちらも、コンテナを動かしたときに実行するコマンドを決める命令です。違いは、docker run で引数を渡したときの動きにあります。先に、2 つの書き方(exec form と shell form)を説明します。

exec form と shell form の違い

RUN・CMD・ENTRYPOINT には、書き方が 2 つあります。

exec formshell form
書き方INSTRUCTION ["executable","param1","param2"]INSTRUCTION command param1 param2
シェル使わない自動で使う(コマンドシェル経由で動く)
変数の置き換え起きない起きる

exec form は JSON の配列として読まれます。そのため、語は '(シングルクォート)ではなく、"(ダブルクォート)で囲みます。シェル(コマンドを受け取って動かすプログラム)を通らないので、変数の置き換えなどのシェルの処理は起きません。公式の例では、RUN [ "echo", "$HOME" ] の $HOME は置き換わりません。置き換えたいときは、shell form にするか、RUN [ "sh", "-c", "echo $HOME" ] のように、シェルを直接動かします。

次は、手元(Docker Engine 28.3.2・alpine:3.24)の出力です。CMD ["echo", "$HOME"] は $HOME とそのまま表示され、CMD echo $HOME は /root と表示されました。

$ docker run --rm blogcheck-df-execvar
$HOME
$ docker run --rm blogcheck-df-shellvar
/root

JSON として正しくない exec form は、shell form として扱われます。手元で CMD ['echo', 'hello'](シングルクォート)と書くと、build は警告だけで通り、動かすとエラーになりました。docker image inspect を見ると、Cmd が ["/bin/sh","-c","['echo', 'hello']"] になっています。

$ docker build -t blogcheck-df-single -f single.Dockerfile .
…
 1 warning found (use docker --debug to expand):
 - JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals (line 2)
…
$ docker run --rm blogcheck-df-single
/bin/sh: [echo,: not found
$ docker image inspect --format 'Cmd={{json .Config.Cmd}}' blogcheck-df-single
Cmd=["/bin/sh","-c","['echo', 'hello']"]

動かしたときの終了コードは 127 でした(127 の意味は、あとの「コンテナが起動しない・すぐ止まるときは?」で説明します)。

CMD と ENTRYPOINT を shell form で書くと、Docker Engine 28.3.2 では、上のような JSONArgsRecommended の警告が出ます(警告の文は版で違うことがあるので、これは 28.3.2 の文です)。公式の説明では、shell form は、実行ファイルがシェルの子プロセスとして動き、そのシェルはシグナルを渡しません。そのため、コンテナの中のプログラムが、SIGTERM や SIGKILL のような OS のシグナルを検知して、正しく応えられなくなります。exec form なら、実行ファイルがコンテナの主なプロセス(PID 1)になります。

CMD: 既定のコマンド

CMD は、イメージからコンテナを動かすときに実行するコマンドを決めます。書き方は 3 つです。

  • CMD ["executable","param1","param2"](exec form)
  • CMD ["param1","param2"](exec form。ENTRYPOINT の既定の引数として)
  • CMD command param1 param2(shell form)

CMD は、Dockerfile に 1 つだけ書けます。2 つ以上書くと、最後の 1 つだけが効きます。手元では、CMD ["echo", "first"] と CMD ["echo", "second"] を並べると、build で警告が出て、動かすと second だけが出ました。

$ docker build -t blogcheck-df-twocmd -f twocmd.Dockerfile .
…
 1 warning found (use docker --debug to expand):
 - MultipleInstructionsDisallowed: Multiple CMD instructions should not be used in the same stage because only the last one will be used (line 2)
…
$ docker run --rm blogcheck-df-twocmd
second

CMD の目的は、動くコンテナに既定を与えることです。実行ファイルを含めても、省いてもかまいません。省くときは、ENTRYPOINT も書く必要があります。

docker run にイメージ名のあとで引数を渡すと、CMD の既定が上書きされます。ただし、bye のように実行ファイルではないものだけを渡すと、bye がコマンドとして扱われます。CMD は、丸ごと置き換わります。

$ docker run --rm blogcheck-df-cmd
hello
$ docker run --rm blogcheck-df-cmd echo bye
bye
$ docker run --rm blogcheck-df-cmd bye
docker: Error response from daemon: …exec: "bye": executable file not found in $PATH: unknown

3 つ目の … は、長いエラーの文の前半を省いたところです。最後の Run 'docker run --help' for more information の行も省いています。このときの終了コードは 127 でした。

ENTRYPOINT: コンテナを 1 つの実行ファイルのように動かす

ENTRYPOINT は、コンテナを 1 つの実行ファイルのように動かすための設定です。exec form が好まれる形です。

docker run <image> のあとの引数は、exec form の ENTRYPOINT の要素のあとに足され、CMD の要素を全部上書きします。ENTRYPOINT を上書きするには、docker run --entrypoint を使います。ENTRYPOINT も、Dockerfile の最後の 1 つだけが効きます。

$ docker run --rm blogcheck-df-ep
hello
$ docker run --rm blogcheck-df-ep bye
hello bye
$ docker run --rm --entrypoint date blogcheck-df-ep
Wed Oct 14 07:04:16 UTC 2026

blogcheck-df-ep は、ENTRYPOINT ["echo", "hello"] だけを書いたイメージです。--entrypoint を渡すと、イメージの既定のコマンド(CMD)も消えます。--entrypoint="" のように空の文字列を渡して、空にすることもできます。

shell form の ENTRYPOINT は、CMD と docker run の引数を無視します。また、/bin/sh -c の子として動き、シグナルを渡さないので、実行ファイルは PID 1 にならず、docker stop の SIGTERM を受け取りません、と公式は説明しています。手元でも、ENTRYPOINT echo hello のイメージに bye を渡しましたが、出力は hello だけでした。

$ docker run --rm blogcheck-df-epshell bye
hello
$ docker image inspect --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}' blogcheck-df-epshell
Entrypoint=["/bin/sh","-c","echo hello"] Cmd=null

shell form の ENTRYPOINT で docker stop に正しく応えさせたいときは、exec で始めます(公式の例は ENTRYPOINT exec top -b)。忘れると、docker stop は待ち時間のあと SIGKILL を送ることになります。

CMD と ENTRYPOINT を組み合わせる

公式は、CMD と ENTRYPOINT の協力の決まりを、4 つにまとめています。

  1. Dockerfile には、CMD か ENTRYPOINT の少なくとも 1 つを書く
  2. コンテナを実行ファイルのように使うなら、ENTRYPOINT を書く
  3. CMD は、ENTRYPOINT の既定の引数か、その場のコマンドを動かすために使う
  4. 別の引数を付けてコンテナを動かすと、CMD は上書きされる

ENTRYPOINT と CMD を組み合わせるときは、CMD も exec form にします。CMD を shell form にすると /bin/sh -c で包まれ、ENTRYPOINT は、本来のコマンドと引数ではなく、シェルの呼び出しを引数として受け取ってしまいます。

次の表は、公式の「組み合わせで実行されるコマンド」の表です(exec_entry や exec_cmd は、実行ファイルの名前の例です)。

ENTRYPOINT なしENTRYPOINT exec_entry p1_entry(shell form)ENTRYPOINT ["exec_entry", "p1_entry"](exec form)
CMD なしエラー(書けない)/bin/sh -c exec_entry p1_entryexec_entry p1_entry
CMD ["exec_cmd", "p1_cmd"]exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry exec_cmd p1_cmd
CMD exec_cmd p1_cmd/bin/sh -c exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

手元の 4 つのイメージの動きをまとめると、次のとおりです(Docker Engine 28.3.2・alpine:3.24)。

Dockerfile の書き方引数なしで docker runイメージ名のあとに引数を付ける
CMD ["echo", "hello"]helloecho bye を付けると bye
ENTRYPOINT ["echo", "hello"]hellobye を付けると hello bye
ENTRYPOINT ["echo"] と CMD ["default-arg"]default-argmy-arg を付けると my-arg
ENTRYPOINT echo hello(shell form)hellobye を付けても hello

3 行目の ENTRYPOINT ["echo"] と CMD ["default-arg"] のイメージに、--entrypoint ls と / を渡すと、CMD の default-arg は使われず、ls / の結果だけが出ました。

ベースイメージに CMD があっても、ENTRYPOINT を設定すると、CMD は空に戻ります。値が必要なら、今のイメージで CMD を書き直します。手元では、alpine:3.24 の Cmd は ["/bin/sh"] でしたが、ENTRYPOINT だけを書いたイメージの Cmd は null でした。

$ docker image inspect --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}' alpine:3.24
Entrypoint=null Cmd=["/bin/sh"]
$ docker image inspect --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}' blogcheck-df-ep
Entrypoint=["echo","hello"] Cmd=null

どう使い分ける?

公式の説明をまとめると、次のとおりです。

  • ENTRYPOINT を上書きするには、フラグ(--entrypoint)をはっきり渡す必要があります。既定のコマンド(CMD)は、位置の引数だけで上書きできます
  • ENTRYPOINT のいちばんよい使い方は、イメージの主なコマンドを決めて、そのコマンドのように動かし、CMD を既定のフラグにすることです(公式の例は ENTRYPOINT ["s3cmd"] と CMD ["--help"])
  • CMD は、ほぼいつも CMD ["executable", "param1", "param2"] の形で書きます。CMD ["param", "param"] を ENTRYPOINT と組み合わせる使い方は、ENTRYPOINT に慣れていない限り、まれにします
  • サービスのイメージなら、CMD ["apache2","-DFOREGROUND"] のように書きます

コンテナが起動しない・すぐ止まるときは?

コンテナが動かないときは、「起動の途中でエラーになった」のか、「動いたが、すぐ終わった」のかで、見る場所が違います。

すぐ止まる: 主なプロセスが終わった

コンテナは、それを動かす主な(ルートの)プロセスが終わると止まります。-d(裏で動かす)で動かしたコンテナも、主な(ルートの)プロセスが終わると止まります。-d に --rm も付けると、止まったときにコンテナが消えます。

-d のコンテナに、service x start のような「起動して、すぐ戻ってくる」コマンドを渡すと、そのコマンドが戻った時点でコンテナが止まります。公式はこの渡し方を勧めていません。nginx なら、docker run -d -p 80:80 my_image nginx -g 'daemon off;' のように、前で(裏に回らずに)動かし続ける形で渡します。

手元では、終わるコマンドが CMD のイメージを -d で動かすと、2 秒後には Exited (0) でした。出力は docker logs で見られました。alpine:3.24 をそのまま -d で動かしても、CMD が /bin/sh なので Exited (0) でした。

$ docker run -d --name blogcheck-df-exit2 blogcheck-df-hello
36040cec2e676301eabd52b1d5125670e277da26dbb5ae995b4dd55a806bbf69
$ sleep 2; docker ps -a --filter name=blogcheck-df-exit2 --format "table {{.Names}}\t{{.Command}}\t{{.Status}}"
NAMES                COMMAND         STATUS
blogcheck-df-exit2   "sh hello.sh"   Exited (0) 2 seconds ago
$ docker logs blogcheck-df-exit2
Hello from Dockerfile

ENTRYPOINT にスクリプトを書いたら、すぐ止まった、という場合も同じです。スクリプトが終わると、コンテナの主なプロセスが終わります。起動用のスクリプトの最後のコマンドは、exec で動かすと、そのアプリがコンテナの PID 1 になり、コンテナに送られた Unix のシグナルを受け取れます(公式の例は postgres の exec "$@")。

起動しない: docker run の終了コード

docker run の終了コードには、決まった意味があります。

終了コード意味
125Docker デーモン(コンテナを動かす本体)の側のエラー
126指定したコマンドを起動できない
127指定したコマンドが見つからない
それ以外コンテナのコマンドの終了コード

次は、手元(Docker Engine 28.3.2・Mac)で出た例です。

実行の権限が無いスクリプト: 権限 644 のスクリプトを ENTRYPOINT ["./start.sh"] にすると、起動できず、終了コードは 126 でした。COPY --chmod=755 start.sh . でコピーすると動きました。

$ docker run --rm blogcheck-df-noexec
docker: Error response from daemon: …exec: "./start.sh": permission denied: unknown
$ docker run --rm blogcheck-df-chmod
start.sh ran

無いコマンド: CMD ["no-such-command"] にすると、起動できず、終了コードは 127 でした。

$ docker run --rm blogcheck-df-notfound
docker: Error response from daemon: …exec: "no-such-command": executable file not found in $PATH: unknown

上の 2 つのエラーの文の … は、長い前半を省いたところです。最後の Run 'docker run --help' for more information の行も省いています。

シングルクォートの exec form: 前の節のとおり、CMD ['echo', 'hello'] は shell form として扱われ、/bin/sh: [echo,: not found(終了コード 127)になりました。

改行が CRLF(Windows の改行)のスクリプト: 手元では、#!/bin/sh で始まるスクリプトの改行を CRLF にして ENTRYPOINT に書くと、スクリプトがあるのに「無い」と出て、終了コードは 255 でした。この原因について、Docker の公式の説明は見つけられませんでした。ここでは、手元でこう出た、というところまでにとどめます。

$ docker run --rm blogcheck-df-crlf
exec ./crlf.sh: no such file or directory

.dockerignore でイメージに入れないファイルを決めるには?

.dockerignore は、ビルドコンテキストから外すファイルやディレクトリを書くファイルです。要らないものをビルダーに送らずに済み、ビルドが速くなります(とくに、リモートのビルダーを使うとき)。ビルドのとき、コンテキストの根にある .dockerignore が読まれ、当たったものは、ビルダーに送る前にコンテキストから外されます。

書き方は次のとおりです。

  • 1 行に 1 つのパターンを書きます。Unix のシェルのファイル名の glob(* などの当てはめ)に似たものです。前後の / は無視されます
  • 1 列目が # の行は、コメントです
  • 当てはめは、Go の filepath.Match の決まりです。それに加えて、** が何段のディレクトリにも当たります(例は **/*.go)
  • ! で始まる行は例外です(外さない)。あるファイルに当たる行のうち、最後の行で決まります

公式の best practices は、ソースの置き方を変えずに、ビルドに関係ないファイルを外すには、.dockerignore を使うように勧めています。.gitignore に似たパターンが使えます。.gitignore の書き方は、Git .gitkeep と .gitignore の違いの記事で説明しています。

手元で確かめた動き

次のファイルがあるフォルダで、COPY . . してから、イメージの中のファイルを find で一覧しました(.env の中身は偽の値です)。

./.env
./.git/HEAD
./Dockerfile
./NOTES.md
./README.md
./docs/guide.md
./node_modules/pkg/index.js
./src/app.js

.dockerignore が無いと、全部がイメージに入りました。次の .dockerignore を置くと、結果は次のとおりです。

$ cat .dockerignore
.env
.git
node_modules
*.md
!README.md
$ docker run --rm blogcheck-df-ctx | sort
./.dockerignore
./Dockerfile
./README.md
./docs/guide.md
./src/app.js

.env・.git・node_modules は入っていません。*.md は外れましたが、!README.md で README.md は残りました。ただし、docs/guide.md は残っています。

*.md は、ビルドコンテキストの直下にある md だけに当たります。サブディレクトリの md は、外れません。サブディレクトリの md も外したいときは、**/*.md と書きます。.gitignore では、/ を含まない *.md のようなパターンは、その .gitignore より下のどの階層にも当たります。この点が .dockerignore と違います。

$ cat .dockerignore
.env
.git
node_modules
**/*.md
!README.md
$ docker run --rm blogcheck-df-ctx2 | sort
./.dockerignore
./Dockerfile
./README.md
./src/app.js

**/*.md にすると、docs/guide.md と NOTES.md が外れ、README.md だけが残りました。

.dockerignore で Dockerfile と .dockerignore を外しても、ビルドに要るので、ビルダーには送られます。ただし、ADD・COPY・bind mount でイメージに入れることはできなくなります。手元でも、外さないと COPY . . で .dockerignore と Dockerfile もイメージに入りました。

.env のような秘密を書いたファイルを、イメージに入れたくないときは、.dockerignore に書いて外します。.env の考え方は、.env と環境変数とは?の記事で説明しています。

Dockerfile ごとに別の ignore ファイルも置けます。Dockerfile と同じ場所に、<Dockerfile の名前>.dockerignore を置くと、両方あるときはそちらが優先されます。

秘密の値を Dockerfile に書かずに渡すには?

ビルドの秘密は、パスワードや API トークンのように、アプリのビルドの途中で使う機密の情報です。

先に結論を書きます。ARG と ENV には、秘密を書かないでください。 秘密は、RUN --mount=type=secret で渡します。この節の秘密の値は、全部偽の値(fake-…)です。

ARG と ENV に秘密を書いてはいけない理由

公式の Build secrets のページは、ビルド引数(ARG)と環境変数(ENV)は、秘密をビルドに渡すのに向かない、と書いています。最後のイメージに残るからです。代わりに、secret mount か SSH mount を使います。

Dockerfile のリファレンスの ARG の警告も、ユーザーの認証情報や API トークンのような秘密を、ビルド引数で渡すのは勧めないとしています。ビルド引数は、docker history で見えてしまうからです。安全な方法は、RUN --mount=type=secret です。

公式の 2 つのページで、ARG の説明が食い違って見えるところがあります。Dockerfile のリファレンスの ARG の説明は、「ENV と違い、ARG の変数はイメージに埋め込まれず、最後のコンテナでは使えない」と書いています。一方、同じページの警告と Build secrets のページは、ビルド引数は docker history で見える、最後のイメージに残る、と書いています。手元で確かめると、次のとおりでした(Docker Engine 28.3.2・alpine:3.24)。

まず、ARG API_TOKEN を使う Dockerfile に、--build-arg API_TOKEN=fake-arg-token-abcde で偽の値を渡して build します。キー名が機密らしいと、警告が出ました。

$ cat arg.Dockerfile
FROM alpine:3.24
ARG API_TOKEN
RUN echo "using token of length ${#API_TOKEN}"
$ docker build --progress=plain --no-cache -t blogcheck-df-arg --build-arg API_TOKEN=fake-arg-token-abcde -f arg.Dockerfile .
…
 1 warning found (use docker --debug to expand):
 - SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "API_TOKEN") (line 2)

この警告の文は、Docker Engine 28.3.2 の文面です。公式の build checks のページの文面とは、言い方が違います。

イメージの履歴を見ると、値がそのまま出ました。docker image save で書き出したイメージの中身にも、値の文字列がありました。一方、そのイメージから動かしたコンテナの中では、$API_TOKEN は空でした。

$ docker history --no-trunc --format '{{.CreatedBy}}' blogcheck-df-arg | head -3
RUN |1 API_TOKEN=fake-arg-token-abcde /bin/sh -c echo "using token of length ${#API_TOKEN}" # buildkit
ARG API_TOKEN=fake-arg-token-abcde
CMD ["/bin/sh"]
$ docker run --rm blogcheck-df-arg sh -c 'echo "API_TOKEN=[$API_TOKEN]"'
API_TOKEN=[]
$ docker image save blogcheck-df-arg | grep -a -c fake-arg-token
1

つまり、ARG の値は、コンテナの環境変数には残りませんが、イメージの履歴には残ります。「ARG はイメージに残らないので、秘密を渡してよい」とは考えないでください。

ENV のほうは、もっとはっきり残ります。ENV API_TOKEN=fake-env-token-12345 と書くと、docker image inspect の Env にも、docker history にも、コンテナの中の printenv にも出ました。

$ docker image inspect --format '{{json .Config.Env}}' blogcheck-df-env
["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","API_TOKEN=fake-env-token-12345"]
$ docker run --rm blogcheck-df-env printenv API_TOKEN
fake-env-token-12345
$ docker history --no-trunc --format '{{.CreatedBy}}' blogcheck-df-env | head -2
RUN /bin/sh -c echo "built" # buildkit
ENV API_TOKEN=fake-env-token-12345

あとの RUN で unset しても、消えません。ENV の行ごとに層ができ、その層に値が残って取り出せます。

RUN --mount=type=secret で渡す

RUN --mount=type=secret は、トークンや秘密鍵のような秘密の値を、イメージに焼き込まずに、ビルドのコンテナから使えるようにします。秘密は、ビルドを動かす側(ビルドクライアント)から受け取り、その 1 つの命令の間だけ、ビルドのコンテナの中で使えます。

使い方は 2 段です。

  1. docker build --secret で、秘密を渡す
  2. Dockerfile の RUN --mount=type=secret で、使う

Dockerfile の中では、秘密は既定で /run/secrets/<id> のファイルになります。target で別のパスに、env で環境変数にできます。秘密の元は、ファイルか環境変数です。

次は、ファイルから渡す例です。token.txt に、偽の値 fake-file-token-67890 を書いておきます。

$ cat secret.Dockerfile
FROM alpine:3.24
RUN --mount=type=secret,id=api_token \
    echo "secret file has $(wc -c < /run/secrets/api_token) bytes"
RUN ls -la /run/secrets 2>&1 || true
$ docker build --progress=plain --no-cache -t blogcheck-df-secret --secret id=api_token,src=token.txt -f secret.Dockerfile .
…
#5 [stage-0 2/3] RUN --mount=type=secret,id=api_token     echo "secret file has $(wc -c < /run/secrets/api_token) bytes"
#5 0.539 secret file has 21 bytes
…
#6 [stage-0 3/3] RUN ls -la /run/secrets 2>&1 || true
#6 0.504 ls: /run/secrets: No such file or directory
…

1 つ目の RUN の中では、秘密のファイルの大きさ(21 バイト)が読めました。次の RUN では、/run/secrets 自体がありません。秘密は、その 1 つの命令の間だけ使えるからです。

イメージの履歴にも、イメージの中身にも、値は出ませんでした。動かしたコンテナにも、/run/secrets はありませんでした。

$ docker history --no-trunc --format '{{.CreatedBy}}' blogcheck-df-secret | head -3
RUN /bin/sh -c ls -la /run/secrets 2>&1 || true # buildkit
RUN /bin/sh -c echo "secret file has $(wc -c < /run/secrets/api_token) bytes" # buildkit
CMD ["/bin/sh"]
$ docker run --rm blogcheck-df-secret ls -la /run/secrets
ls: /run/secrets: No such file or directory
$ docker image save blogcheck-df-secret | grep -a -c fake-file-token
0

最後の 0 は、fake-file-token という文字列が、書き出したイメージの中に 0 件だった、という意味です。

環境変数から渡すこともできます。--secret id=API_TOKEN のように src と env を省くと、同じ名前の環境変数があればその値を、無ければ同じ名前のファイルを使います。Dockerfile 側は env=API_TOKEN を付けて、環境変数として受け取ります(env は Dockerfile v1.10.0 から使えます)。手元では、環境変数 API_TOKEN に偽の値 fake-env-secret-xyz を入れて build し、19 文字を受け取れました。このときも、履歴とイメージの中身に値は出ませんでした。

$ cat secretenv.Dockerfile
FROM alpine:3.24
RUN --mount=type=secret,id=API_TOKEN,env=API_TOKEN \
    echo "env secret has ${#API_TOKEN} chars"
$ API_TOKEN=fake-env-secret-xyz docker build --progress=plain --no-cache -t blogcheck-df-secretenv --secret id=API_TOKEN -f secretenv.Dockerfile .
…
#5 1.206 env secret has 19 chars
…

渡し忘れても build が成功する(required=true)

RUN --mount=type=secret で使えるオプションは、次のとおりです。

オプション意味
id秘密の ID。既定は、target のファイル名
targetマウントするパス。既定は /run/secrets/ + id(env も無いとき)
envファイルの代わりに(または両方で)環境変数にする
requiredtrue なら、秘密が無いとエラーになる。既定は false
mode秘密のファイルの権限(8 進数)。既定は 0400
uid・gid秘密のファイルのユーザー ID・グループ ID。既定は 0

注意したいのは required です。既定は false なので、--secret を渡し忘れても、build は止まりません。手元では、required を付けずに --secret を渡し忘れると、/bin/sh: can't open /run/secrets/api_token: no such file と出たのに、build は成功しました(終了コード 0)。required=true を付けると、build は secret api_token: not found で止まりました(終了コード 1)。

(required を付けない。--secret を渡し忘れた)
#5 0.480 /bin/sh: can't open /run/secrets/api_token: no such file
#5 0.483 secret file has  bytes
→ docker build の終了コード: 0

(required=true を付けた。--secret を渡し忘れた)
ERROR: failed to build: failed to solve: secret api_token: not found
→ docker build の終了コード: 1

渡し忘れに気づきたい秘密には、required=true を付けます。

SSH mount と、動かすときの秘密

SSH のエージェントのソケットや鍵をビルドの中で使いたいときは、secret mount の代わりに SSH mount が使えます。よくある使い方は、非公開の Git リポジトリの取得です。

ここまでは、ビルドのときの秘密の話です。動かすときの秘密は、別の話になります。Compose のドキュメントは、パスワードや API キーを環境変数で渡すと、意図せず漏れるおそれがあり、エラーを調べるときのログに出ることもある、と書いています。Compose の secrets は、/run/secrets/<secret_name> にファイルとしてマウントされます(Linux のコンテナだけ)。詳しくは、公式のCompose で secrets を使うのページを見てください。.env の扱いは、.env と環境変数とは?の記事で説明しています。

compose.yaml から Dockerfile を使うには?

docker compose では、compose.yaml のサービスに build を書くと、ソースからイメージを(作り直して)作れます。文字列で書けば、ビルドコンテキストのパスになり、その根の Dockerfile が使われます。詳しい形で書けば、ビルド引数や、別の Dockerfile の場所も書けます。

  • 相対パスは、compose.yaml のあるディレクトリから解かれます。context を書かなければ、既定はプロジェクトのディレクトリ(.)です
  • dockerfile で、別の Dockerfile を指定できます。相対パスは、ビルドコンテキストから解かれます
  • サービスの command は、イメージの既定のコマンド(Dockerfile の CMD など)を上書きします
  • サービスの entrypoint は、Dockerfile の ENTRYPOINT を上書きします。entrypoint が null 以外なら、Compose はイメージの既定のコマンド(CMD)を無視します
  • command は、Dockerfile の CMD と違い、イメージの SHELL の中では自動で動きません。変数の展開のようなシェルの機能が要るときは、シェルをはっきり動かします(公式の例は command: /bin/sh -c 'echo "hello $$HOSTNAME"')

ビルドの秘密も、compose で渡せます。build: の下の secrets と、トップレベルの secrets で定義します。公式の例は、環境変数 NPM_TOKEN の値を、npm_token という秘密としてビルドに渡す形です。

次は、手元(Docker Compose v2.38.2・Docker Engine 28.3.2)で流した例です。Dockerfile と compose.yaml を同じフォルダに置きました。トークンは偽の値 fake-npm-token です。

# Dockerfile
FROM alpine:3.24
RUN --mount=type=secret,id=npm_token \
    echo "build secret: $(wc -c < /run/secrets/npm_token) bytes"
ENTRYPOINT ["echo"]
CMD ["from CMD"]
# compose.yaml
services:
  app:
    build:
      context: .
      secrets:
        - npm_token
  app2:
    build: .
    command: ["from compose command"]
secrets:
  npm_token:
    environment: NPM_TOKEN

docker compose build のときに、app は NPM_TOKEN の値を、ビルドの秘密として受け取れました(14 バイト)。動かすと(up の出力は、サービスの出力の行だけを載せています)、app2 は command の from compose command が、ENTRYPOINT ["echo"] のあとに付いて表示されました。app は CMD の from CMD です。docker compose run --rm app "from run args" で引数を渡すと、CMD の代わりに使われました。

$ NPM_TOKEN=fake-npm-token docker compose -p blogcheck-df build --progress=plain app
…
#6 0.968 build secret: 14 bytes
…
 app  Built
$ NPM_TOKEN=fake-npm-token docker compose -p blogcheck-df up
…
app2-1  | from compose command
app-1   | from CMD
…
$ NPM_TOKEN=fake-npm-token docker compose -p blogcheck-df run --rm app "from run args"
from run args
$ NPM_TOKEN=fake-npm-token docker compose -p blogcheck-df down
…
 Container blogcheck-df-app-1  Removed
 Container blogcheck-df-app2-1  Removed
 Network blogcheck-df_default  Removing
 Network blogcheck-df_default  Removed

docker compose config で展開された内容を見ると、context は、compose.yaml のあるフォルダの絶対パスになり、dockerfile: Dockerfile と出ました。docker と docker compose の違いは、docker?docker compose?違いと使い分けの記事を見てください(その記事には、ハイフンを付けた docker-compose の書き方も出てきます。この記事は、ハイフンなしの docker compose で書いています)。

よくある質問

RUN と CMD の違いは?

RUN は、ビルドのときにコマンドを動かして、結果をイメージの層に残します。CMD は、ビルドのときには何も実行せず、イメージの既定のコマンドを決めるだけです。公式も、RUN と CMD を混同しないよう書いています。CMD が動くのは、コンテナを動かしたときです。

CMD は複数書ける?

書いても、最後の 1 つだけが効きます。ENTRYPOINT も同じで、最後の 1 つだけが効きます。手元では、CMD を 2 つ書くと、build で MultipleInstructionsDisallowed の警告が出ました(Docker Engine 28.3.2)。

ARG と ENV の違いは?

どちらも変数を決める命令ですが、使える場面が違います。

  • ENV: 環境変数を設定します。そのあとの命令で使え、イメージから動かしたコンテナにも残ります
  • ARG: docker build --build-arg で渡す、ビルドのときの変数です。あとの RUN には環境変数として渡りますが、最後のコンテナでは使えません

どちらも、秘密を書く場所ではありません。ARG の値も、手元では docker history に残りました。秘密は RUN --mount=type=secret で渡します。

.dockerignore と .gitignore の違いは?

どちらも、似たパターンで「外すもの」を書きます。.dockerignore は、Docker のビルドコンテキストから外すもので、ビルダーに送る前に外されます。違いの一例が、*.md です。.dockerignore の *.md は、ビルドコンテキストの直下の md だけに当たり、サブディレクトリの md は外れません。.gitignore では、/ を含まない *.md は、下の階層にも当たります。

MAINTAINER は使っていい?

新しく書くときは使いません。MAINTAINER は非推奨(deprecated)で、公式は、LABEL のほうがずっと柔軟なので、LABEL を使うように書いています。同じ意味のラベルは、LABEL org.opencontainers.image.authors="…" です。

手元の Docker Engine 28.3.2 では、MAINTAINER を書いても build は通りますが、警告が出ました。値は docker image inspect の .Author に入りました。LABEL org.opencontainers.image.authors=… なら警告は出ず、.Config.Labels に入りました。

$ docker build -t blogcheck-df-maint -f maint.Dockerfile .
…
 1 warning found (use docker --debug to expand):
 - MaintainerDeprecated: Maintainer instruction is deprecated in favor of using label (line 2)
…
$ docker image inspect --format 'Author={{json .Author}} Labels={{json .Config.Labels}}' blogcheck-df-label
Author="" Labels={"org.opencontainers.image.authors":"author@example.com"}

まとめ

  • Dockerfile は、イメージを作るためのテキストの文書です。docker build でイメージを作り、docker run でコンテナを動かします
  • 最初の行は FROM です。WORKDIR・COPY・RUN・ENV などを上から順に書きます。ENV は ENV KEY=value の形で書きます
  • EXPOSE は、ポートを知らせるだけです。公開するのは docker run -p です
  • CMD は既定のコマンドで、docker run の引数で上書きされます。ENTRYPOINT は、コンテナを実行ファイルのように使うための設定です。どちらも exec form(["…"])で書きます
  • コンテナは、主なプロセスが終わると止まります。起動しないときは、docker run の終了コード(125・126・127)を見ます
  • .dockerignore の *.md は、直下の md だけに当たります。.env や .git、node_modules は外しておきます
  • ARG と ENV に秘密を書かないでください。ビルドの秘密は RUN --mount=type=secret で渡し、渡し忘れに気づきたいときは required=true を付けます
  • MAINTAINER は非推奨です。LABEL を使います

参考資料

※この記事は、公式の資料をもとに AI(Claude)で下書きし、2026年10月14日時点の公式の資料と照らして確かめてから公開しています。誤りに気づいたらお問い合わせからお知らせください。

PR
PR
タイトルとURLをコピーしました