Dockerfileの書き方 - FROM / RUN / COPY / CMD の使い方をやさしく解説

DevOps

はじめに

Dockerは、アプリケーションをコンテナとしてまとめて実行できる技術として多くの現場で利用されています。
その魅力の一つは、環境構築をシンプルにしつつ、チーム開発でも環境差異を最小限に抑えられる点にあります。
そして、Dockerの世界で欠かせないのがDockerfileという設定ファイルです。
このファイルを使ってイメージをビルドすることで、自分が必要とする依存関係やアプリケーション本体をまとめてコンテナ化できます。

しかし、初めてDockerfileに触れる方にとっては、どう書けばよいのか最初のハードルが高く感じられるかもしれません。
とくにFROMRUNCOPYCMDといった命令は、Dockerfileの基本でありながら「どんな順序で配置すればいいの?」「何のために使うの?」と疑問を持つことがあるように思います。

そこで本記事では、Dockerfileがどのような仕組みで動いているのかをイメージしながら、初心者でも理解しやすいように具体例や実務での活用を交えて解説します。
特にFROMRUNCOPYCMDを中心に、初心者がつまずきやすいポイントや意識しておきたいコツを丁寧に紹介します。

この記事を読むとわかること

  • Dockerfileの役割と基本的な概念
  • FROMRUNCOPYCMD命令の使い方と注意点
  • 実務でのDockerfile活用シーンとメリット
  • キャッシュやレイヤー構造を意識したベストプラクティス
  • マルチステージビルドや環境変数の管理など少し進んだ書き方
  • トラブルシューティングの考え方

ここから順を追って解説していきます。

Dockerfileとは何か

Dockerfileは、コンテナイメージをビルドするための手順や設定が書かれたテキストファイルです。
ここで作成したレシピをもとに、Dockerはステップを一つずつ実行してイメージを積み重ねるように作り上げます。
他の人が同じDockerfileを使えば、同じイメージを構築できるという明確な再現性が得られるのが特長です。

Dockerfileの役割

Dockerfileは単なる設定ファイルではなく、アプリケーションに必要な環境とライブラリをまとめるための設計図としての役割を果たします。
たとえば、サーバーアプリケーションを動かすために必要なライブラリや設定を事前にまとめておくと、開発者や運用担当者はOSの違いを気にせず、どこでも同じ動作を再現できます。
これは従来の環境構築にかかる時間と労力を大幅に削減する大きなメリットとなるでしょう。

Dockerのイメージとコンテナ

Dockerfileを使って生成される成果物をイメージと呼びます。
イメージはファイルシステムや各種設定をレイヤー構造で持ち、Dockerの環境下でコンテナとして起動できる状態を保持します。
コンテナは軽量であるため、同じホスト上で複数立ち上げても干渉が少なく、動作テストやスケーリングがしやすい点が特徴です。
Dockerfileは、このイメージを効率良く正確に作るための手順書と考えると分かりやすいかもしれません。

FROM命令

Dockerfileで最初に登場する可能性が高いのがFROM命令です。
これは「どのベースイメージを基にするか」を指定します。
Dockerイメージはレイヤーを重ねて作られるため、はじめに土台となるベースイメージを指定して、その上に追加の設定やアプリケーションを積み重ねていきます。

ベースイメージの選び方

ベースイメージを選ぶ際は、用途に合ったものを選ぶことが重要です。
たとえば、Webサーバーを動かすならNginxやApacheを含む公式イメージを使うケースがあります。
Node.jsアプリケーションであれば公式のNode.jsイメージを選択する方法が多いです。
ベースイメージを大きくしすぎると最終的なイメージ容量も肥大化してしまうため、使用するライブラリとのバランスを意識しながら選ぶのがポイントです。

FROMを使う具体例

FROM命令を一つ書くと、そこで指定したイメージの上にレイヤーが構築され始めます。
基本的な書き方は以下のようになります。

FROM node:16

# ここからRUNやCOPYなど、追加の命令を書いていく

上記のように書くと、Node.jsがあらかじめインストールされたイメージを基にコンテナを構築できます。
Webアプリケーションなら、この後にソースコードをコピーして依存関係をインストールし、最終的に実行するまでのステップを追記します。

