自分の Node イメージを構築

事前準備

Docker の概念を理解するため、導入ガイド Part 1 での説明を読み、準備をしてください。

BuildKit 有効化

イメージの構築を始める前に、マシン上で BuildKit を有効化します。 BuildKit があれば Docker イメージを効率的に構築できます。詳しい情報は、 BuildKit でイメージ構築 をご覧ください。

Docker Desktop 上のすべてのユーザは、デフォルトで BuildKit は有効化されています。Docker Desktop をインストール済みの場合、手動で BuildKit を有効化する必要はありません。 Linux 上で Docker を動かしている場合は、 BuildKit を有効化するため、環境変数を設定するか、 BuildKit がデフォルトになるよう設定を変更します。

docker build コマンドの実行時、 BuildKit 環境編集を設定するには、次のように実行します。

$ DOCKER_BUILDKIT=1 docker build .

Docker で BuildKit をデフォルトで有効化するには、 /etc/docker/daemon.json のデーモン設定で機能を true にし、デーモンを再起動します。 daemon.json ファイルが存在しなければ、 daemon.json という名前でファイルを作成し、ファイル内に以下を追加します。

{
  "features":{"buildkit" : true}
}

Docker デーモンを再起動します。

概要

これでコンテナと Docker プラットフォームの概要が分かりましたので、初めてのイメージを構築しましょう。イメージにはアプリケーションの実行に必要な全てを含みます。具体的には、コードやバイナリ、ランタイム、依存関係、必要なその他のファイルシステムです。

このチュートリアルを終えるには、以下が必要です。

サンプル アプリケーション

例として使える、簡単な Node.js アプリケーションを作りましょう。ローカルマシン内に node-docker という名前のディレクトリを作成し、簡単な REST API を作成するため、以下の手順を進めます。

$ cd [path to your node-docker directory]
$ 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

アプリケーションが正しく動作しているか確認するには、まず何らかの JSON を API に 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 build コマンドを実行し、 Docker に対してイメージ構築を命令すると、 Docker はこれらの命令を読み込み、命令を実行し、その結果を Docker イメージとして作成します。

アプリケーションのために Dockerfile を作成する流れを見ていきましょう。プロジェクトのルートで、 Dockerfile という名前のファイルを作成し、このファイルをテキストエディタで開きます。

注釈

Dockerfile の名前はどうしますか?

Dockerfile のデフォルトファイル名は Dockerfile です(拡張子はありません)。デフォルトの名前を使えば、 docker build コマンドの実行し、コマンドにフラグの追加が不要です。

プロジェクトによっては、特定の目的に対して Dockerfile を分ける必要があるでしょう。一般的な慣習として、名前を Dockerfile.<何か><何か>.Dockerfile にします。このような Dockerfile は docker build コマンドで --file ( や省略形 -f` )オプションを渡して利用できます。 --file オプションについて学ぶには、 docker build リファレンスの Dockerfile の指定(-f) を参照ください。

このガイドの大部分の例でも使われているように、プロジェクトで主となる Dockerfile には、デフォルト( Dockerfile )の利用を推奨します。

Dockerfile の1行目に追加するのは、 # syntax パーサ ディレクティブ です。この命令は「オプション」ですが、Docker ビルダがどの Dockerfile を使って解釈するかを指定できます。さらに、古い BuildKit が入っている Docker のバージョンで構築する前に、アップグレードをできるようにします。 パーサ ディレクティブ は、 Dockerfile 内であらゆるコメント、空白、 Dockerfile より前に書く必要があるため、 Dockerfile では1行目に書くべきです。

# syntax=docker/dockerfile:1

私たちは docker/dockerfile:1 の指定を推奨します。これは、バージョン1構文の最新リリースを常に示します。 BuildKit は構築前、自動的に構文を確認するため、直近の現行バージョンを使えるようにします。

次は、Docker にアプリケーションが何のベースイメージを使うかを伝えるため、 Dockerfile に行を追加する必要があります。

# syntax=docker/dockerfile:1

FROM node:12.18.1

Docker イメージは他のイメージを 継承(inherit) できます。そのため、自分でベースイメージを作成するのではなく、公式の Node.js イメージを使います。イメージには Node.js アプリケーションの実行に必要なツールとパッケージが全て入っています。これはオブジェクト指向プログラミング言語における、クラス継承と同じように考えられます。たとえば、 JavaScript で Docker イメージを作成できるならば、書き方は以下のようになります。

class MyImage extends NodeBaseImage {}

これは MyImage と呼ぶクラスを作成し、基底クラス NodeBaseImage の機能を継承します。

同じような手法で、 FROM コマンドを使用すると、私たちのイメージの全ての機能は node:12.18.1 イメージに入っていると Docker に伝えることになります。

注釈

自分でベースイメージを作成する方法についての情報は /develop/develop-images/baseimages をご覧ください。

NODE_DEV 環境変数は、アプリケーションの動作環境を指定します(通常は、 development か production)。パフォーマンス改善にもっとも簡単な方法は、 NODE_ENVproduction に指定します。

ENV NODE_ENV=production

以降のコマンドを実行しやすくるため、作業ディレクトリを作成しましょう。この命令は、以降すべてのコマンドを実行するデフォルトの場所として、指定したパスを使うよう Docker に対して伝えます。この方法によりフルパスを入力する必要がなくなりますが、その作業ディレクトリを基準とした相対パスで記述する必要があります。

WORKDIR /app

通常、 Node.js で書かれたプロジェクトをダウンロードして最初にするのは、 npm パッケージのインストールです。これにより、アプリケーションのすべての依存関係が node_modules ディレクトリにインストールされ、 Node ランタイムがそれらを見つけられるようになります。

npm install を実行する前に、 package.jsonpackage-lock.json ファイルをイメージの中に入れる必要があります。そのためには COPY 命令が使えます。 COPY 命令は2つのパラメータ、 srcdest を使います。1つめのパラメータ src は、 Docker に対して何のファイル(群)をイメージにコピーするかを伝えます。2つめのパラメータ dest は、 Docker に対してファイル(群)をどこにコピーしたいか伝えます。以下は例です。

COPY ["<src>", "<dest>"]

複数の src リソースをカンマで区切りで指定でいます。たとえば、 COPY ["<src1>", "<src2>",..., "<dest>"] です。ここでは package.jsonpackage-lock.json ファイルを、作業ディレクトリ /app にコピーします。

COPY ["package.json", "package-lock.json*", "./"]

注意として、作業ディレクトリ全体をコピーするのではなく、 package.json ファイルのみコピーします。これにより、 Docker レイヤのキャッシュを活用できます。イメージ内にファイルが入ってしまえば、 RUN 命令を使って npm install コマンドを実行できるようになります。これは、自分のマシン上でローカルに npm install を実行するのと全く同じ挙動です。ですが、今回は各 Node モジュールはイメージ内の node_modules ディレクトリ内へインストールされます。

RUN npm install --production

この時点で、私たちのイメージは node バージョン 12.18.1 をベースにし、必要となる依存関係をインストールしました。次に必要なのは、ソースコードをイメージの中に追加します。先ほど package.json ファイルで行ったように、 COPY コマンドを使います。

COPY . .

この COPY コマンドは、現在のディレクトリ内にある全てのファイルを取得し、すべてをイメージの中にコピーします。次は、イメージの実行時、コンテナ内で実行したいコマンドが何かを Docker に伝える必要があります。これを 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 ファイルの作成

構築コンテクスト(訳者注:docker build で指定したディレクトリ内に含まれる、ファイルなどの中身のこと)内でファイルを使うために、 Dockerfile は COPY 命令のような命令で指定されたファイルを参照します。構築時のパフォーマンスを上げるには、、ファイルやディレクトリを除外するため、コンテクストがあるディクトリに .dockerignore ファイルを追加します。コンテクストの読み込み時間を減らすため、 .dockerignore ファイルを追加し、その中に node_module ディレクトリを追記します。

node_modules

イメージ構築

これで Dockerfile が作成できましたので、イメージを構築しましょう。そのためには docker build コマンドを使います。 docker build コマンドは Dockerfile と "コンテクスト" からイメージを構築します。構築コンテクストとは、指定したパスまたは URL 内に置かれているファイル群です。 Docker 構築プロセスは、コンテクスト内に置かれているあらゆるファイルにアクセス可能です。

build コマンドは、オプションで --tag フラグを付けられます。 タグ(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

実際の出力は様々ですが、イメージの一覧には先ほど構築した node-docker:latest と、 latest タグのあるイメージが確認できるでしょう。

イメージにタグ付け

イメージ名は、スラッシュ記号で区切られた名前の要素で構成されます。名前の要素には、小文字の文字列、数字、 セパレータ(separator) (区切り文字)を含みます。セパレータとして定義されているのは、ピリオド、1つまたは2つのアンダースコア、1つまたは2つのダッシュです。名前の要素では、初めと終わりにセパレータを使えません。

イメージは マニフェスト(manifest) と一連のレイヤによって構成されます。簡単に言うと、「タグ」が示すのは、これら アーティファクト(artifact) (訳者注:完成したイメージのこと。成果物)の組み合わせを示します。イメージは複数のタグを持てます。構築済みのイメージに2つめのタグを作成し、レイヤをみてみましょう。

先ほど構築したイメージに新しいタグを作成するには、以下のコマンドを実行します。

$ docker tag node-docker:latest node-docker:v1.0.0

docker tag コマンドはイメージに新しいタグを作成しますが、新しいイメージは作成しません。タグが示すのは同じイメージであり、そのイメージを別の方法で参照しているだけです。

次は 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つのイメージの値は同じに見えますので、どちらも同じイメージだと分かります。

先ほど作成したタグを消しましょう。そのためには、 rmi コマンドを使います。rmi コマンドは「 イメージ削除(remove image) 」を表します。

$ docker rmi node-docker:v1.0.0
Untagged: node-docker:v1.0.0

Docker の応答から分かるのは、イメージは削除しておらず、単に「 タグを削除済み(untagged) 」です。 images コマンドを実行して、これを確認しましょう。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
node-docker         latest              3809733582bc        32 minutes ago      945MB

私たちのイメージは、タグ :v1.0.0 が削除されたものの、まだ node-docker:latest タグはマシン上で利用可能です。

次のステップ

この章では、以降のチュートリアルで使うサンプル Node アプリケーションの設定方法を説明しました。また、Docker イメージ構築に使う Dockerfile を作成しました。それから、イメージにタグをつけ、イメージからタグを削除する方法を説明しました。次の章では、コンテナとしてイメージを実行する方法を説明します。

フィードバック

フィードバックを通し、このトピックの改善を支援ください。考えがあれば、 Docker Docs GitHub リポジトリに issue を作成して教えてください。あるいは、更新の提案のために RP を作成 してください。