Corredor

ウェブ、プログラミングの勉強メモ。

2020-11-28 : このブログは2020年末をもって更新停止する予定です。
2021年以降は Neo's World (https://neos21.net/) で記事を公開していきますので、今後はコチラをご覧ください。
このブログの記事は2021年以降、Neo's World に順次移行していきます。元記事および本ブログは移行次第削除する予定です。

docker-compose を使った Node.js・npm 開発環境構築例

docker-compose を使って、Node.js・npm の開発環境を構築してみる。Docker さえインストールしてあれば、ローカルに Node.js や npm をインストールすることなく、開発が進められるようになる。

プロジェクト例

今回のプロジェクト例は、Express を使って簡易サーバを構築するという例だ。最終的なファイル構成は次のようになる予定。

./my-npm/
├ docker-compose.yaml
├ package.json
├ main.js (などなど)
└ node_modules/

以下の解説からは若干手直ししてあるので名前などが変わっているところもあるが、動作するプロジェクト全体を以下の GitHub リポジトリにて公開しているので、参考にされたし。

github.com

docker-compose.yaml を作成する

それでは開発環境を構築していこう。

予め、次のような docker-compose.yaml ファイルを作っておく。

  • docker-compose.yaml
    • image で Node.js 公式のイメージを指定している。コレにより nodenpm コマンドが実行できる環境を用意している
    • container_name を指定しているので、docker start などする時にコンテナ ID ではなくこのコンテナ名を指定して操作できるようになる。操作がしやすくなる
    • stdin_opentty は、起動後のコンテナを終了させないようにするため記載している
    • commanddocker-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.jsondependencies"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 startdocker-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 を例に挙げたが、同じ考え方で他の言語の開発環境も用意できるだろう。

Dockerによるアプリケーション開発環境構築ガイド(リフロー版)

Dockerによるアプリケーション開発環境構築ガイド(リフロー版)