この記事は、「Dockerfileってどうやって書くの?」という方に向けて、Dockerfileの書き方をわかりやすく解説する入門記事です。
ポイントを一つずつ確認しながら、Dockerfileの作り方を一緒に見ていきましょう!
単語の意味
本記事で使用する単語のざっくりとした意味を紹介します。
記事の内容をスムーズに理解するための参考にしてください。
- イメージ:コンテナを作成するための「設計図」のようなものです。
- コンテナ:イメージをもとに作られる、独立した「アプリケーションの実行環境」のことです。
Dockerfileってなに?
Dockerfileは、アプリケーションを動かすための実行環境を、”コード”で定義した設計図のようなファイルです。
OS・ソフトウェア・設定などを記述しておくことで、誰でも同じ手順でDockerイメージ(実行環境の完成品)を作成できます。
そのイメージからDockerコンテナを起動すれば、どんな環境でも同じ条件でアプリケーションを動かすことができます。

Dockerについてや、Dockerによる環境構築手順が知りたい方は、本記事の最後に載せている参考記事をご活用ください。
今回説明するサンプルコード
以下「【初心者向け】DockerでRails APIとPostgreSQLの環境構築をする(M1 Mac)」で使用したDockerfileを例に解説していきます。
【Dockerfile】
FROM ruby:3.4.3-alpine
ENV RUNTIME_PACKAGES="bash tzdata postgresql-dev git make" \
DEV_PACKAGES="build-base yaml-dev" \
HOME=/${WORKDIR} \
LANG=C.UTF-8 \
TZ=UTC
RUN apk update && \
apk upgrade && \
apk add --no-cache ${RUNTIME_PACKAGES} && \
apk add --virtual build-dependencies --no-cache ${DEV_PACKAGES} && \
apk add nodejs yarn
RUN mkdir /rails-api-docker
ENV APP_ROOT /rails-api-docker
WORKDIR $APP_ROOT
COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT
RUN bundle install
COPY . $APP_ROOT
RUN apk del build-dependencies
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
CMD ["bundle", "exec", "rails", "server", "-p", "3000", "-b", "0.0.0.0"]
EXPOSE 3000
【entrypoint.sh】
#!/bin/bash
set -e
# データベースのマイグレーションを実行
echo "Running database migrations..."
bundle exec rails db:migrate
# コンテナのメインプロセス(DockerfileでCMDと設定されているもの)を実行する
exec "$@"
※entrypoint.shは末尾に「.sh」がつくシェルスクリプトと呼ばれるファイルです。シェルスクリプトは複数のコマンドを自動で順番に実行したり、条件分岐やループなどのプログラム的な処理も書けるため、作業の自動化や繰り返し処理に使われます。今回は、デーベースのタマイグレーションの目的で使用します。
Dockerfileとentrypoint.shの処理の流れ
Dockerfile内の処理がどう実行されるか分かりやすくするため、処理の流れを以下に記載します。
- Dockerfileが上から順番に実行される
- ENTRYPOINTが実行されると、ENTRYPOINTにセットされた「entrypoint.sh」ファイルが実行される
- 「entrypoint.sh」内の処理が上から順番に実行される
- 「entrypoint.sh」内の「
exec "$@"」が実行されると、「entrypont.sh」ファイルから抜けて、Dockerfileの「CMD」が実行される
※「exec "$@"」を実行しない場合、「entrypont.sh」内の「bundle exec rails db:migrate」までが実行された状態で、コンテナが終了します。
Dockerfileの書き方
それではさっそく、記事冒頭でご紹介した、Dockerfileのサンプルコードで定義されている各種命令について解説していきます。
FROM(ベースイメージの指定)
FROMは、Dockerコンテナの元になるイメージを指定します。
Dockerfileは必ずFROMから始めます。
# alpineという種類のRuby3.4.3-alpineイメージを使用する
FROM ruby:3.4.3-alpine
コード例では、Ruby3.4.3-alpineのイメージを使用しています。(Alpineとは、主に開発環境で使用される軽量なDockerイメージのことです)
例えば、Docker公式イメージを使用したい場合は、以下のようになります。
# Docker公式のRuby3.2.0イメージを使用する
FROM ruby:3.2.0
RUN(コマンドの実行)
RUNは、コンテナ内でコマンドを実行します。
# アプリケーション開発に必要なパッケージをコンテナ内にインストールする
RUN apk update && \
apk upgrade && \
apk add --no-cache ${RUNTIME_PACKAGES} && \
apk add --virtual build-dependencies --no-cache ${DEV_PACKAGES} && \
apk add nodejs yarn
# コンテナ内にディレクトリを作成
RUN mkdir /rails-api-docker
・・・省略・・・
# Gemfileに記述されたGem(ライブラリ)をコンテナ内にインストールする
RUN bundle install
・・・省略・・・
# ビルド用に導入した仮想パッケージを削除する(ビルド後のイメージに含めたくないパッケージを削除)
RUN apk del build-dependencies
・・・省略・・・
# 「/usr/bin/entrypoint.sh」ファイルに実行権を付与する
RUN chmod +x /usr/bin/entrypoint.sh
上記のコードでは、パッケージのインストールやディレクトリの作成などを行っています。
RUN命令の書き方には次の2種類があります。
- シェル形式:
RUN <コマンド> - 実行形式:
RUN ["実行ファイル", "パラメータ"]
それぞれの形式には特徴があり、使い分けが重要です。
- シェル形式は、
&&やパイプ (|) などのシェル構文を使いたいときに便利です。
例:RUN apt-get update && apt-get install -y curl
→ 複数コマンドをまとめて実行でき、ビルド効率が良くなります。 - 実行形式は、特定のシェルを明示的に指定したいときに使います。
例:RUN ["bash", "-c", "echo $HOME"]
→ 引数(bash)が正確に渡り、環境(コンテナ内のシェル)に依存せず安定して動作します。
- 基本は「シェル形式」を使う(読みやすくて効率的)
- どうしても特定のシェルが必要な場合だけ「実行形式」を使う
この2つを意識しておけば、ほとんどのケースで迷うことはありません。
ENV(環境変数の設定)
ENVは、コンテナ内で使用できる環境変数を設定します。
# 作成したディレクトリのパスを環境変数 APP_ROOT に設定する
ENV APP_ROOT /rails-api-docker
設定した環境変数は、 $環境変数名 または ${環境変数名} の形式で使用できます。
# 設定した環境変数 APP_ROOT を使用する(WORKDIRはDockerfile内で書かれたコマンドを実行するディレクトリを指定)
WORKDIR $APP_ROOT
or
WORKDIR ${APP_ROOT}
WORKDIR(作業ディレクトリの指定)
WORKDIRは、WORKDIR以降のDockerfile内で書かれたコマンドを実行するディレクトリを指定します。
# 作成したディレクトリを作業ディレクトリとして設定する
WORKDIR $APP_ROOT
コード例では、$APP_ROOT (先ほど設定した「/rails-api-docker」)を作業ディレクトリとして設定しています。
これにより、以降の COPY や RUN は /rails-api-docker 内で実行されます。
例えば、以下のようにDockerfileが書かれている場合、Gemfileは/rails-api-docker 内にコピーされます。
・・・省略・・・
RUN mkdir /rails-api-docker
ENV APP_ROOT /rails-api-docker
WORKDIR $APP_ROOT
COPY Gemfile $APP_ROOT
・・・省略・・・
また他にも、以下のようにDockerfileが書かれている場合を想定してみます。
・・・省略・・・
RUN mkdir /rails-api-docker
ENV APP_ROOT /rails-api-docker
WORKDIR $APP_ROOT
COPY Gemfile ./
・・・省略・・・
この「./」は「WORKDIR $APP_ROOT」から見た時の「./」となるため、「Gemfile」は「/rails-api-docker」にコピーされることになります。
COPY(ファイルのコピー)
COPY は、ホスト(ここではローカル環境)のファイルやディレクトリをコンテナ内のコピー先にコピーします。
<コピー先> は絶対パスか WORKDIR (作業ディレクトリ)からの相対パスです。
# ENV APP_ROOT /rails-api-docker
# WORKDIR $APP_ROOT →「/rails-api-docker」が設定されている。
# ローカルプロジェクトのGemfileをコンテナの $APP_ROOT にコピーする
COPY Gemfile $APP_ROOT/
# ローカルプロジェクトのGemfile.lockをコンテナの $APP_ROOT にコピーする
COPY Gemfile.lock $APP_ROOT/
・・・省略(bundle installなどで依存関係をinstall)・・・
# ローカルプロジェクトのファイル・フォルダをすべてコンテナ内にコピーする
COPY . $APP_ROOT
・・・省略・・・
# ローカルプロジェクトのentrypoint.shをコンテナ内の「/usr/bin/」にコピーする
COPY entrypoint.sh /usr/bin/
コード例では、GemfileやGemfile.lock、entrypoint.shなどのファイルをコンテナ内にコピーしています。
また、COPY . $APP_ROOTを使用し、ローカルプロジェクトの全てのファイルを、WORKDIRで指定された作業ディレクトリ内にコピーしています。
※単純にファイルやフォルダをコンテナ内にコピーしたい場合はCOPY を使用します。
※ADDとCOPYの違いについて詳しく知りたい方は、「Docker のベストプラクティス: Dockerfile の ADD 命令と COPY 命令の違いを理解する」を参考にしてください。
コンテナ起動後にコンテナにアクセスし、ファイル・フォルダ構造を確認すると(ターミナルでlsコマンドを実行すると)、ローカルプロジェクトと同じ構造になっていることを確認することができます。
以下は、Dockerfileで、COPY . $APP_ROOT、COPY . .、COPY . ./それぞれを実行したときの、コンテナ内のフォルダ構造をターミナルで表示したものです。
【Dockerfileで「COPY . $APP_ROOT」を実行した時の、コンテナ内のフォルダ構造】
<コンテナID>:/rails-api-docker# ls
Dockerfile app docker-compose.yml public tmp
Gemfile bin docs script vendor
Gemfile.lock config entrypoint.sh storage
README.md config.ru lib terraform
Rakefile db log test
【Dockerfileで「COPY . .」を実行した時の、コンテナ内のフォルダ構造】
<コンテナID>:/rails-api-docker# ls
Dockerfile app docker-compose.yml public tmp
Gemfile bin docs script vendor
Gemfile.lock config entrypoint.sh storage
README.md config.ru lib terraform
Rakefile db log test
【Dockerfileで「COPY . ./」を実行した時の、コンテナ内のフォルダ構造】
<コンテナID>:/rails-api-docker# ls
Dockerfile app docker-compose.yml public tmp
Gemfile bin docs script vendor
Gemfile.lock config entrypoint.sh storage
README.md config.ru lib terraform
Rakefile db log test
全て同じファイル/フォルダ構造になっていることが確認できました。
コンテナ内にアクセスするコマンド(bashを使用している場合)
docker exec -it <コンテナID> bashdocker exec -it <コンテナID> /bin/bash
(起動中のコンテナの)コンテナIDを確認するコマンド
docker psdocker ps -a
ENTRYPOINT(コンテナ起動時に必ず実行したいコマンド・ファイルを設定)
ENTRYPOINTは、コンテナ起動時に必ず行っておきたい処理(pidの削除、DBの準備等の初期化処理など)を記述したファイルやコマンドを設定/実行することができます。
# コンテナ起動時に entrypoint.sh 実行する
ENTRYPOINT ["entrypoint.sh"]
コード例では、entrypoint.shを実行しています(今回はentrypoint.shファイル内で「#!/bin/bash」を指定しているため、bashで実行されています)。
※Dockerfile には少なくとも1つの CMD または ENTRYPOINT 命令を含む必要があります。
CMD(コンテナ起動時のメイン処理を設定)
CMDは、コンテナを起動した時のメイン処理が書かれたファイルやコマンドを設定することができます。
# コンテナ起動時のメイン処理を設定
CMD ["bundle", "exec", "rails", "server", "-p", "3000", "-b", "0.0.0.0"]
コード例では、Railsサーバーを起動させるコマンドを指定しています。
CMDで指定したコマンドは上書きもできます。
詳しくは「Dockerリファレンス」を参考にしてください。
EXPOSE(公開ポートを明示的に知らせる※ただし実際には公開されない)
EXPOSEは、コンテナが外部と通信する際に使用するポート番号を、明示的に書いておくための命令です。
# コンテナが3000番ポートで通信を受け付けることを宣言
EXPOSE 3000
例えば、「このコンテナは3000番ポートで動くんだな」と一目で分かるようになるため、EXPOSE を書いておくと、開発者同士の連携やコンテナ間の設定がしやすくなります。
ただし、EXPOSE を書いただけではポートは実際には公開されません。
実際に外部(ホスト側)へアクセスできるようにするには、docker run -p コマンドや docker-compose.yml の ports 設定でポートを公開する必要があります。
以上で各種コマンドの説明は終わりです!
おわりに
この記事では、Dockerfileの基本構造と各命令の役割を解説しました。
FROMでベースイメージを指定し、RUNでコマンドを実行、COPYでファイルをコピーし、ENVで環境変数を設定。
そして、ENTRYPOINT や CMD でコンテナ起動時の動作を決めます。
この流れを理解すれば、Dockerfileの基本はしっかり押さえられます。
より詳しい仕様や最新構文を知りたい方は、以下をチェックしてみてください👇
- 🇯🇵 日本語版(Docker 1.12時点) https://docs.docker.jp/v1.12/engine/reference/builder.html
- 🌍 英語版(最新版・公式) https://docs.docker.com/reference/dockerfile
本記事が、Dockerfileを理解する第一歩になれば幸いです!

