DockerfileでBashを実行する方法|RUN・CMD・ENTRYPOINTの使い分け

環境&ワークフロー

DockerfileでBashを実行する方法は、「いつ実行したいか」で使う命令が変わります。イメージのビルド中ならRUN、コンテナ起動時ならCMDまたはENTRYPOINTです。このページでは3つの命令の使い分けから、RUNが既定でbashではなくshを使うという落とし穴、そして実際のビルド出力で確認したエラーの直し方までを解説します。

結論:3つの命令の使い分け

命令 実行タイミング 用途
RUN イメージのビルド中 パッケージのインストール、ビルドスクリプトの実行
CMD コンテナの起動時 既定のコマンド。docker run の引数で上書きされる
ENTRYPOINT コンテナの起動時 必ず実行するコマンド。引数で上書きされない
SHELL 以降の命令に適用 シェル形式の既定シェルをshからbashへ変更
FROM ubuntu:24.04
SHELL ["/bin/bash", "-c"]          # 以降の RUN を bash で実行する
RUN echo "ビルド中に実行される"
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["bash", "/entrypoint.sh"]   # 起動時に実行される

なお、すでに動いているコンテナに入ってBashを操作したい場合はDockerfileではなくdocker run -itdocker exec -itを使います。用途が違うので、次の節で切り分けてください。

ビルド時のbashと、起動中コンテナのbashは別物

「DockerでBashを使う」には大きく2つの場面があり、混同するとうまくいきません。

やりたいこと 使うもの 実行される場所
イメージを作る過程でコマンドを実行したい DockerfileのRUN docker build の最中
コンテナが起動したら自動でスクリプトを走らせたい DockerfileのCMD / ENTRYPOINT docker run の直後
新しいコンテナを起動して対話的に操作したい docker run -it イメージ bash 手元のターミナル
すでに動いているコンテナの中に入りたい docker exec -it コンテナ bash 手元のターミナル

後半2つはDockerfileには書きません。詳しくはdocker run bash でコンテナ内に入る方法:-it オプション・exec との違いDocker Execコマンドでコンテナ内のBashを利用する方法と注意点を参照してください。

DockerfileでBashを実行する基本

Dockerfileは、テキスト形式のファイルであり、Dockerイメージの構築手順を定義します。Bashを実行するには、Dockerfile内で適切な命令を追加する必要があります。最も基本的な方法は、RUN命令を使ってBashのスクリプトやコマンドを実行することです。

RUNの既定シェルはbashではない

見落とされがちですが、RUNのシェル形式はbashではなく/bin/sh -cで実行されます。Ubuntuイメージの/bin/shの実体はBashではなくdashです。実際にビルドして確認します。

FROM ubuntu:24.04
RUN echo "既定シェル: $0" ; readlink -f /bin/sh
RUN echo "配列は $BASH_VERSION"
#5 [2/3] RUN echo "既定シェル: $0" ; readlink -f /bin/sh
#5 0.169 既定シェル: /bin/sh
#5 0.170 /usr/bin/dash
#6 [3/3] RUN echo "配列は $BASH_VERSION"
#6 0.200 配列は

$BASH_VERSIONが空であることから、Bashではないことが分かります。そのため[[ ]]・配列・set -o pipefailといったBash固有の記法は、そのままでは使えません。Bashで実行したい場合は次の3つのいずれかを選びます。

# 方法1:都度 bash -c で呼ぶ
RUN bash -c 'if [[ -f /etc/os-release ]]; then echo ok; fi'

# 方法2:exec形式で bash を明示する
RUN ["bash", "-c", "echo $BASH_VERSION"]

# 方法3:SHELL 命令で既定シェルごと切り替える(以降すべてに効く・推奨)
SHELL ["/bin/bash", "-c"]
RUN if [[ -f /etc/os-release ]]; then echo ok; fi
FROM ubuntu:latest

# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y bash

# Bashコマンドを実行
RUN bash -c "echo 'Hello, Docker!'"

