Docker のベストプラクティス: RUN、CMD、および ENTRYPOINT の選択

コンテナ化ツールとしてのDockerの柔軟性と堅牢性には、気が遠くなるような複雑さが伴います。 同様のタスクを実行するために複数の方法が利用可能であり、ユーザーは利用可能なオプションの長所と短所を理解して、プロジェクトに最適なアプローチを選択する必要があります。

紛らわしい領域の 1 つは、 RUNCMD、および ENTRYPOINT Dockerfile の命令に関するものです。 この記事では、これらの手順の違いについて説明し、それぞれのユースケースについて説明します。

2400x1260 run cmd と entrypoint のどちらを選択するか

走る

RUN 命令は、Docker イメージをビルドおよび構成するコマンドを実行するために Dockerfile で使用されます。これらのコマンドはイメージのビルドプロセス中に実行され、 RUN 命令ごとに Docker イメージに新しいレイヤーが作成されます。 たとえば、特定のソフトウェアまたはライブラリのインストールを必要とするイメージを作成する場合は、 RUN を使用して必要なインストール コマンドを実行します。

次の例は、イメージのビルド中に Docker のビルドプロセスに apt cache を更新し、Apache をインストールするように指示する方法を示しています。

RUN apt update && apt -y install apache2

RUN 命令は、画像レイヤーを最小限に抑えるために慎重に使用し、画像サイズを縮小するために、可能な場合は関連するコマンドを 1 つの RUN 命令に結合する必要があります。

ティッカー

CMD 命令は、コンテナーが Docker イメージから起動されたときに実行する既定のコマンドを指定します。コンテナの起動時に(つまり、 docker run コマンドで)コマンドが指定されていない場合、このデフォルトが使用されます。 CMD は、 docker run にコマンドライン引数を指定することでオーバーライドできます。

CMD は、デフォルトのコマンドや簡単にオーバーライドできるパラメータを設定する場合に便利です。 これは、デフォルトの実行パラメータを定義する方法としてイメージでよく使用され、コンテナの実行時にコマンドラインから上書きできます。 

たとえば、デフォルトでは、Web サーバーを起動したい場合がありますが、ユーザーはこれをオーバーライドしてシェルを実行できます。

CMD ["apache2ctl", "-DFOREGROUND"]

ユーザーは、Apache を起動する代わりに、 docker run -it <image> /bin/bash でコンテナを起動して Bash シェルを取得できます。  

エントリポイント

ENTRYPOINT 命令は、コンテナのデフォルトの実行可能ファイルを設定します。docker run コマンドに指定された引数は、 ENTRYPOINT コマンドに追加されます。

手記: ENTRYPOINT は、コンテナで常に同じ基本コマンドを実行する必要があり、ユーザーが最後に追加のコマンドを追加できるようにする場合に使用します。1 つの注意点は、--entrypoint フラグを指定することで、docker run コマンド ラインで ENTRYPOINT をオーバーライドできることです。

ENTRYPOINT コンテナをスタンドアロンの実行可能ファイルに変換する場合に特に便利です。 たとえば、引数を必要とするカスタムスクリプト( “my_script extra_args”など)をパッケージ化しているとします。 その場合は、ENTRYPOINT を使用して常にスクリプト プロセス (“my_script”) を実行し、イメージ ユーザーが docker run コマンド ラインで“extra_args”を指定できるようにすることができます。次の操作を実行できます。

ENTRYPOINT ["my_script"]

CMD と ENTRYPOINT の組み合わせ

CMD 命令は、exec 形式で指定されている場合、ENTRYPOINTにデフォルトの引数を提供するために使用できます。この設定により、エントリポイントをメインの実行可能ファイルにし、ユーザーがオーバーライドできる追加の引数 CMD 指定できます。

たとえば、Python アプリケーションを実行するコンテナーがあり、常に同じアプリケーション ファイルを使用し、ユーザーが異なるコマンド ライン引数を指定できるようにするとします。

ENTRYPOINT ["python", "/app/my_script.py"]
CMD ["--default-arg"]

docker run myimage --user-argを実行すると、python /app/my_script.py --user-argが実行されます。

次の表に、これらのコマンドと使用例の概要を示します。

コマンドの説明と使用例

命令形容ユースケース
ティッカーDocker イメージの既定の実行可能ファイルを定義します。 これは、 docker run 引数によってオーバーライドできます。ユーティリティイメージを使用すると、ユーザーはコマンドラインでさまざまな実行可能ファイルと引数を渡すことができます。
エントリポイント既定の実行可能ファイルを定義します。 これは、 “--entrypoint”  docker run 引数でオーバーライドできます。デフォルトの実行可能ファイルをオーバーライドすることが望ましくない特定の目的のために構築されたイメージ。
走るレイヤーを構築するコマンドを実行します。イメージの構築

