Node イメージのビルド
読む時間の目安: 6 分
前提条件
「はじめよう」の 1 部 に示しているガイドや設定をひととおり読んで、Docker の考え方について理解してください。
BuildKit の有効化
イメージビルドを行うにあたっては、マシン上において BuildKit が有効になっていることを確認してください。 BuildKit が有効になっていれば、Docker イメージビルドを効率的に行うことができます。 詳しくは BuildKit によるイメージ構築 を参照してください。
BuildKit は Docker Desktop 上の全ユーザーに対して、デフォルトで有効になっています。 Docker Desktop をインストールしているのであれば、手動で BuildKit を有効にする必要はありません。 Linux 上において Docker を利用している場合は、環境変数を用いるか、あるいは BuildKit がデフォルト設定となるようにして、BuildKit を有効にします。
docker buildコマンド時に環境変数 BuildKit を設定するには、以下のようにします。
$ DOCKER_BUILDKIT=1 docker build .
デフォルトで BuildKit を有効にするには、/etc/docker/daemon.json内のデーモン設定をtrueに設定して、デーモンを再起動します。
daemon.jsonファイルが存在していない場合は、このdaemon.jsonファイルを新規生成して、以下の記述を追加します。
{
"features":{"buildkit" : true}
}
そして Docker デーモンを再起動します。
概要
ここまでに コンテナーや Docker プラットフォームについての概要を理解してきたので、ここからは初めてのイメージ作りを見ていくことにしましょう。 イメージというものは、アプリケーションを動作させるために必要なものをすべて含みます。 コード、実行モジュール、ランタイム、依存パッケージ、その他にシステムオブジェクトが必要とするあらゆるファイルです。
本チュートリアルを進めていくためには、以下が必要になります。
- Node.js バージョン 12.18 またはそれ以降。 Node.js をダウンロードしてください。
- ローカルで動作している Docker。 以下の手順に従って Docker のダウンロードとインストール を行ってください。
- ファイル編集を行うための IDE またはテキストエディター。 ここでは Visual Studio Code をお勧めします。
サンプルアプリケーション
例として用いる簡単な Node.js アプリケーションを作っていきます。
ローカルマシン内にnode-dockerという名前のディレクトリを生成し、以下の手順により簡単な REST API を生成します。
$ cd [各自のnode-dockerディレクトリ]
$ npm init -y
$ npm install ronin-server ronin-mocks
$ touch server.js
そこで REST 要求を行うコードを追加してみます。 簡単なモックサーバーを用いて、アプリケーションの Docker 化を行っていきます。
IDE 環境においてワーキングディレクトリを開きます。
そして以下のコードをserver.jsファイルとして書き込みます。
const ronin = require('ronin-server')
const mocks = require('ronin-mocks')
const server = ronin.server()
server.use('/', mocks.server(server.Router(), false, true))
server.start()
モックサーバーはRonin.jsと呼ぶことにして、デフォルトでポート8000 番を待ち受けるものとします。
ルート(/)エンドポイントに対して POST 要求が行えるものとし、サーバーに送信した JSON データは、すべてメモリに保存されるようにします。
同じくルートエンドポイントに GET 要求が送信可能であり、その前に POST 要求した結果を JSON オブジェクトの配列として受信するものとします。
アプリケーションのテスト
ここからアプリケーションを起動させて正しく動作することを確認します。 ターミナルを開いて、上で生成したワーキングディレクトリに移動します。
$ node server.js
アプリケーションが適切に動作していることを確認するため、初めに API に対して JSON データを POST します。 そして GET リクエストを送信して、データがどのように保存されるかを見てみます。 新たなターミナルを開いて、以下の curl コマンドを実行します。
$ curl --request POST \
--url http://localhost:8000/test \
--header 'content-type: application/json' \
--data '{"msg": "testing" }'
{"code":"success","payload":[{"msg":"testing","id":"31f23305-f5d0-4b4f-a16f-6f4c8ec93cf1","createDate":"2020-08-28T21:53:07.157Z"}]}
$ curl http://localhost:8000/test
{"code":"success","meta":{"total":1,"count":1},"payload":[{"msg":"testing","id":"31f23305-f5d0-4b4f-a16f-6f4c8ec93cf1","createDate":"2020-08-28T21:53:07.157Z"}]}
サーバーを起動しているターミナルに戻ります。 サーバーログとして以下のようなリクエストが表示されているはずです。
2020-XX-31T16:35:08:4260 INFO: POST /test
2020-XX-31T16:35:21:3560 INFO: GET /test
すばらしい。 アプリケーションが動作しているのを確認できました。 この時点で、サーバースクリプトの動作テストはローカルで完了したことになります。
サーバーを稼働させているターミナルセッション内からCTRL-cを入力して、サーバーを停止させます。
2021-08-06T12:11:33:8930 INFO: POST /test
2021-08-06T12:11:41:5860 INFO: GET /test
^Cshutting down...
Docker 内でのアプリケーションのビルドと実行は、この後も続けます。
Node.js を用いた Dockerfile の生成
Dockerfile とは、 Docker イメージを取得する命令を含んだテキストファイルのことです。
Docker に対してdocker buildコマンドを実行してイメージビルドを指示すると、Docker は記述された命令を読み込んで実行し、最終的に Docker イメージを作り出します。
ではアプリケーション向けに Dockerfile を生成していく手順を見ていきます。
プロジェクトのルートディレクトリにDockerfileという名前のファイルを生成して、テキストエディターでこれを開きます。
Dockerfile の名前を何にするか
Dockerfile に対して用いられるデフォルトのファイル名は、
Dockerfileです (ファイル拡張子はありません)。 このデフォルトファイル名を用いておけば、docker buildコマンドを実行する際に、コマンドラインフラグを追加して指定する必要がありません。プロジェクトによっては、特定の目的のため、Dockerfile に別名を与える場合があります。 普通行われる慣例としては、
Dockerfile.<something>や<something>.Dockerfileとします。 このような Dockerfile は、docker buildコマンドの実行にあたって、--fileオプション (その短縮形-f) を用いて指定します。--fileオプションの利用方法については、docker buildリファレンス内の Dockerfile の指定 のセクションを参照してください。プロジェクトの主となる Dockerfile には、デフォルト名 (
Dockerfile) を用いることをお勧めします。 本ガイドに示すほとんどの例においては、この名前を用いています。
Docker ファイルの 1 行めに書くのは # syntaxパーサーディレクティブ です。
これは 任意の記述 ではありますが、Dockerfile の解析にあたって Docker ビルダーがどの文法を採用するのかを指示します。
また古い Docker バージョンにおいて BuildKit を利用する際に、ビルド前にパーサーをアップグレードするようになります。
パーサーディレクティブ は Dockerfile において、いずれのコメント、空行、Dockerfile 命令よりも前に、つまり第一に記述することが必要です。
# syntax=docker/dockerfile:1
docker/dockerfile:1を用いることをお勧めします。
こうしておくと、常に文法バージョン 1 の最新リリース版を指し示すことになります。
BuildKit は、ビルド処理の前に文法に更新がないかを自動的にチェックし、最新バージョンが用いられていることを確認します。
Dockerfile にその次に加えるのは、ベースイメージに何を用いるのかを指定します。 そのベースイメージを利用してアプリケーションを構築します。
# syntax=docker/dockerfile:1
FROM node:12.18.1
Docker イメージというものは、別のイメージを継承することができます。 したがって独自のベースイメージを作るのではなく、公式の Node.js イメージを利用することにします。 そのイメージには、Node.js アプリケーションの実行に必要となるツールやパッケージがすでに含まれています。 ここで行った継承は、オブジェクト指向プログラミング言語におけるクラス継承と同じようなものとして考えることができます。 たとえば Docker イメージを JavaScript 言語によってプログラミングできるとしたら、以下のように記述するイメージです。
class MyImage extends NodeBaseImage {}
上はMyImageという名のクラスを生成し、それはNodeBaseImageというベースクラスから機能性を継承するものです。
同じようなこととして Docker ではFROMコマンドを用います。
ここではnode:12.18.1というイメージの機能性をすべて含んだイメージとすることを Docker に指示します。
メモ
独自のベースイメージ作りについて学びたい場合は、ベースイメージの生成 を参照してください。
環境変数NODE_ENVはアプリケーションが動作する環境を定義します(通常は development(開発環境) か production(本番環境)です。)。
性能を改善させるために取るべき単純な方法は、このNODE_ENV変数をproductionに設定することです。
ENV NODE_ENV=production
これ以降のコマンド実行をやりやすくするように、ここでワーキングディレクトリを生成します。 Docker に対してこれを指示しておけば、この後に続くコマンドにおいてデフォルトディレクトリとして用いられます。 これによりファイルのフルパスを記述する必要がなくなり、ワーキングディレクトリからの相対パスを用いることができます。
WORKDIR /app
通常は Node.js で書かれたプロジェクトのダウンロード後、一番に行っておくことが npm パッケージのインストールです。
これを行っておくとアプリケーションの依存パッケージがすべてnode_modulesディレクトリ内にインストールされます。
Node ランタイムはそこから必要なパッケージを探し出せるようになります。
npm installを実行する前には、package.jsonとpackage-lock.jsonの両ファイルをイメージ内にコピーしておくことが必要です。
COPYコマンドを使ってこれを行います。
COPYコマンドには 2 つの引数srcとdestがあります。
1 つめの引数srcは、Docker に対してイメージ内にコピーしたい元のファイルを指示します。
2 つめの引数destは、Docker に対してそのファイルをイメージ内のどこにコピーするかを指示します。
たとえば以下のとおりです。
COPY ["<src>", "<dest>"]
複数のsrcがある場合は、カンマで区切って指定します。
たとえばCOPY ["<src1>", "<src2>",..., "<dest>"]とします。
ここではpackage.jsonとpackage-lock.jsonをワーキングディレクトリ/appにコピーします。
COPY ["package.json", "package-lock.json*", "./"]
なおここでは、ワーキングディレクトリ全体をコピーするのではなく、package.json ファイルだけをコピーしています。
こうすることで Docker レイヤーのキャッシュをうまく利用するためです。
package.jsonファイルをイメージ内に置いたらRUNコマンドによって npm install を行います。
この際の処理は npm install をマシン内でローカルに実行しているかのようにして動作します。
ただし Node モジュール類のインストール先は、イメージ内のnode_modulesディレクトリとなります。
RUN npm install --production
ここまでに Node バージョン 12.18.1 に基づくイメージを構築して、依存パッケージのインストールを行いました。
次に行うのは、イメージ内にソースコードを置くことです。
先に行ったpackage.jsonファイルと同じように COPY コマンドを用いることにします。
COPY . .
この COPY コマンドは、カレントディレクトリにあるファイルすべてをイメージ内にコピーします。
最後に行うのは、このイメージがコンテナー内において実行される際に実行させたいコマンドを指定します。
これを行うにはCMDコマンドを用います。
CMD [ "node", "server.js" ]
以下が完全な Dockerfile です。
# syntax=docker/dockerfile:1
FROM node:12.18.1
ENV NODE_ENV=production
WORKDIR /app
COPY ["package.json", "package-lock.json*", "./"]
RUN npm install --production
COPY . .
CMD [ "node", "server.js" ]
.dockerignore ファイルの生成
ビルドコンテキスト内のファイルを利用するには、たとえば COPY 命令などのように Dockerfile 内の命令においてそのファイルを指定します。
ビルド処理を効率化するためには、不要なファイルやディレクトリは .dockerignore ファイルに指定してコンテキストディレクトリに置きます。
以下は、コンテキストのロード時間を改善するために.dockerignoreを生成して、そこにnode_modulesディレクトリを加える例です。
node_modules
イメージのビルド
Dockerfile を生成したので、ここからイメージをビルドします。
これを行うにはdocker buildコマンドを使います。
docker buildコマンドは Dockerfile と「コンテキスト(context)」から Docker イメージをビルドします。
ビルドのコンテキストとは、指定されているパスや URL 内にある一連のファイルのことです。
Docker のビルド処理においては、コンテキスト内にあるファイルはどれにでもアクセスすることができます。
build コマンドにはオプションとして--tagフラグをつけることができます。
タグ(tag)はイメージ名とオプションとなるタグ名を‘name:tag’という書式で指定します。
話を単純にするため、ここでは「タグ」は用いないことにします。
タグを指定しなければ Docker はデフォルトのタグ名として「latest」を用います。
これはビルド処理結果の出力最終行に示されます。
では初めての Docker イメージをビルドしてみます。
$ docker build --tag node-docker .
[+] Building 93.8s (11/11) FINISHED
=> [internal] load build definition from dockerfile 0.1s
=> => transferring dockerfile: 617B 0.0s
=> [internal] load .dockerignore 0.0s
...
=> [2/5] WORKDIR /app 0.4s
=> [3/5] COPY [package.json, package-lock.json*, ./] 0.2s
=> [4/5] RUN npm install --production 9.8s
=> [5/5] COPY . .
ローカルイメージの確認
ローカルマシン内にあるイメージの一覧を見るには 2 つの方法があります。 1 つは CLI を用いる方法、もう 1 つは Docker Desktop を用いる方法です。 これまでターミナルを使って作業を進めてきていますから、イメージ一覧は CLI を使って取得することにします。
イメージを一覧表示するには、単純にimagesコマンドを実行します。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
node-docker latest 3809733582bc About a minute ago 945MB
出力結果は上とは異なるはずです。
ただし上でビルドしたものとして、タグlatestを持つnode-docker:latestは表示されているはずです。
イメージへのタグづけ
イメージ名はスラッシュによって区切られた名称により構成されます。 この名称には、英字の小文字、数字、セパレーター文字が利用可能です。 このセパレーター文字とは、1 つのピリオド、1 つまたは 2 つのアンダースコア、いくつかのダッシュ、のいずれかです。 各名称のはじめと終わりにセパレーター文字を用いることはできません。
イメージとは、マニフェストと複数レイヤーによって構成されるものです。 単純に言って「タグ」というものは、それらの生成要素の組み合わせを指しています。 イメージに対しては複数のタグを設定できます。 作り上げてきたイメージに対する 2 つめのタグを生成します。 そしてそのレイヤー構成を見てみます。
イメージに対して新たなタグを生成するには、以下のコマンドを実行します。
$ docker tag node-docker:latest node-docker:v1.0.0
docker tag コマンドはイメージに対するタグを生成します。 新たなイメージが作り出されるわけではありません。 このタグもまた同じイメージを指していて、イメージを参照するもう 1 つの手段が出来上がったことになります。
そこでdocker imagesコマンドを実行して、ローカルイメージの一覧を確認します。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
node-docker latest 3809733582bc 24 minutes ago 945MB
node-docker v1.0.0 3809733582bc 24 minutes ago 945MB
node-dockerで始まるイメージが一覧に 2 つ表示されています。
IMAGE ID 列を見てみれば、その 2 つのイメージは同一のものであることがわかります。
2 つのイメージの ID 値は同じだからです。
では今生成したタグを削除してみます。 これを行うには rmi コマンドを使います。 rmi コマンドは「remove image」を表しています。
$ docker rmi node-docker:v1.0.0
Untagged: node-docker:v1.0.0
Docker の出力結果からわかるように、イメージは削除されたわけではなく「タグづけ解除」が行われただけです。 images コマンドを実行して確認してみます。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
node-docker latest 3809733582bc 32 minutes ago 945MB
:v1.0.0としてタグづけを行ったイメージは削除されましたが、マシン上にはnode-docker:latestというタグを通じてイメージが参照可能です。
次のステップ
本節ではサンプル Node アプリケーションの設定を行いました。 これはこの先のチュートリアルを通じて利用していきます。 また Dockerfile を生成して Docker イメージのビルドに利用しました。 そしてイメージへのタグづけ、タグづけ解除を行いました。 次節では以下のことを行います。
フィードバック
本トピック改善のためにフィードバックをお寄せください。 お気づきの点があれば Docker Docs の GitHub リポジトリに issue をあげてください。 あるいは PR の生成 により変更の提案を行ってください。