一つのDockerfileに複数のFROMを用いてマルチステージビルドを行うことも可能です。
ただし基本的には、1つ目のFROMでスタートしたイメージを継承しながら後続の命令を積み重ねていく流れが一般的です。

RUN命令

RUN命令は、Dockerイメージをビルドするときに実行されるコマンドです。
たとえばapt-getなどのパッケージマネージャを使って必要なソフトウェアをインストールするときにも利用します。
RUN命令を使うたびに新しいレイヤーが作られ、そのレイヤーにインストール結果が反映されます。

RUNの基本的な書き方

基本的な書き方は以下の通りです。

FROM ubuntu
RUN apt-get update && apt-get install -y curl

この例では、Ubuntuベースのイメージに対してcurlをインストールしています。
&&を使ってまとめて書くのは、不要なイメージレイヤーを増やさないための一般的なテクニックです。

RUN命令のベストプラクティス

RUN命令を何度も使ってしまうと、それだけイメージレイヤーが増えてサイズが大きくなりがちです。
また、apt-get updateapt-get installを別々のRUN命令で書くと、それぞれが別のレイヤーを形成してしまいます。
そのため、必要に応じて&&で繋いだり、まとめて書くことでレイヤーの肥大化を抑えることが多いです。

ただし、何でもかんでも1行にまとめすぎると可読性が下がる場合もあります。
あまりに長くなりそうなときは、\で改行して見やすくするなど工夫してみると良いでしょう。

COPY命令

アプリケーションをコンテナ内で動かすには、ソースコードや静的ファイルなどをイメージに取り込む必要があります。
その際に使うのがCOPY命令です。

ADD命令との違い

DockerfileにはCOPYに似たADDという命令も存在します。
しかし、初心者の段階では基本的にCOPYの利用を推奨することが多いです。
理由は、ADDはターボール形式のアーカイブを自動解凍してしまうなどの動作を行うため、意図せぬ挙動が発生する可能性があるためです。
シンプルにファイルやディレクトリをコピーしたいだけならCOPYで十分と考えられます。

COPY命令の具体例

COPY命令は、ホスト側の指定ディレクトリをイメージ内にコピーします。

FROM node:16
WORKDIR /app
COPY . /app
RUN npm install

この例では、ローカルのカレントディレクトリ(=ソースコードや設定ファイル)を/appフォルダへコピーしています。
その後、依存パッケージをインストールして環境を整えています。
COPY命令によってソースコードがイメージに取り込まれるため、コンテナ内でアプリケーションが実行できるようになります。

CMD命令

CMD命令は、コンテナを起動したときに実行されるプロセスを指定する役割を持ちます。
Dockerコンテナが起動する際に「デフォルトの実行コマンド」となるイメージです。

CMDとENTRYPOINTの違い

CMDと似た命令としてENTRYPOINTがあります。
どちらもコンテナ起動時の実行プロセスを指定しますが、ENTRYPOINTは強制的に実行されるポイントを定義する命令です。
一方、CMDはあくまで「デフォルト」のコマンドであり、コンテナ起動時にオプション指定で上書きされることがあります。

初心者の段階では、シンプルにCMDを使うケースが多いです。
カスタマイズされた引数を強制したい場合や、メインプロセスとして固定したいものがあるときにENTRYPOINTを使うと考えると分かりやすいかもしれません。

CMD命令の注意点

CMDはDockerfileの最後に1回だけ記述するのが通常です。
複数のCMDが書かれた場合、最後に書いたものが優先される点に注意してください。
そのため、もし複数のコマンドを実行したい場合は、スクリプトを作ってそれをCMDとして実行する形が一般的になります。

また、CMDを書く位置を誤ってしまうと、意図しないプロセスが起動してしまう場合があります。
たとえばイメージをビルドしたのに、そもそもコンテナが起動しないという事態を招くこともあるため、CMDで実行するコマンドは動作確認をしておくと安心です。

Dockerfileを使った実務での活用シーン

Dockerfileは単に学習用ではなく、ビジネス現場でもさまざまな形で使われています。
ここでは、実務でよく見かける活用シーンを2つ紹介します。

チーム開発での利点

