docker-compose を使った Node.js・npm 開発環境構築例
docker-compose
を使って、Node.js・npm の開発環境を構築してみる。Docker さえインストールしてあれば、ローカルに Node.js や npm をインストールすることなく、開発が進められるようになる。
目次
- プロジェクト例
docker-compose.yaml
を作成するpackage.json
を生成する- Express のインストール
- サーバを実装して動かしてみる
- テストを書きたくなったら…?
- 以上
プロジェクト例
今回のプロジェクト例は、Express を使って簡易サーバを構築するという例だ。最終的なファイル構成は次のようになる予定。
./my-npm/
├ docker-compose.yaml
├ package.json
├ main.js (などなど)
└ node_modules/
以下の解説からは若干手直ししてあるので名前などが変わっているところもあるが、動作するプロジェクト全体を以下の GitHub リポジトリにて公開しているので、参考にされたし。
docker-compose.yaml
を作成する
それでは開発環境を構築していこう。
予め、次のような docker-compose.yaml
ファイルを作っておく。
docker-compose.yaml
image
で Node.js 公式のイメージを指定している。コレによりnode
・npm
コマンドが実行できる環境を用意しているcontainer_name
を指定しているので、docker start
などする時にコンテナ ID ではなくこのコンテナ名を指定して操作できるようになる。操作がしやすくなるstdin_open
とtty
は、起動後のコンテナを終了させないようにするため記載しているcommand
でdocker-compose up
時に実行するコマンドを定義しておく。ココではnpm start
を実行し、Express サーバを起動させるつもりvolumes
でこのプロジェクトディレクトリを/root/project
にマウントする- ホストの
~/.npmrc
をマウントしておくとnpm install
を実行する時とかに参照できるようになるので設定しておく bash
でアタッチした時の作業ディレクトリが、マウントした/root/project
になるようworking_dir
を指定しておくports
で npm が公開するポートをホスト側と紐付けておく。Express サーバが 8080 ポートを使うので、そのままホスト側のhttp://localhost:8080/
でアクセスできるようにしてある
my-npm:
image: node:latest
container_name: my-npm
stdin_open: true
tty: true
command: npm start
volumes:
- .:/root/project
- ~/.npmrc:/root/.npmrc
working_dir: /root/project
ports:
- 8080:8080
package.json
を生成する
docker-compose.yaml
ファイルを用意したので、コレを利用して npm init
を実行してみる。
$ docker-compose run --rm my-npm npm init -y
Wrote to /root/project/package.json:
{
"name": "project",
"version": "1.0.0",
"main": "main.js",
"dependencies": {
"express": "4.17.1"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"keywords": [],
"description": ""
}
docker-compose run
は、コンテナを1度だけ生成して処理を実行するためのコマンド- 実行後にコンテナを破棄するため
--rm
オプションを付けている。付けないで実行した場合は、$ docker ps -a
で一覧を見た時に、終了したコンテナとしてゴミが残る。後で消すこともできるので、付け忘れても気にしない docker-compose.yaml
に記載されているmy-npm
というサービスを起動している- コマンド末尾の
npm init -y
部分が実行したいコマンド。-y
オプションを付けたが、付けずに対話形式でpackage.json
を生成しても良い。
コレでプロジェクトディレクトリ直下に package.json
が生成できたはずだ。
Express のインストール
続いて Express をインストールし、package.json
に記載してみる。
$ docker-compose run --rm my-npm npm install -S express
+ express@4.17.1
updated 1 package and audited 126 packages in 4.677s
found 0 vulnerabilities
npm install
もコンテナ経由で実行する。プロジェクトディレクトリがそのままマウントしてあるので、package.json
の dependencies
に "express": "4.17.1"
が追記されるというワケ。
このように、npm
コマンドを実行したい場合は docker-compose run
を使用することで、ローカルに npm
コマンドをインストールした時と同様に作業できる。
サーバを実装して動かしてみる
それでは、Express を使った簡単なサーバを構築してみる。ホスト OS 側で良いので、VSCode などを使って次のようなファイルを実装する。
main.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
console.log(new Date().toISOString(), '[/]');
res.send('Hello World');
});
app.listen(8080, () => {
console.log('Server Started');
});
ファイルの準備ができたら、サーバを起動して動作確認してみよう。ココで docker-compose up
を使うことになる。 - サーバを起動して動作確認する
$ docker-compose up
# こんな風にログが見える
Creating my-npm ... done
Attaching to my-npm
my-npm |
my-npm | > docker-node@0.0.0 start /root/project
my-npm | > node main.js
my-npm |
my-npm | Server Started
# ホスト側のブラウザで http://localhost:8080/ にアクセスすると、以下のようにログが出力される
my-npm | 2019-11-18T01:43:56.567Z [/]
# 終了する場合は Ctrl + C で切る
Gracefully stopping... (press Ctrl+C again to force)
Killing my-npm ... done
サーバログは見えなくて良い、という場合はバックグラウンド起動すれば良い。
# --detach は -d と記載可能
$ docker-compose up --detach
Creating my-npm ... done
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7ef4b7941a9d node:latest "docker-entrypoint.s…" 17 seconds ago Up 16 seconds 0.0.0.0:8080->8080/tcp my-npm
この状態でもホストのブラウザで http://localhost:8080/
にアクセスすると、この Docker コンテナと通信が行われる。
このようにバックグラウンド起動しているコンテナのサーバログを見るには、次のようにアタッチする。
$ docker attach my-npm
# このあとホストのブラウザでアクセスしてみると、サーバログが出力される
2019-11-18T01:50:41.509Z [/]
# プロセスを終了せずに抜けるには Ctrl + P → Ctrl + Q と入力する
# Ctrl + C で終了してしまうとプロセスが終了してしまう
Ctrl + C
で終了したりした場合は、プロセスは次のように Exited
というステータスになっているかと思う。
$ docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a7b6ba061fd4 node:latest "docker-entrypoint.s…" 6 seconds ago Exited (0) 1 second ago my-npm
終了させてしまったプロセスを再起動するには、docker start
か docker-compose start
を使う。
# もしくは `docker start my-npm` でも良い
$ docker-compose start my-npm
Starting my-npm ... done
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7ef4b7941a9d node:latest "docker-entrypoint.s…" 3 minutes ago Up 1 second 0.0.0.0:8080->8080/tcp my-npm
docker-compose up
で起動したサービスを停止・破棄するにはこのようにする。
# オプションは -sf と省略可能
$ docker-compose rm --stop --force
Stopping my-npm ... done
Going to remove my-npm
Removing my-npm ... done
コードの編集はホスト側でやればいいし、npm
コマンドを実行する場合も docker-compose run
を使えば良いが、もしコンテナの Bash セッションに接続したい場合は、次のように叩く。
# サービスとして起動し、そのコンテナに接続する場合
$ docker-compose up -d
$ docker exec -it my-npm bash
# とにかく Bash 環境に入る場合
$ docker-compos run --rm my-npm bash
node:latest
は Debian 9 (Stretch) ベースなので、bash
は勿論、基本的なコマンドは動作するので、必要な場合はこのように実行すれば良い。
テストを書きたくなったら…?
ユニットテストを書きたくなったとしたら、例えば次のように操作すれば良い。
# mocha をインストールして devDependencies に追記させる
$ docker-compose run --rm my-npm npm install -D mocha
# ホスト OS で良いので、テストコードを書く
$ vi test.js
# package.json の npm-scripts に test 項目を記述する
$ vi package.json
# テストを実行する
$ docker-compose run --rm my-npm npm test
やはり docker-compose run
を使う。
ココまで多用すると docker-compose
という文字数が多くて大変なので、自分はこんなエイリアスを作って ~/.bashrc
に記載している。
alias d='docker'
alias da='docker attach'
alias db='docker build --no-cache --tag'
alias di='docker images'
alias dit='docker image tag'
alias dpl='docker pull'
alias dps='docker ps -a'
alias dpush='docker push'
alias dr='docker run -it' # ex. docker run -it -v "$(pwd):/tmp/shared" -p 8080:8080 【Image Name】 bash
alias drm='docker rm -f'
alias drma='docker rm -f $(docker ps -aq)'
alias drmi='docker rmi'
alias drun='docker run -it'
alias ds='docker start'
alias dsta='docker start'
alias dsto='docker stop'
# 対象コンテナが止まっていても強制的に docker exec する
function de() {
if [ "$#" -eq 0 ]; then
echo '[de : docker exec function] Requires at least 1 argument.'
return 1
fi
docker start "$1" > /dev/null
if [ "$#" -eq 1 ]; then
docker exec -it "$1" 'bash'
else
docker exec -it "$@" # ex. 【Container ID or Container Name】 bash
fi
}
alias dc='docker-compose'
alias dcb='docker-compose build --no-cache'
alias dcup='docker-compose up'
alias dcupd='docker-compose up -d'
alias dcr='docker-compose run --rm'
alias dcrm='docker-compose rm -sf'
alias dcrun='docker-compose run --rm'
alias dcsta='docker-compose start'
alias dcsto='docker-compose stop'
このようにエイリアスを作っておけば、
$ dcupd
で起動〜$ da my-npm
でアタッチ〜$ dcr my-npm npm test
でテストして〜$ de my-npm
で Bash 接続〜$ dcrm
で全サービス終了して〜$ drma
で停止したコンテナ含めて全コンテナ破棄〜
という感じで操作できるようになる。
以上
共有ディレクトリとしてマウントしたプロジェクトディレクトリ配下に node_modules/
などが格納されているので、コンテナはいくら生成・破棄してもそれを見てくれる。ホスト OS に Node.js や npm がインストールしてある必要はなく、ベースイメージで指定した Node.js のバージョンをそのまま利用できるので、環境を汚さずに済む。
docker-compose
を使うと、素の docker run
で煩雑になるオプション引数を設定ファイルに書いておけるようになるので、今回のように使用するコンテナが1つの場合でも、上手く活用してあげると良いだろう。
今回は Node.js を例に挙げたが、同じ考え方で他の言語の開発環境も用意できるだろう。