logo
FluctCube 技術ブログ

任意のバージョンの Node.js Docker 環境をコマンド1つで用意する

作成 2023/05/27
更新 2023/05/28
#nodejs#docker

Node.js を Docker 環境で動作させる方法は数多くありますが、その中でも一行のコマンドで利用できるコマンド例を紹介します。 ホスト側にあるファイルをコンテナと共有し、Node.js プログラムの実行はコンテナ内で行います。 制約はありますが、ホスト環境を汚さず手軽に実行できます。

コマンド例

まずはコマンド例です。(Linux では実行ユーザーを調整する必要があるため -u オプションを追加しています。)

ホスト側とコンテナでファイルを共有したいディレクトリにて以下のコマンドを実行します。

Linux, WSL

docker run --rm -it -v "$PWD":/home/node/app -w /home/node/app -u $(id -u):$(id -g) node:18.16.0-alpine sh

Mac

docker run --rm -it -v "$PWD":/home/node/app -w /home/node/app node:18.16.0-alpine sh

ここでは 18.16.0-alpine のイメージを使用していますが、使用可能なタグは Docker Hub から確認できますので、任意のイメージを指定して起動してください。

起動するとシェルが実行されます。 docker コマンドを実行したディレクトリをコンテナ内の /home/node/app というディレクトリにバインドマウントしているため、コンテナ内からホスト側のファイルを操作(読み書き)できます。 終了する場合は、 exit を入力します。 シェルを抜けるとコンテナは停止&終了し、自動的に破棄されます。

引数の説明

引数説明
--rmコンテナが停止したときに自動的に削除される。
-it-i: STDIN が接続されていなくても、STDIN を開いたままにする。 -t: 擬似 TTY を割り当てる。
-vボリュームをバインドマウントする。
-wコンテナ内の作業ディレクトリ
-uユーザー名または UID (フォーマット: <name or uid>[:<group or gid>] )

参考

-it

-it は、コンテナの標準入力に接続された擬似 TTY を割り当てるよう Docker に指示し、コンテナ内に対話型の sh シェルを作成します。これによりコンテナ起動後、ホスト側のターミナルとコンテナ内でシェルの通信(文字の入出力)が可能となり、コマンドを入力したり結果を表示したりできます。

This example runs a container named test using the debian:latest image. The -it instructs Docker to allocate a pseudo-TTY connected to the container’s stdin; creating an interactive bash shell in the container. The example quits the bash shell by entering exit 13, passing the exit code on to the caller of docker run, and recording it in the test container’s metadata. 引用: Assign name and allocate pseudo-TTY (--name, -it)🔗 | docker run | Docker Documentation

-v, -w

-v でホスト側のディレクトリとコンテナ内のディレクトリをバインドマウントします。 -v ホスト側のパス:コンテナ側のパス の形式で指定しますが、ホスト側は $PWD 変数を使用してカレントディレクトリを指定しています。 コンテナ側は自由ですが、 node イメージを使用すると node というユーザーとそのユーザーディレクトリがあるので、配下に app というディレクトリを作成(バインドマウントにて自動的に作成)し、そこをワーキングディレクトリとしてみました。

ポートの使用

ポートを使用するには -p オプションを使用します。 例えばポート 3000 のトラフィックをコンテナに流すには以下のようにします。

Linux, WSL

docker run --rm -it -v "$PWD":/home/node/app -w /home/node/app -p 3000:3000 -u $(id -u):$(id -g) node:18.16.0-alpine sh

Mac

docker run --rm -it -v "$PWD":/home/node/app -w /home/node/app -p 3000:3000 node:18.16.0-alpine sh

また、一例ですがポート 3000 番を Listen するサーバー (server.js) に対して以下のように内部プログラムを変更することなく同一のサーバーを複数起動することも簡単にできます。 (localhost からは 3000 と 3001 にアクセスします。)

# シェル1
$ docker run --rm -it -v "$PWD":/home/node/app -w /home/node/app -p 3000:3000 node:18.16.0-alpine node server.js

# シェル2
$ docker run --rm -it -v "$PWD":/home/node/app -w /home/node/app -p 3001:3000 node:18.16.0-alpine node server.js

制約

動作的には当然ですが、 exit でコンテナ環境を抜けるとコンテナは終了します。バインドマウントしたディレクトリはホスト側に残りますが、それ以外のでディレクトリに作成されたファイルは削除されます。

なお、Linux で実行する際 npm install -g nodemon 等のグローバルインストールは root のアクセス権がないためできません。 Mac の場合は install することは可能ですが、コンテナを終了するとグローバルインストールしたパッケージは、環境ごとすべて削除されます。

Linux で実行する場合の注意

Linux 環境で実行する場合は、コンテナ側のユーザーをホスト側のユーザー ID と一致させることをおすすめします。 これは -u $(id -u):$(id -g) オプションを使用することで回避(一致)できます。

例えば -u オプションなしで実行すると、コンテナ内のユーザーは root で実行されますが、この状態でコンテナ内からファイルを作成する(例: touch sample.txt)と、ホスト側でも root ユーザーで作成したことになります。

ホスト側がユーザーレベルのディレクトリの場合では、ファイルの権限(オーナー)が root ユーザーであると、ホスト側からファイルを編集・削除するには root 権限が必要になります。

root ユーザーが所有者のファイルやディレクトリは、通常ユーザーでは操作時に権限不足 (Permission denied.) となり不便であったり、予期せぬエラーが発生する可能性があるため、コンテナ内で実行するユーザーはホストと一致させておく必要があります。