大きな開発チームでは、メンバーごとに環境設定が異なるとトラブルが発生しがちです。
Dockerfileにインストールすべきソフトウェアやライブラリを明示しておけば、複数の人が同じDockerfileからビルドしたイメージを使えるようになります。
これにより動作確認やデバッグ作業の再現性が高まり、不要な環境トラブルが減る利点があります。

さらに、DockerfileはテキストファイルなのでGitなどのバージョン管理とも相性が良いです。
誰がいつどのように変更したかが一目で分かるため、設定の変更履歴を追いかけることも容易になります。

CI/CDパイプラインとの連携

自動テストや自動デプロイを行う際にもDockerfileは活用されます。
例えば、リポジトリにプルリクエストが作成されるたびに自動でDockerイメージをビルドし、テストを実行するなどの仕組みづくりが考えられます。
その過程でイメージが合格したら本番環境にデプロイする、という流れで連携している組織も多いです。

このようにCI/CDパイプラインを構築すれば、コードの変更と同時に環境の変更も追従できるため、デプロイミスが大幅に減ります。
「本番環境だけ特定のバージョンが違う」というリスクを抑えられるのがDockerfileの大きなメリットです。

Dockerfileベストプラクティス

Dockerfileを書き進める中で、覚えておきたいベストプラクティスがいくつかあります。
これらを意識することで、イメージサイズやビルド速度、セキュリティ面などの最適化につながります。

キャッシュを意識した書き方

Dockerは命令ごとにキャッシュを作る仕組みがあります。
つまり、一度実行が成功したステップは変更がない限り再実行されず、ビルド時間が短縮されます。
しかし命令の順番次第ではキャッシュを無駄に使えず、毎回時間のかかる処理をやり直すことになってしまうケースがあります。

たとえば、依存パッケージのインストールを行うRUN命令よりも先に、ソースコードをコピーするCOPY命令を置いてしまうと、ソースコードが変更されるたびにキャッシュが無効化され、毎回依存関係のインストールが走ってしまいます。
できるだけ依存パッケージのインストール命令を先に記述し、その後でソースコードをCOPYする流れにするのが一つの工夫です。

レイヤーを減らす工夫

Dockerイメージを構成するレイヤーが増えるほど、イメージサイズが肥大化しやすくなります。
また、ビルドやプッシュ・プルの時間も長くなり、チーム全体の開発効率に影響を及ぼします。
そのため、RUN命令は少なくし、不要なファイルはCOPYしない、といった意識が求められます。

RUN apt-get updateRUN apt-get installを分けるのではなく、まとめて書く。
特にログファイルや一時ファイルがイメージに含まれないように注意する。
こうした小さな気づかいが、最終的には大きな差を生むことがあります。

セキュリティ対策のポイント

コンテナ化しても、内部で動かすのはOSやアプリケーションなので、セキュリティ面の考慮は不可欠です。
Dockerfileでのセキュリティ対策としては、不要なパッケージをインストールしない、権限の高いユーザーで起動しない、といった点が挙げられます。
USER命令を使ってrootユーザーではなく一般ユーザーに切り替える運用も多いです。

あとは、ソースコードに含まれている機密情報や環境変数が漏れないように注意してください。
たとえば認証トークンやパスワードなどを含まないようにDockerfileを設計し、機密情報は別途安全な手段で注入することが望ましいです。

Dockerfileの書き方のポイントをもう少し深掘り

ここからは、ベーシックな命令以外で押さえておくと便利なテクニックを紹介します。
実務で「もう少し効率を上げたい」「より柔軟な構成を作りたい」と思ったときに役立つはずです。

マルチステージビルド

マルチステージビルドは、ビルド用の環境と最終的なアプリケーション実行用の環境を分けて書く手法です。
例えば、GoやJavaなどのコンパイルが必要な言語を使っているとき、ビルド環境には多数のライブラリやコンパイラが必要になりますが、実行環境には不要です。
そこで、最初のFROM命令でビルド専用のイメージを使い、完成したバイナリだけを次のステージのイメージへコピーすると、最終的にサイズを抑えた軽量イメージが作れます。

FROM golang AS builder
WORKDIR /app
COPY . /app
RUN go build -o myapp

FROM alpine
COPY --from=builder /app/myapp /usr/local/bin/myapp
CMD ["myapp"]