PID 1 とは何か、なぜ重要なのか?

Unix および Docker コンテナを含む Unix ライクなシステムのコンテキストでは、PID 1 はシステムの起動中に開始される最初のプロセスを指します。 他のすべてのプロセスは、プロセスツリーモデルではシステム内のすべてのプロセスの親であるPID 1によって開始されます。 

Docker コンテナーでは、PID 1 として実行されるプロセスは、コンテナー内の他のすべてのプロセスを管理する役割を担うため、非常に重要です。 さらに、PID 1 は、Docker ホストからのシグナルを確認して処理するプロセスです。 たとえば、コンテナへの SIGTERM はPID 1によってキャッチおよび処理され、コンテナは正常にシャットダウンする必要があります。

シェル形式を使用して Docker でコマンドを実行すると、通常、シェルプロセス (/bin/sh -c) は PID 1になります。 それでも、これらの信号を適切に処理しないため、コンテナのクリーンなシャットダウンにつながる可能性があります。 対照的に、exec形式を使用する場合、コマンドはシェルを介さずにPID 1 として直接実行されるため、シグナルを直接受信して処理できます。 

この動作により、コンテナは正常に停止、再起動、または割り込みを処理できるため、堅牢で応答性の高いシグナル処理を必要とするアプリケーションには exec 形式が適しています。

シェルと実行の形式

前の例では、 RUNCMD、および ENTRYPOINT 命令に引数を渡すために 2つの方法を使用しました。 これらは、シェル形式と実行形式と呼ばれます。 

手記: 視覚的な違いは、exec形式が、要素ごとに1つの引数/コマンドを持つコマンドと引数のコンマ区切りの配列として渡されることです。 逆に、シェル形式は、コマンドと引数を組み合わせた文字列として表現されます。 

各フォームは、コンテナ内でコマンドを実行することに影響を与え、シグナル処理から環境変数の拡張まで、あらゆるものに影響を与えます。 次の表に、さまざまなフォームのクイック リファレンス ガイドを示します。

シェルと exec フォームのリファレンス

フォーム形容
シェルフォーム<INSTRUCTION> <COMMAND>の形をとります。CMD echo TEST 又は ENTRYPOINT echo TEST
実行フォーム<INSTRUCTION> ["EXECUTABLE", "PARAMETER"]の形をとります。CMD ["echo", "TEST"] 又は ENTRYPOINT ["echo", "TEST"]

シェル形式では、コマンドはサブシェル (通常は Linux システムで /bin/sh -c ) で実行されます。 この形式は、シェル処理(変数展開、ワイルドカードなど)を可能にし、特定のタイプのコマンドに対してより柔軟になるため便利です(シェル処理の例については、この シェルスクリプトの記事 を参照してください)。 ただし、コマンドを実行しているプロセスがコンテナのPID 1ではないことも意味し、Dockerによって送信されたシグナル(グレースフルシャットダウンの SIGTERM など)は意図したプロセスではなくシェルによって受信されるため、シグナル処理に問題が発生する可能性があります。

exec 形式では、コマンド・シェルは呼び出されません。 つまり、指定したコマンドは、コンテナに送信されたシグナルを正しく処理するために重要な、コンテナの PID 1として直接実行されます。 さらに、この形式はシェル展開を実行しないため、特に外部ソースから引数やコマンドを指定する場合に、より安全で予測可能です。

すべてをまとめる

Docker の RUNCMDENTRYPOINT 命令の実際のアプリケーションとニュアンス、およびシェル形式と実行形式の選択を説明するために、いくつかの例を確認しましょう。 これらの例は、実際の Dockerfile シナリオで各命令を効果的に利用する方法を示し、シェル形式と実行形式の違いを強調しています。 

これらの例を通じて、各ディレクティブをいつ、どのように使用してコンテナの動作をニーズに合わせて正確に調整し、Docker コンテナの適切な構成、セキュリティ、パフォーマンスを確保するかをよりよく理解できます。 この実践的なアプローチは、これまで説明してきた理論的知識を、Docker プロジェクトに直接適用できる実用的な洞察に統合するのに役立ちます。

RUN命令

RUN、Docker ビルドプロセス中にパッケージのインストールやファイルの変更に使用される場合、シェル形式と実行形式のどちらを選択するかは、シェル処理の必要性によって異なります。シェル形式は、パイプラインやファイルグロビングなど、シェル機能を必要とするコマンドに必要です。 ただし、exec形式は、複雑さと潜在的なエラーを軽減するため、シェル機能のない単純なコマンドに適しています。

# Shell form, useful for complex scripting
RUN apt-get update && apt-get install -y nginx

