dockerとdocker-composeを使ってnode.jsのHello Worldを動かす

# はじめに

Hello Worldを表示するnode.js(ただのJavascriptですが)のプログラムを作成し、それをdocker + docker-composeで立ち上げるまでの手順を説明します。諸々考え方が間違っているところも多いかと思いますがご容赦ください。
以下のような方を対象としています。

* 今さらdockerとかdocker-composeなんて人に聞けねーよ、という方
* 今さらnode.jsなんて人に聞けねーよ、という方

一式[github](https://github.com/amama-nagigi/node-docker-helloworld)に上げました。

## 話の流れ

1. dockerの概要説明
2. docker-composeの概要、サンプル説明
3. node.jsのhello-world説明
4. コンテナとして起動する部分の説明

## 環境

* 開発環境はWindows10
* Hyper-VでDebian9を立ち上げ、dockerホストとして使用([こちら](https://qiita.com/amama-nagigi/items/9a80484da7daf392a6d7)参照)
* IDEはVisualStudioCodeを使用

## フォルダ構成

```
node-docker-helloworld
│  docker-compose.yml
└─src
        index.js
        package.json
```

# docker

dockerとは仮想環境の一種で、アプリケーションをコンテナ化して起動できます。
アプリケーションを直接サーバーにインストールする場合と比較して以下のような利点があります。

* バージョン違いや、他のアプリケーションと競合しない
* 別のサーバーに配置するときなど、毎回アプリケーションのインストールを行う必要がない

例えばPHPのWebアプリをApacheを使って動かすときなど、

* Apacheがうまくインストールできない
* 既にApacheで別のWebアプリが動いてるから別エイリアスにしたいけど設定がわからん
* 他バージョンのPHPが入ってて困る

みたいなことに毎回悩まされませんか？dockerは1つ1つのアプリがコンテナとして独立しているので、上記のような心配事が緩和されます！
ぶっちゃけ、とりあえず最初は「超軽い仮想マシン」くらいの認識でも大丈夫です。

## 用語について

### コンテナ

dockerを使って起動した仮想環境を指します。

### イメージ

コンテナの元となるものです。
コンテナがクッキーならイメージが金型みたいな感じ？どこかで聞いたような…
dockerhubから持って来たり、後述するDockerfileを元に作成したりします。今回は前者を使います。

なお、Dockerfileを使う場合でも元となるイメージをdockerhubから取得する必要があります。つまり、コンテナを初回起動するタイミング(イメージを初回作成するタイミング)では、**ホストがインターネットに接続されている必要があります。**

### Dockerfile

簡単に言うと「イメージを作成するための手順書」みたいなもんです。
元となるイメージと、そこからapt-getでなんか入れたりファイルコピーしたりコマンド実行したりして新しいイメージを作成するための手順が記載されています。例えば、公式のphpイメージにもろもろ追加モジュールを入れたPHPイメージを作成するためのDockerfileは以下のようになります。

```dockerfile
FROM php:apache
COPY php.ini /usr/local/etc/php/
RUN apt-get update \
  && apt-get install -y libfreetype6-dev libjpeg62-turbo-dev libpng-dev libmcrypt-dev \
  && docker-php-ext-install pdo_mysql mysqli mbstring gd iconv \
  && pecl install mcrypt-1.0.1 \
  && docker-php-ext-enable mcrypt
```

1. 1行目で元となるイメージを決めています。ここに書いたものを、初回起動時にdockerhubから引っ張ってきます。今回の例だと[ここ](https://hub.docker.com/_/php/)。
2. 2行目では、COPYコマンドでphp.iniをコピーしています。php.iniをDockerfileと同じ場所に置いておいて、それをイメージ内の`/usr/local/etc/php/`にコピーする、という意味です。
3. 3行目で諸々のモジュールを入れてます。RUNコマンドで、Linuxコマンドを実行することができます。

いうて今回やりたいことは公式イメージで十分そうなので、Dockerfileは使いません :P

# docker-compose

複数のdockerコンテナをまとめて起動したり停止したり、起動時の設定とかを記載できる仕組みです。

## docker-compose.yml

docker-composeの設定を記載するファイルです。今回の例を使い手っ取り早く説明します。

```yml:docker-compose.yml
version: '2'
services:
  node:
    image: library/node
    container_name: node-helloworld
    volumes:
      - ./src:/src
    working_dir: "/src"
    command: "node index.js"
```

1. 1行目はおまじないです。
2. 2行目に`services`という記載があります。ここに複数のサービス(コンテナ起動設定)を記載することが出来ます。
3. 3行目の`node`が実際に起動させるコンテナの設定です。
4. 4行目でコンテナの金型となるイメージを決めています。今回はdockerhubから持ってきていますが、Dockerfileを基にする場合は`image`ではなく`build`となります。
5. 5行目でコンテナの名前を指定してます。省略可。
6. 6～7行目の`volumes`で、ホストのフォルダとコンテナのフォルダをマッピングしています。この例で行くと、「ホストの`./src`ってフォルダが、あたかもコンテナ内の`/src`にあるように見せかけてコンテナ起動するよ」という意味です。
7. 8行目で作業フォルダを指定しています。
8. 9行目で「コンテナ起動時に実行するコマンド」を指定しています。

そいじゃ、次はsrcフォルダの中に置くhelloworldについて説明します。

# node.js

## インストール

とにかくインストールが必要です。[node.jsの公式ページ](https://nodejs.org/ja/)からインストーラーを落とすか、[nodist](https://github.com/marcelklehr/nodist)等のバージョン管理ツールでインストールしましょう。インストール確認をお忘れなく。

```shell
> node -v
v8.4.0
```

OKそうなら次に進みます。

最近はもはや、インストールしたプログラムの実態がどこにあるかとか気にしないのが普通なようですね。慣れるまで非常に気持ち悪かったですが、慣れると「俺の端末のどこかにnodeとかいうプログラムがインストールされて、パスが通ってるっぽい」以上のことは何も考えなくなりました。

## フォルダの初期化

node.jsは、フォルダ自体がプロジェクトになるんですよねー。普通にコマンドプロンプトを開いてフォルダに行き、`npm init`コマンドを叩きます。

```shell
> cd src
src> npm init
```

npmはnodeと一緒にインストールされる、nodeのパッケージ管理ツールです。initコマンドを叩くと、nodeのプロジェクト設定ファイルとも言える`package.json`が作成されます。
対話側でいろいろ聞かれますが適当に入れちゃってください。Enter連打でも多分いけます。私の場合は以下のようなファイルが出来ました。

```json:package.json
{
  "name": "node-helloworld",
  "version": "1.0.0",
  "description": "node.jsのプログラムをdocker-composeで起動する用のサンプル",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
```

## index.jsの作成

ではプログラムの実態となる`index.js`を作成します。

```js:index.js
console.log("hello.world");
```

だってhello-worldだもん…
ちなみに、vscodeで開いてF5押せばデバッグが可能です。nodeが入ってるからかしら？単純なjavascriptのテストをしたいだけなら、これだけで十分そうですね。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/174324/97c9ef93-4938-d37b-ca45-def99ce75857.png)

# サーバーに配置

これで一通りのファイルが出来たので、仕上げです。dockerのホストにファイルをコピーし、コンテナを起動してみましょう。

## ホストへのインストール

### docker

[こちら](https://qiita.com/amama-nagigi/items/e4f89468c02200d3b94d)にも記載しましたが改めて。[公式](https://docs.docker.com/install/linux/docker-ce/debian/)のコピペです。

```sh
$ sudo apt-get update
$ sudo apt-get install \
     apt-transport-https \
     ca-certificates \
     curl \
     gnupg2 \
     software-properties-common
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce
```

### docker-compose

こちらも[公式](https://docs.docker.com/compose/install/)のコピペです。

```sh
$ sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
```

## コンテナの実行

### ファイルの配置

WinSCPを使い、`/home/amama/docker-compose/node-docker-helloworld`にファイルを配置。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/174324/d665da20-4791-e314-601d-e53d07cec0e7.png)

### 実行

ターミナルで接続し、`docker-compose up`コマンドを実行しコンテナを起動します。
最初はイメージの作成から始まり、それが終わるとコンテナが起動し、HelloWorldを出力して終了します。

```sh
amama@debian:~/docker-compose/node-docker-helloworld$ sudo docker-compose up -d
Creating network "node-docker-helloworld_default" with the default driver
Pulling node (library/node:)...
latest: Pulling from library/node
1c7fe136a31e: Pull complete
...
Creating node-helloworld ... done
Attaching to node-helloworld
node-helloworld | hello.world
node-helloworld exited with code 0
```

出来ました。

# 最後に

ミニマムコードで試したつもりですがずいぶんと長くなってしまいました。
次回は、まずはnode.jsでやりたいことがあるのでそちらを詰めます。dockerやdocker-composeの掘り下げはまた別の機会に。