Mac の場合はこのあたりの権限処理は適当に行われるようで、手元で検証する限り逆に -u オプションは使用しないほうが良さそうです。

WSL 環境で Ubuntu を使用している場合、初期で生成されるユーザー ID は 1000 ですので、すでにわかっている場合は以下のように直接書くこともあります。

WSL(Ubuntu)

docker run --rm -it -v "$PWD":/home/node/app -w /home/node/app -u 1000:1000 node:18.16.0-alpine sh

ユーザーをホスト側に合わせる必要があるかどうか

「ユーザーをホスト側に合わせる必要があるかどうか」というのは、「コンテナ内からバインドマウントしたディレクトリに対してファイルを作成することがあるかどうか」という観点で考えられます。

Node.js の場合は node_modules ディレクトリのキャッシュやログ等、コンテナ内からバインドマウントしたディレクトリを操作することは比較的多くあると考えます。

簡単な例では、コンテナ内から npm install でパッケージをインストールする場合です。

# Linux で -u なしで実行する場合
$ docker run --rm -it -v "$PWD":/home/node/app -w /home/node/app node:18.16.0-alpine sh

## コンテナ内
/home/node/app $ id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

/home/node/app $ npm init
(snip)
Is this OK? (yes)

/home/node/app $ npm install express
added 58 packages, and audited 59 packages in 2s
8 packages are looking for funding
  run `npm fund` for details
found 0 vulnerabilities

上記実行後、コンテナ側・ホスト側でファイルを確認するとファイルやディレクトリの所有者が root であることがわかります。

コンテナ側

/home/node/app $ ls -l
total 32
drwxr-xr-x   60 root     root          4096 May 28 05:36 node_modules
-rw-r--r--    1 root     root         22278 May 28 05:36 package-lock.json
-rw-r--r--    1 root     root           249 May 28 05:36 package.json

ホスト側 (Ubuntu)

$ ls -l
total 32
drwxr-xr-x 60 root root  4096 May 28 14:36 node_modules
-rw-r--r--  1 root root 22278 May 28 14:36 package-lock.json
-rw-r--r--  1 root root   249 May 28 14:36 package.json

上記の状況で、仮にユーザーレベルをあわせたコンテナを実行し node_modulespackage.json を操作するようなコマンドを実行すると権限不足のエラーが発生します。(ホスト側から操作しても同様です。)

# Linux で -u ありで改めて実行する
$ docker run --rm -it -v "$PWD":/home/node/app -w /home/node/app -u 1000:1000 node:18.16.0-alpine sh

# コンテナ内
~/app $ id
uid=1000(node) gid=1000(node) groups=1000(node)

~/app $ npm uninstall express
npm ERR! code EACCES
npm ERR! syscall rename
npm ERR! path /home/node/app/node_modules/accepts
npm ERR! dest /home/node/app/node_modules/.accepts-KuGSsgov
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, rename '/home/node/app/node_modules/accepts' -> '/home/node/app/node_modules/.accepts-KuGSsgov'
npm ERR!  [Error: EACCES: permission denied, rename '/home/node/app/node_modules/accepts' -> '/home/node/app/node_modules/.accepts-KuGSsgov'] {
npm ERR!   errno: -13,
npm ERR!   code: 'EACCES',
npm ERR!   syscall: 'rename',
npm ERR!   path: '/home/node/app/node_modules/accepts',
npm ERR!   dest: '/home/node/app/node_modules/.accepts-KuGSsgov'
npm ERR! }
npm ERR!
npm ERR! The operation was rejected by your operating system.
npm ERR! It is likely you do not have the permissions to access this file as the current user
npm ERR!
npm ERR! If you believe this might be a permissions issue, please double-check the
npm ERR! permissions of the file and its containing directories, or try running
npm ERR! the command again as root/Administrator.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/node/.npm/_logs/2023-05-28T05_45_45_949Z-debug-0.log

所有者が root のファイルはユーザーは操作できないため、この様になると該当のファイルを削除して作り直すか、以下のように chown コマンドを使用して root 権限で所有者をユーザーに変更します。

ホスト側(WSL Ubuntu の例)

$ ls -la
total 40
drwxr-xr-x  3 ubuntu ubuntu  4096 May 28 14:36 .
drwxr-xr-x  4 ubuntu ubuntu  4096 May 28 11:05 ..
drwxr-xr-x 60 root   root    4096 May 28 14:36 node_modules
-rw-r--r--  1 root   root   22278 May 28 14:36 package-lock.json
-rw-r--r--  1 root   root     249 May 28 14:36 package.json

# ユーザーIDを確認する。
$ id -u
1000

$ id -g
1000

# カレントディレクトリ配下のすべてのファイル・ディレクトリの所有者を 1000:1000 に変更する。
$ sudo chown -R 1000:1000 ./*

$ ls -la
total 40
drwxr-xr-x  3 ubuntu ubuntu  4096 May 28 14:36 .
drwxr-xr-x  4 ubuntu ubuntu  4096 May 28 11:05 ..
drwxr-xr-x 60 ubuntu ubuntu  4096 May 28 14:36 node_modules
-rw-r--r--  1 ubuntu ubuntu 22278 May 28 14:36 package-lock.json
-rw-r--r--  1 ubuntu ubuntu   249 May 28 14:36 package.json

以上です。

Linux でバインドマウントを使用するとファイルの所有者に関して考えることが多いですが、慣れると便利ですのでご活用ください。

© FluctCube