このように書くことで、ビルドのための余分なファイルを本番環境に持ち込まずに済みます。
Dockerのレイヤーを効率的に活用できるので、本番デプロイが高速かつ安全になるメリットがあります。

環境変数の扱い

アプリケーションが動作する上で、よく環境変数を利用するケースがあります。
DockerfileではENV命令を使うことで環境変数を設定できます。
例えば、アプリケーションのポート番号や言語設定などをENVで定義しておけば、イメージ内でのデフォルト値を固定できます。

FROM node:16
ENV PORT=3000
WORKDIR /app
COPY . /app
RUN npm install
CMD ["npm", "start"]

ただし、秘密のトークンやパスワードを直接ENVに書くのはリスクがあります。
そのような機密情報は、Dockerのビルド時には扱わずに、実行時に外部から注入する形にするなどの工夫を検討すると良いでしょう。

トラブルシューティング

Dockerfileを使ってイメージをビルドしていると、予期せぬエラーや動作の不具合に直面することがあります。
ここではよくあるトラブル例を2つ挙げ、対処の考え方を簡単にまとめます。

Dockerfileビルド失敗時の対応

よくあるケースとして、RUN apt-get installの段階でパッケージが見つからない、ネットワークのエラーが起きるなどが挙げられます。
特にネットワークまわりは一時的な問題も多いので、少し時間を置いて再度ビルドを試みるだけで解決する場合もあります。
また、レポジトリのURLが正しく指定されているか、apt-get updateを省略していないかなど、基本的な手順を確認しましょう。

次に、依存関係の競合でビルドが失敗する場合もあります。
ライブラリ同士のバージョンがかみ合わないと、インストール時にエラーが生じることがあります。
この場合は、Dockerfileを小さく分割してトラブルの箇所を特定し、どのライブラリが衝突しているかを丁寧に調べることが大切です。

コンテナのデバッグ方法

ビルドが成功しても、実際にコンテナを起動したら動作が想定と違うというケースもあります。
そのような場合には、コンテナの中に入って直接状況を確認する手段があります。

コンテナを起動した後、docker exec -it <コンテナ名> /bin/bashのようにしてシェルに入れます。
もしシェルが用意されていないイメージならshや別の手段を使う場合もありますが、とにかくコンテナ内に入り、インストールされているパッケージや環境変数の状態を確認できるのです。
この操作によって、ファイルの存在やポートの開放などを確認し、問題解決につなげやすくなります。

コンテナ内部に入って直接ファイルを編集すると、次回のビルド時に変更が反映されません。
あくまでデバッグ用の操作である点を理解し、実際の修正はDockerfileに反映させましょう。

まとめ

ここまで、Dockerfileの基本命令であるFROMRUNCOPYCMDを中心に、実務での利用シーンやベストプラクティス、さらに少し進んだテクニックまで紹介しました。

Dockerfileの最大の利点は、特定の環境構築手順を明確化できるところです。
チーム開発の効率化、CI/CDでの自動化、イメージの移植性など、多くのメリットをもたらしてくれます。

ただし、イメージのサイズやセキュリティを軽視すると、後々の運用で大きなつまずきになることもあります。
依存パッケージの選び方、レイヤー数の最適化など、細やかな部分を意識してDockerfileを整備すると、トラブルが減り、パフォーマンスも向上しやすくなるでしょう。

最初はシンプルなDockerfileから始めて、慣れてきたらマルチステージビルドや環境変数の管理などを活用して、自分のプロジェクトに合ったコンテナ環境を築いてみてはいかがでしょうか。

  • FROMでベースイメージを決め、RUNで環境構築を行い、COPYでソースをコピーし、CMDで起動する流れが基本
  • キャッシュを意識した命令の順番がビルド効率を左右する
  • マルチステージビルドやENV命令など、少し進んだ機能も覚えると応用の幅が広がる
  • 運用時のデバッグやセキュリティ管理もDockerfileでは大切な要素

Dockerfileをしっかり理解して使いこなせるようになれば、チーム開発や本番環境へのデプロイがスムーズになり、コードとインフラを一体で管理できる利便性を実感できるはずです。
ぜひ、実践の中でDockerfileの書き方と運用ノウハウを深めていってください。

Dockerをマスターしよう

この記事で学んだDockerの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。