上記の例では、Ubuntuの最新バージョンをベースにしてBashがインストールされ、bash -cで単純なEchoコマンドを実行しています。

ENTRYPOINTとCMDの活用

Bashスクリプトをエントリーポイントとして設定すると、コンテナがデフォルトでこのスクリプトを実行するようにすることができます。ENTRYPOINT命令は、コンテナ内で実行されるデフォルトのプロセスを定義します。

FROM ubuntu:latest

# Bashをインストール
RUN apt-get update && apt-get install -y bash

# スクリプトをコピーして実行
COPY my-script.sh /usr/local/bin/my-script.sh
RUN chmod +x /usr/local/bin/my-script.sh

ENTRYPOINT ["bash", "/usr/local/bin/my-script.sh"]

ここでは、コンテナが起動するとmy-script.shが実行される設定になっています。

セットアップをBashスクリプトに切り出す

インストール手順が長くなってきたら、RUNを並べ続けるよりも1つのスクリプトにまとめてビルド時に呼び出すほうが、見通しがよくローカルでの検証もしやすくなります。

FROM ubuntu:24.04
SHELL ["/bin/bash", "-c"]

RUN apt-get update && \
    apt-get install -y --no-install-recommends curl ca-certificates git && \
    rm -rf /var/lib/apt/lists/*

COPY --chmod=755 setup.sh /usr/local/bin/setup.sh
RUN /usr/local/bin/setup.sh

呼び出されるスクリプト側は、先頭にset -euo pipefailを必ず書いてください。これが無いと途中のコマンドが失敗しても最後まで走りきり、壊れたイメージがビルド成功として出来上がります。

#!/usr/bin/env bash
set -euo pipefail

echo "アプリケーションディレクトリを作成"
mkdir -p /app/myapp

echo "アーカイブを取得"
curl -fsSL https://example.com/somefile.tar.gz -o /tmp/somefile.tar.gz

echo "展開"
tar -xzf /tmp/somefile.tar.gz -C /app/myapp
rm -f /tmp/somefile.tar.gz

echo "Setup complete!"

curlには-f(HTTPエラーで失敗させる)を付けるのが重要です。付けないと404のHTMLをダウンロードして「成功」と判定され、後続のtarで意味の分かりにくいエラーになります。set -eの挙動についてはBashのset -eオプションでシェルスクリプトのエラー検出を強化する方法で解説しています。

なお、スクリプト内でexport PATH=... と書いてもそのRUNの中でしか有効になりません。ビルド後のコンテナにも引き継ぎたい環境変数は、Dockerfile側でENVを使ってください。

ENV PATH="/custom/bin:${PATH}"

ベストプラクティス

Dockerfileを書く際には、いくつかのベストプラクティスを守ることが重要です。これにより、効率的で保守しやすいイメージを作成することが可能になります。

レイヤー削減

Dockerイメージは各命令に対してレイヤーを生成します。大量のレイヤーがあると、ビルド時間が長くなり、イメージが無駄に大きくなる可能性があります。RUN命令はできるだけまとめ、一度に実行すると良いでしょう。

RUN apt-get update && \
    apt-get install -y bash && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

キャッシュの効率化

Dockerのキャッシュ機能を効率的に利用するために、頻繁に変更される命令をできるだけ後に配置します。これにより、変更があっても早い段階のキャッシュが無駄にならず効率的です。

セキュリティの確保

DockerコンテナでのBashスクリプト実行時には、入力の検証や正当性確認を行い、スクリプトが意図しない動作をするリスクを最小限に抑えましょう。また、可能であればシェルスクリプトの代わりに専用のエントリーポイントスクリプトを使うことを検討してください。

シンプルな設計

必要以上に複雑なスクリプトや動作を避け、できるだけシンプルな設計を心がけましょう。これにより、管理やトラブルシューティングが容易になります。

RUNとCMD/ENTRYPOINTのシェル形式・exec形式の違い

DockerfileでBashを使う命令にはシェル形式exec形式(JSON配列)の2種類の書き方があり、挙動が異なります。この違いを理解していないと、シグナルが正しく伝わらない・環境変数が展開されない、といった問題が起こります。

# シェル形式:/bin/sh -c 経由で実行される(変数展開・パイプ・&&が使える)
RUN echo "Hello, $HOME"
CMD echo "container started"

# exec形式:シェルを介さず直接実行される(配列で記述、変数展開はされない)
RUN ["bash", "-c", "echo Hello, $HOME"]
CMD ["bash", "/usr/local/bin/my-script.sh"]

シェル形式はシグナル(SIGTERMなど)が/bin/shプロセスに渡ってからアプリケーションに転送されるため、コンテナ停止時にシグナルが正しく伝わらないことがあります。CMD/ENTRYPOINTで長時間稼働するプロセスを起動する場合は、シグナルを直接受け取れるexec形式を使うのが推奨です。

SHELL命令でデフォルトシェルを変更する

Dockerのシェル形式命令(RUNなど)は既定で/bin/sh -cを使いますが、SHELL命令でBashに変更すると、Bash固有の機能(set -o pipefailやプロセス置換など)がシェル形式のまま使えるようになります。

FROM ubuntu:latest
RUN apt-get update && apt-get install -y bash

# 以降のシェル形式命令はbashで実行される
SHELL ["/bin/bash", "-c"]

# pipefailを有効にすることで、パイプ内のコマンドが失敗したらビルドも失敗させられる
RUN set -o pipefail && curl -fsSL https://example.com/install.sh | bash

SHELLを指定しないままset -o pipefailを書くと、dashがそのオプションを知らないためビルド自体が失敗します。実際の出力が次です。

# Dockerfile: RUN set -o pipefail && echo ok
#5 0.196 /bin/sh: 1: set: Illegal option -o pipefail
#5 ERROR: process "/bin/sh -c set -o pipefail && echo ok" did not complete successfully: exit code: 2

SHELL ["/bin/bash", "-c"]を先に書いておけば通ります。

# Dockerfile: SHELL ["/bin/bash", "-c"] を追加してから同じ RUN
#5 0.161 pipefail OK  shell=/bin/bash  bash=5.2.21(1)-release

pipefailが無効なシェル(sh)のままだと、パイプの途中でコマンドが失敗してもビルドが成功扱いになってしまうことがあるため、パイプを使うビルド手順ではこの設定が特に重要です。

マルチステージビルドでのBash活用

マルチステージビルドでは、ビルド用ステージでBashスクリプトを使って依存関係の準備やテストを行い、最終イメージには成果物だけをコピーすることで、イメージサイズを小さく保てます。

FROM ubuntu:latest AS builder
RUN apt-get update && apt-get install -y bash build-essential
COPY build.sh .
RUN bash build.sh

FROM ubuntu:latest AS runtime
COPY --from=builder /app/dist /app/dist
CMD ["/app/dist/start.sh"]

ビルドステージ(builder)にだけBashやビルドツールを入れ、最終ステージには実行に必要な成果物だけをコピーすることで、本番イメージを軽量かつセキュアに保てます。

Dockerfileでbashを実行するときによくあるエラーと対処法

「/bin/sh: bash: not found」というエラーが出る

AlpineベースのイメージなどBashが標準で入っていない場合に発生します。実際のビルド出力は次のようになります(終了コードは127=コマンドが見つからない)。

#5 0.191 /bin/sh: bash: not found
#5 ERROR: process "/bin/sh -c bash -c \"echo hello\"" did not complete successfully: exit code: 127

Alpineではapt-getではなくapkでBashを追加します。

FROM alpine:3.20
RUN apk add --no-cache bash
RUN bash -c "echo 'Hello from bash'"

なお、Bashを入れずに済むならAlpine標準のsh(BusyBox ash)のまま書くほうがイメージは軽くなります。Bash固有の記法を使っていないか見直してから判断してください。

スクリプトを実行すると「Permission denied」になる

COPYしたスクリプトに実行権限が付与されていないことが原因です。終了コードは126(実行できない)になります。

#7 0.210 /bin/sh: 1: /usr/local/bin/my-script.sh: Permission denied
#7 ERROR: process "/bin/sh -c /usr/local/bin/my-script.sh" did not complete successfully: exit code: 126

対処は2通りです。COPY後にchmod +xするか、COPY --chmodで最初から権限を付けます。

# 方法1:chmod +x する
COPY my-script.sh /usr/local/bin/my-script.sh
RUN chmod +x /usr/local/bin/my-script.sh
ENTRYPOINT ["bash", "/usr/local/bin/my-script.sh"]

# 方法2:COPY 時に権限を指定する(レイヤーが1つ減る)
COPY --chmod=755 my-script.sh /usr/local/bin/my-script.sh
ENTRYPOINT ["/usr/local/bin/my-script.sh"]

bash スクリプト名 の形で呼び出す場合は実行権限が無くても動きますが、ENTRYPOINT ["/usr/local/bin/my-script.sh"] のように直接指定するなら実行権限が必須です。権限まわりの切り分け全般はPermission deniedの原因と直し方|6つの切り分け手順にまとめています。

環境変数がexec形式のCMD/ENTRYPOINTで展開されない

exec形式(JSON配列)はシェルを経由しないため、$VARのような変数展開は行われません。変数展開が必要な場合は、シェル形式を使うか、exec形式内で明示的にbash -cを呼び出してください。

# NG:$NAMEがそのまま文字列として出力される
CMD ["echo", "Hello, $NAME"]

# OK:bash -c 経由で変数展開させる
CMD ["bash", "-c", "echo Hello, $NAME"]

よくある質問(FAQ)

DockerfileでRUNとCMDはどう違いますか?

RUNはイメージのビルド中に実行され、結果がイメージのレイヤーとして保存されます。CMDはビルド時には実行されず、コンテナを起動したときに走ります。「インストールはRUN、起動するアプリはCMD」と覚えてください。

DockerfileでBashスクリプトを実行するには?

スクリプトをCOPYしてから、ビルド中に走らせたいならRUN bash script.sh、コンテナ起動時に走らせたいならENTRYPOINT ["bash", "/script.sh"]と書きます。実行権限を付けずに済ませたい場合はbashを明示的に前置きするのが確実です。

Dockerfileでシェルをbashに変更するには?

SHELL ["/bin/bash", "-c"]を書きます。これ以降のシェル形式のRUNCMDENTRYPOINTがBashで実行されるようになります。ベースイメージにBashが入っていない場合は、先にインストールしておく必要があります。

CMDとENTRYPOINTは両方書けますか?

書けます。両方をexec形式で指定した場合、CMDの内容がENTRYPOINTへの引数として渡されます。「必ず実行するコマンド=ENTRYPOINT、差し替え可能な既定の引数=CMD」という組み合わせが定番です。

ENTRYPOINT ["bash", "/entrypoint.sh"]
CMD ["--mode", "production"]
# docker run イメージ --mode staging で引数だけ差し替えられる

ビルドは通るのにコンテナがすぐ終了します

CMDで指定したコマンドが終了すると、コンテナも終了します。CMD ["bash"]のように対話シェルを指定した場合、docker run-itを付けないと入力が無いまま即終了します。起動して中に入りたいときはdocker run -it イメージ bashとしてください。

関連記事

まとめ

DockerfileでBashを実行することは、コンテナ化された環境で柔軟なスクリプトの実行を可能にします。適切な命令を使用し、ベストプラクティスを守ることで、安全かつ効率的なDockerイメージを構築することができます。これらの方法を覚えておくことで、様々なユースケースに対応できるようになりますので、ぜひ実践してみてください。

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント