Docker で Node.js をサクッと使う

Node.js の環境構築は面倒です.
本体の Node とパッケージ管理の npm (もしくはyarn) はバージョンがたくさんあって組み合わせが悪いとうまく動かない可能性がある, 検索すると入れ方が何通りも出てきてどれが一番良いのか不明, パスを通す手順が分かりにくい, etc...
今回は Docker を経由して Node.js を楽に使う手順を紹介します.

Docker とは

Docker を入れる

Mac

Windows

イメージを入れる

Docker Hub というサイトに多くのイメージが公開されています. 公式レポジトリ一覧を見ると, Ubuntu, CentOS, Debian のようなLinuxディストリビューションや, nginx, Redis のようなサーバー, さらには Node.js, PHP, Ruby のようなサーバーサイドで動く言語を中心とした様々な言語の実行環境なども提供されているのが分かります.
Node のイメージ一覧(https://hub.docker.com/_/node/)を見てみましょう. f:id:HelloRusk:20180910231922p:plain

[Nodeのバージョン番号]-[Linuxのコードネーム]でタグ付けされていて, 例えば8.11.4-jessieDebian 8.0 jessie 上で Node.js 8.11.4 の環境を構築したものです.
タグ名をクリックすると Dockerfile が確認できるので, 具体的にどういう操作が行われるか見ておくのがよいでしょう.

↓例えばこれは8.11.4-jessieのDockerfileです. Node.js 8.11.4, yarn v1.6.0 を入れていることがわかります.

Dockerfile とはイメージを生成する際の命令をまとめたものです. Dockerfile リファレンスを参考にしましょう.


それでは, 実際にイメージを入れてみましょう.

docker pull node:8.11.4-jessie

正常にダウンロードされれば, docker imagesと入力すると

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
node                8.11.4-jessie       XXXXXXXXXXXX        5 days ago          673MB

などと表示されるはずです.
なお, docker inspect 8.11.4-jessieのように, docker inspect [イメージのタグ]と入力すると, イメージに関する詳細な情報がJSONで表示され, 環境変数などを確認できます.

コンテナを作成する

入れられた8.11.4-jessieのイメージをもとに仮想環境のコンテナを作成するには, docker runします. このコマンドの基本的な使い方は

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

です.
[OPTIONS]の部分で指定できるオプションが多数用意されていて, docker run --helpで確認できます. 一部を紹介すると

  • -it: ターミナル (or コマンドプロンプト or PowerShell) でコンテナを操作するために必要です.
  • --name string: コンテナにstringという名前を付けることができます. string の部分にカギカッコは不要です. 指定しないと自動で名前が与えられます.
  • -p: ポートフォワードします. 例えば-p 127.0.0.1:80:3000でローカルホストの80番ポートと仮想環境上の3000番ポートを接続します.
  • -v: バインドマウント(コンテナの外にあるデータをコンテナ内で利用できる)します. 本体のOSとの共有ディレクトリが作れるということです. 例えば元のOS上の/hogehoge/fugafugaというディレクトリと仮想環境上の/usr/appというディレクトリをバインドしたい場合は, -v /hogehoge/fugafuga:/usr/appとします.
  • -w: 起動時に開かれる作業用ディレクトリを指定できます. 指定しないとルート直下で起動します.


では, コンテナを作成してみましょう.

docker run -it --name ContennaTest -p 127.0.0.1:80:3000 -v /hogehoge/fugafuga:/usr/app -w /usr/app node:8.11.4-jessie "/bin/bash"

仮想環境が起動し, "/bin/bash"と指定したことにより bash がコマンドを待ち受ける状態になって,

root@zzzzzzzzzzzz:/usr/app#

のように表示されるでしょう. この状態でコンテナを停止するにはexitを入力します.


docker ps -aと入力することで,

CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS                     PORTS                    NAMES
cccccccccccc        node:8.11.4-jessie   "/bin/bash"         59 seconds ago      Up 24 minutes              127.0.0.1:80->3000/tcp   ContennaTest

というように, コンテナの一覧を参照できます. コンテナの起動 + 仮想環境の操作はdocker start [コンテナ名]docker exec -it [コンテナ名] "/bin/bash", コンテナの停止はdocker stop [コンテナ名], 削除はdocker rm [コンテナ名]です.

なお, docker inspect [コンテナ名]でコンテナに関する詳細な情報がJSONで表示されます.

試しに...

実際に Node.js を使ってみましょう.

docker start ContennaTest
docker exec -it ContennaTest "/bin/bash"

で, 先ほどのように

root@zzzzzzzzzzzz:/usr/app#

と表示させます.

root@zzzzzzzzzzzz:/usr/app# node -v
v8.11.4
root@zzzzzzzzzzzz:/usr/app# yarn -v
1.6.0
root@zzzzzzzzzzzz:/usr/app# npm -v
5.6.0

Dockerfile で確認した通り, Node, yarn が確かに入っています. npm も入っています.
touch test.jsと打ってみると, 仮想環境上の/usr/appディレクトリだけでなく, バインドされた元のOS上の/hogehoge/fugafugaというディレクトリにもtest.jsが作成されていることが確認できると思います. それを適当なエディタで

const http = require('http');

http.createServer(function (req, res) {
    res.writeHead(200, {
        'Content-Type': 'text/html'
    });
    res.write("<h1>Hello World</h1>");
    res.end();
}).listen(3000);

と編集し, node test.jsと入力してから, localhost:80 にアクセスすると, ローカルホストの80番ポートと仮想環境上の3000番ポートが接続されているため, きちんと表示されます. f:id:HelloRusk:20180911030438p:plain

Expressを使うことで, 簡単に Web フレームワーク を作ることもできます.

npm install express -g
npm install express-generator -g

で, expressexpress-generatorをグローバルインストールした後, express myappと打つと, myappというディレクトリが作成され, アプリケーションのひな形一式が揃います.

cd myapp
npm install
DEBUG=myapp:* npm start

で, localhost:80 にアクセスすると, Express は内部(/bin/www/)で3000番ポートを開放しているため, 同様にうまくいきます.

f:id:HelloRusk:20180911031702p:plain

Express の詳細についてはこの記事の本題から逸れるので, これ以上は立ち入りません.

おわりに

Docker で Node.js をサクッと使う方法について述べてきました. このように Docker のような手軽な仮想環境で環境構築するメリットとしては,

  • 本体のOSの動作にダメージを与える可能性のあるコマンド入力を減らせる
  • 複数のバージョンのイメージを pull することで, 異なるバージョンで実行を試せる

などでしょうか.
どんな言語・プログラムであれ, 環境構築はひとつの壁となってしまうので, 「Dockerのイメージの配布」による環境構築が標準となればもっと優しい世界になるのかな, と思います.



(9/12 追記)コンテナからイメージを作成する

公式に配布されているイメージからdocker runしてコンテナを作成し, 様々な言語の実行環境を整えたものの, docker runの際に設定すべきオプションを忘れてしまうことがあります.
例えば-e LANG=ja_JP.UTF-8環境変数を設定するのをやらなかったために, せっかく仮想環境上でapt-get install language-pack-ja-base language-pack-jaして日本語パックを入れたのに, 毎回起動時にupdate-locale LANG=ja_JP.UTF-8しないといけないなど...
そういう場合は,

docker commit [すでに利用しているコンテナ名] [新しく作成するイメージ名]

で新しくイメージを作成し, そのイメージをもとに, 再び適切なオプションを設定してdocker runするのが簡便です.