# Exec form, for direct command execution
RUN ["apt-get", "update"]
RUN ["apt-get", "install", "-y", "nginx"]

CMD と ENTRYPOINT

これらの命令は、コンテナーの実行時の動作を制御します。 exec 形式を ENTRYPOINT とともに使用すると、コンテナのメインアプリケーションがシグナルを直接処理することが保証されます。CMD exec形式で定義された ENTRYPOINT にデフォルトパラメータを提供し、柔軟性と堅牢な信号処理を提供します。

# ENTRYPOINT with exec form for direct process control
ENTRYPOINT ["httpd"]

# CMD provides default parameters, can be overridden at runtime
CMD ["-D", "FOREGROUND"]

信号処理と柔軟性

ENTRYPOINT を exec 形式とCMDで使用してパラメーターを指定すると、Docker コンテナーはオペレーティング システムのシグナルを適切に処理し、ユーザー入力に動的に応答し、安全で予測可能な操作を維持できます。 

この設定は、信頼性の高いシャットダウンと構成動作を必要とする重要なアプリケーションを実行するコンテナに特に役立ちます。 次の表に、フォーム間の主な相違点を示します。

シェルとexecの主な違い

シェルフォーム実行フォーム
フォーム[]角かっこのないコマンド。コンテナのシェル( /bin/sh -cなど)によって実行されます。[]括弧で囲まれたコマンド。シェルを経由せずに直接実行します。
変数の置換シェルから環境変数 ( $HOME$PATH など) を継承します。シェル環境変数を継承しませんが、 ENV 命令変数に対して同じように動作します。
シェル機能サブコマンド、配管出力、チェーンコマンド、I/Oリダイレクトなどをサポートします。シェル フィーチャーはサポートしていません。
信号のトラッピングとフォワーディングほとんどのシェルは、プロセスシグナルを子プロセスに転送しません。SIGINTのように信号を直接トラップして転送します。
ENTRYPOINTでの使用信号転送で問題が発生する可能性があります。信号処理が優れているため、推奨されます。
ENTRYPOINTパラメータとしてのCMDシェル形式では不可能です。配列の最初の項目がコマンドでない場合は、すべての項目が ENTRYPOINTのパラメーターとして使用されます。

図 1 は、Dockerfile のビルドで RUNCMDENTRYPOINT を使用するためのデシジョン ツリーを示しています。

2400x1260 run cmd エントリポイント
図 1:デシジョンツリー — RUN、CMD、ENTRYPOINT。

図 2 は、exec 形式とシェル形式のどちらを使用するかを判断するのに役立つデシジョン・ツリーを示しています。

2400x1260 決定木 exec と shell の比較
図 2:決定木—execとシェルの形式。

次のセクションでは、 CMDENTRYPOINTの違いの概要について説明します。 これらの例では、 RUN コマンドは含まれていませんが、これは、2 つの異なる形式を確認することで簡単に処理できるからです。

Dockerfile をテストする

# Use syntax version 1.3-labs for Dockerfile
# syntax=docker/dockerfile:1.3-labs

# Use the Ubuntu 20.04 image as the base image
FROM ubuntu:20.04

