このブログ記事では、DockerにおけるDockerfileについて紹介します。
前回の記事では、Dockerとイメージおよびコンテナの概念を紹介しました。今回の記事では、 ポート4000を開いて待つNodeJSで構築されたウェブサーバーの例を使用して、イメージとコンテナを作成および管理する方法について説明します。
Dockerfiles
イメージを作成するには、Dockerエンジンにイメージの構成(レイヤー)を指定するDockerfileを作成します。 以下はウェブサーバー用のイメージを作成するためのDockerfileの例です:
# Node.JSバージョン23 Alpine Linux 3.20
FROM 23-alpine3.20
# ワーキングディレクトリ
WORKDIR /app
# ソースコードをワーキングディレクトリにコピー
COPY . .
# package.jsonから依存関係インストール
RUN npm install
# コンテナ内で実行するコマンドを設定
CMD ["node", "app.js"]上記では、FROMキーワードを使用して親イメージを指定することから始めます。ウェブサーバーはNode.JSで作られているため、
Node.JSバージョン23が事前にインストールされているAlpine Linux 3.20の公式nodeイメージ23-alpine3.20を取得します。
次に、作業ディレクトリ(WORKDIR)を設定し、COPYでソースコードをコピーし、npm installを実行して依存関係をインストールします。
CMDキーワードはコンテナ内で実行するコマンドを設定する一方、RUNはイメージ構築時に実行するコマンドを指定します。
CMDキーワードは必要に応じて変数を受け取るために文字列ではなく文字列のリストを受け付けます。
イメージとコンテナの管理
Dockerfileからイメージを構築するには、docker build -t <イメージ名> <相対パス>を使用できます。
ここで-tはタグ(または名前)を設定するためのフラグであり、<相対パス>はDockerfileへの相対パスです。
ここでは、イメージのバージョン管理のためにmyserver:v1のような名前を使用できます。作成後docker imagesを使用してイメージのリストとその詳細を確認できます。
イメージを構築した後、docker run --name <コンテナ名> <イメージ名>を使用してイメージを実行し、コンテナを起動することができます。
コンテナはポート4000で待機するウェブサーバーを実行するため、コンテナのポート4000をローカルホストの任意のポートにマップする必要があります。
これは-p <ローカルポート>:<コンテナポート>を追加することで行えます。また、ターミナルをブロックするのを防ぐために-dフラグを使用し、
プロセスをデタッチすることもできます。
コンテナを起動し、コンテナポートをマップしたローカルホストのポートからデータをリクエストすると、コンテナ内のサーバーからのデータが返ってくるのが確認できるはずです。
コンテナの停止と再起動には、それぞれdocker stop <コンテナ名>とdocker start <コンテナ名>を使用できます。
また、docker psを使用して実行中のすべてのコンテナを確認でき、-aフラグを使用すると実行されていないものも含めたすべてのコンテナを確認できます。
最後に、docker image rm <イメージ名>とdocker container rm <コンテナ名>を使ってイメージとコンテナを削除できます。
(Dockerには他にも多くのコマンド(シェルと対話するためのdocker exec -itなど)や、多くのコマンドに役立つタグがあります。
これらについての詳細は公式ドキュメントを参照してください。)
レイヤーキャッシング
イメージを構築する際、Dockerは一時的にイメージレイヤーをキャッシュに保存し、同様のイメージをより効率的に作成できるようにします。 例えば、ソースコードのみを変更し、上記と同じDockerfileで新しいイメージを作成する場合、Dockerは最初の2つのレイヤーを再実行する必要がないと自動的に判断し、 代わりにキャッシュされたイメージレイヤーを読み込みます。このキャッシングを活用して、イメージ作成を次のように高速化できます。
FROM 23-alpine3.20
WORKDIR /app
# 全てのソースコードをコピーする前に依存関係をインストール
COPY pacakge.json .
RUN npm install
COPY . .
CMD ["node", "app.js"]package.jsonをコピーしてソースコード全体をコピーする前に依存関係をインストールすることで、
ソースコードのみを変更した後に新しいイメージを作成する際に依存関係のインストールを再実行する必要がなくなります(Dockerがpackage.jsonの変更を検出した場合は依存関係のインストールを再実行します)。
ボリューム
コードを頻繁に変更し開発する場合、レイヤーキャッシングによってイメージ作成が高速化されたとしても、コード変更のたびに新しいイメージを作成し、
新しいイメージを実行して新しいコンテナを起動するのは面倒です。この問題を解決するために、docker runの-v <絶対ローカルパス>:<コンテナパス>というボリュームフラグを使用して、
コンテナがローカルマシン上のファイルにアクセスできるようにすることができます。ボリュームを使用する場合、コンテナは<コンテナパス>内のファイルを参照する際に<絶対ローカルパス>内のファイルを使用します。
通常、コンテナの作業ディレクトリ全体をローカルデバイス上のプロジェクトフォルダ全体にマップするボリュームを単純に追加することが多いです。
しかし、どの個別のローカルファイルがコンテナからアクセス可能であるべきか、そうでないべきかを選択したい場合もあります(例えば、ローカルデバイス上の古いnode_modulesではなく、
npm installでセットアップされたコンテナ自身のnode_modulesをコンテナに使用させたい場合)。
そのような場合、個別のファイルごとにボリュームを追加するか、プロジェクトフォルダ全体にボリュームを追加し、-v <コンテナパス>のように特定のパスに対してコンテナに独自のファイルを使用させる匿名ボリュームを使用することができます。
ここで重要なのは、ボリュームを使用していても、イメージをデプロイして共有する際には、すべての変更を反映させるために新しいイメージを作成する必要があるということです。
結論
この記事では、Dockerfile、イメージとコンテナを管理するためのいくつかのコマンド、レイヤーキャッシング、およびボリュームを紹介しました。
これらはDockerを使い始めるために必要な基本的な概念のほとんどをカバーしています。作成されたDockerイメージは、
アカウントを作成し、docker loginでログインし、docker pushでプッシュすることでDocker Hubで共有できます。
基本を押さえたところで、次の記事では、Dockerの使用をより簡単にする便利な機能を紹介します。
リソース
- Net Ninja. 2022. Docker Crash Course #5 - The Dockerfile. YouTube.
- Net Ninja. 2022. Docker Crash Course #7 - Starting & Stopping Containers. YouTube.
- Net Ninja. 2022. Docker Crash Course #8 - Layer Caching. YouTube.
- Net Ninja. 2022. Docker Crash Course #9 - Managing Images & Containers. YouTube.
- Net Ninja. 2022. Docker Crash Course #10 - Volumes. YouTube.
- Net Ninja. 2022. Docker Crash Course #13 - Sharing Images on Docker Hub. YouTube.