# Run the following commands inside the container:
# 1. Update the package lists for upgrades and new package installations
# 2. Install the apache2-utils package (which includes the 'ab' tool)
# 3. Remove the package lists to reduce the image size
#
# This is all run in a HEREDOC; see
# https://www.docker.com/blog/introduction-to-heredocs-in-dockerfiles/
# for more details.
#
RUN <<EOF
apt-get update;
apt-get install -y apache2-utils;
rm -rf /var/lib/apt/lists/*;
EOF

# Set the default command
CMD ab

最初のビルド

このイメージをビルドし、 ab としてタグ付けします。

$ docker build -t ab .

[+] Building 7.0s (6/6) FINISHED                                                               docker:desktop-linux
 => [internal] load .dockerignore                                                                              0.0s
 => => transferring context: 2B                                                                                0.0s
 => [internal] load build definition from Dockerfile                                                           0.0s
 => => transferring dockerfile: 730B                                                                           0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                0.4s
 => CACHED [1/2] FROM docker.io/library/ubuntu:20.04@sha256:33a5cc25d22c45900796a1aca487ad7a7cb09f09ea00b779e  0.0s
 => [2/2] RUN <<EOF (apt-get update;...)                                                                       6.5s
 => exporting to image                                                                                         0.0s
 => => exporting layers                                                                                        0.0s
 => => writing image sha256:99ca34fac6a38b79aefd859540f88e309ca759aad0d7ad066c4931356881e518                   0.0s
 => => naming to docker.io/library/ab 

次で実行 CMD ab

引数がないと、期待どおりに使用ブロックが得られます。

$ docker run ab
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds
<-- SNIP -->

ただし、 ab を実行してテストするURLを含めると、最初にエラーが発生します。

$ docker run --rm ab https://jayschmidt.us
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "https://jayschmidt.us": stat https://jayschmidt.us: no such file or directory: unknown.

ここでの問題は、コマンドラインで指定された文字列( https://jayschmidt.us )が CMD 命令をオーバーライドしており、それが有効なコマンドではないため、エラーがスローされることです。 したがって、実行するコマンドを指定する必要があります。

$ docker run --rm  ab ab https://jayschmidt.us/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking jayschmidt.us (be patient).....done


Server Software:        nginx
Server Hostname:        jayschmidt.us
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-ECDSA-AES256-GCM-SHA384,256,256
Server Temp Key:        X25519 253 bits
TLS Server Name:        jayschmidt.us

Document Path:          /
Document Length:        12992 bytes

Concurrency Level:      1
Time taken for tests:   0.132 seconds
Complete requests:      1
Failed requests:        0
Total transferred:      13236 bytes
HTML transferred:       12992 bytes
Requests per second:    7.56 [#/sec] (mean)
Time per request:       132.270 [ms] (mean)
Time per request:       132.270 [ms] (mean, across all concurrent requests)
Transfer rate:          97.72 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       90   90   0.0     90      90
Processing:    43   43   0.0     43      43
Waiting:       43   43   0.0     43      43
Total:        132  132   0.0    132     132

ENTRYPOINTで実行

この実行では、Dockerfile から CMD ab 命令を削除し、 ENTRYPOINT ["ab"] に置き換えてから、イメージを再構築します。

これは CMD コマンドと似ていますが、ENTRYPOINT を使用する場合、docker run コマンドで –entrypoint フラグを使用しない限り、コマンドをオーバーライドすることはできません。代わりに、 docker run に渡された引数は、 ENTRYPOINTへの引数として扱われます。

$ docker run --rm  ab "https://jayschmidt.us/"
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking jayschmidt.us (be patient).....done


Server Software:        nginx
Server Hostname:        jayschmidt.us
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-ECDSA-AES256-GCM-SHA384,256,256
Server Temp Key:        X25519 253 bits
TLS Server Name:        jayschmidt.us

Document Path:          /
Document Length:        12992 bytes

Concurrency Level:      1
Time taken for tests:   0.122 seconds
Complete requests:      1
Failed requests:        0
Total transferred:      13236 bytes
HTML transferred:       12992 bytes
Requests per second:    8.22 [#/sec] (mean)
Time per request:       121.709 [ms] (mean)
Time per request:       121.709 [ms] (mean, across all concurrent requests)
Transfer rate:          106.20 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       91   91   0.0     91      91
Processing:    31   31   0.0     31      31
Waiting:       31   31   0.0     31      31
Total:        122  122   0.0    122     122

構文はどうですか?

上記の例では、 ENTRYPOINT ["ab"] 構文を使用して、実行するコマンドを角括弧と引用符で囲みます。 ただし、 ENTRYPOINT ab を指定することは可能です(引用符や括弧なし)。 

それを試すとどうなるか見てみましょう。

$ docker run --rm  ab "https://jayschmidt.us/"
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds
<-- SNIP -->

最初に考えるのは、上記のCMD abで行ったように、実行可能ファイルと引数の両方を与えるdocker runコマンドを再実行することです。

$ docker run --rm ab ab "https://jayschmidt.us/"
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds
<-- SNIP -->

これはENTRYPOINTdocker runコマンドに–entrypoint引数を明示的に追加した場合にのみ上書きできるためです。重要なのは、実行時にコンテナ内の特定の実行可能ファイルの使用を強制する場合は、常に ENTRYPOINT を使用することです。

まとめ:重要なポイントとベストプラクティス

RUNCMDENTRYPOINTの使用を含む意思決定プロセスと、シェル形式と実行形式の選択は、Dockerの複雑な性質を示しています。各コマンドは、Docker エコシステムで明確な目的を果たし、コンテナの構築方法、操作方法、環境とのやり取りに影響を与えます。 

開発者は、特定のシナリオごとに適切なコマンドとフォームを選択することで、信頼性が高く、安全で、効率が最適化された Docker イメージを構築できます。 Docker のコマンドとその形式をこのレベルで理解し、適用することは、Docker の機能を完全に活用するために重要です。 これらのベストプラクティスを実装することで、Dockerコンテナにデプロイされたアプリケーションがさまざまな設定で最大のパフォーマンスを達成し、開発ワークフローと本番環境のデプロイが強化されます。

さらに詳しく