JavaScript
Node.js
nginx
docker
jsnote

docker-composeで開発したnginx+node.jsのアプリケーションをDocker Hubで配布する方法

要約

配布方法

Dockerfileをdocker-composeでビルドして, nginx由来とnode.js由来の2つのimageを作り, Docker Hubにプッシュする. docker-composeでビルドするときに, buildの中でcontextを使うと便利.

アプリケーションを作るdocker-compose.yamlの例
https://github.com/Toyoharu-Nishikawa/jsnote/tree/docker

git clone -b docker https://github.com/Toyoharu-Nishikawa/jsnote.git
docker-compose up -d

利用方法

利用者がアプリケーションを使うときは, docker-compose.yamlでDocker Hubに登録したimageをpullして使う.

アプリケーションを使うdocker-compose.yamlの例
https://github.com/Toyoharu-Nishikawa/jsnote/tree/app

git clone -b app https://github.com/Toyoharu-Nishikawa/jsnote.git
docker-compose up -d

背景

jsnoteというブラウザで動くJavaScriptのエディタを作っていた. 静的ファイルの配布はnginxで行い, データの保存はnode.jsで行うことにした.

docker-composeでnginxとnode.jsのイメージを立ち上げて, それぞれに必要なソースコードをvolumeでマウントすることでコンテナとファイルを共有し, コーディングをしていた.

Docker Hubにイメージを登録して, 誰でも簡単に使えるようにしようと思ったけれど, Docker Hubに置くimageをどう作成するべきかわからなかった.

Imgur

jsnoteについてはこちらの記事

問題

下記のようなディレクトリ構成の下, public/でフロント側の開発を行い, src/でサーバ側の開発を行っていた場合, gitで管理したまま, public/とsrcを入れたdocker imageを如何に作成するか.
(例: jsnoteの階層の下に全ファイルをがあるとする.)

./jsnote
|-- docker-compose.yaml
|-- conf.d
|   |-- server.conf
|
|-- public
|   |-- index.html
|   |-- scripts
|   |-- styles
|
|-- src
|   |-- index.js
|   |-- node_modules
|   |-- package.json
|   |-- package-lock.json

開発中のdocker-compose.yamlの内容は下記.

docker-compose.yaml
version: '2'
services:
  node:
    image: node 
    restart: always 
    volumes:
      - ./src:/usr/src
      - ./public/sample:/usr/share/nginx/html/sample
      - /var/log/jsnote:/var/log/node
    working_dir: '/usr/src'
    command: npm start
  nginx:
    image: nginx
    restart: always 
    volumes:
      - ./public:/usr/share/nginx/html
      - ./conf.d:/etc/nginx/conf.d
      - /var/log/jsnote:/var/log/nginx
    ports:
      - "2555:80"

試行

3つの方法を思いついた順に試行した.

方法1: nginxとnode.jsをひとつのdockerのimageに入れるDockerfileを作成, build.shでビルドする.

nginxもしくはnode.jsのイメージを基に, もう一つを入れる. 例えば, node.jsを基に作るとしたら次のようになる.

FROM node:alpine
以下nginx:alipineのDocker HubのDockerfileの中身をコピペ
.
.
.
RUN rm /etc/nginx/conf.d/*
ADD conf.d /etc/nginx/conf.d
ADD public /usr/share/nginx/html
ADD src/ /usr/src
CMD nginxとnode.js起動するコマンド

nginxとnode.jsの2つを起動するコマンドをどう書くべきかで悩んだ. Dockerの公式サイト Dockerのベストプラクティスを見て, 方法1を中止した.

コンテナ毎に1つのプロセスだけ実行

ほとんどの場合、1つのコンテナの中で1つのプロセスだけ実行すべきです。アプリケーションを複数のコンテナに分離することは、水平スケールやコンテナの再利用を簡単にします。サービスとサービスに依存関係がある場合は、 コンテナのリンク を使います。

方法2: nginxとnode.jsのimageからから別々のimageを作成するようにDockerfileを作成し, 双方をbuild.shでビルドする.

nginxとnode.jsを基にimageを作るDockerfileをそれぞれ用意する.

web/Dockerfile
FROM nginx:alpine
RUN rm /etc/nginx/conf.d/*
ADD conf.d /etc/nginx/conf.d
ADD public /usr/share/nginx/html
web/build.sh
build -t jsnote_web .
api/Dockerfile
FROM node:alpine
ADD  src/ /usr/src
WORKDIR /usr/src
ENTRYPOINT ["npm","start"]
api/build.sh
build -t jsnote_api .

docker-composeでまとめているメリットを活かせないことに気づき, 方法2を中止した.

方法3: 方法2をdocker-composeでまとめてビルドする.

docker-composeの中でbuildを使うことで, (2)のDockerfileをまとめてbuildできる. しかし, Dockerfileと同じディレクトリにADDするファイルを置かないといけない....

⇒対処法参照

解決法

buildの中で, contextを指定することで, 親ディレクトリのファイルをDockerfileでADDできる. ことを利用すると, 開発中のdirectory階層のままでDockerfileをビルドすることができる.

step1: webとapiのディレクトリを追加し, その中にDockerfileを置く.

docker-compose.yaml
./jsnote
|-- docker-compose.yaml
|-- web                   ← 追加
|   |-- Dockerfile        ← 追加
|
|-- api                   ← 追加
|   |-- Dockerfile        ← 追加
|
|-- conf.d
|   |-- server.conf
|
|-- public
|   |-- index.html
|   |-- scripts
|   |-- styles
|
|-- src
|   |-- index.js
|   |-- node_modules
|   |-- package.json
|   |-- package-lock.json

step2: docker-compose.yamlを下記のようにする.

docker-compose.yaml
version: '2'
services:
  api:
    build: 
      context: ./ 
      dockerfile: ./api/Dockerfile
  web:
    build:  
      context: ./ 
      dockerfile: ./web/Dockerfile 
    ports:
      - "2555:80"

step3: docker-composeでビルドする.

docker-compose build

これでdockerのimageが以下のように2つできる.

  • jsnote_web
  • jsnote_api

Docker Hubにpush

docker tagでimage名をDocker Hubに書き換えて, pushする.

step1: docker login

予めDocker Hubに作って置いたアカウントにログインする.

docker login

step2: docker tag

docker tagでDocker Hubに登録するimageの名前に変更する. この時, ユーザ名/を頭に付ける必要がある.

docker tag jsnote_web ユーザ名/jsnote_web:latest
docker tag jsnote_api ユーザ名/jsnote_api:latest

step3: docker push

docker push ユーザ名/jsnote_web:latest
docker push ユーザ名/jsnote_api:latest

以上で, Docker Hubでimageが公開され, 配布できるようになる.

以降は, 誰でもdocker pullでimageをダウンロードできる.

自分で試す場合は, docker rmiでimageを削除してから, pullできるか試す必要がある.

docker rmi ユーザ名/jsnote_web:latest
docker rmi ユーザ名/jsnote_api:latest

docker pull ユーザ名/jsnote_web_web:latest
docker pull ユーザ名/jsnote_api:latest

最後に(アプリケーションの使い方)

nginx+node.jsのアプリケーションとして使うには, docker-composeを使って, 2つをリンクさせる必要がある.

./jsnote              
|-- docker-compose.yaml
docker-compose.yaml
version: '2'
services:  
  api:
    image: ユーザ名/jsnote_api
  web:  
    image: ユーザ名/jsnote_web
    ports:
      - "80:80"

(注意)
本例のdocker-compose.yamlの場合, nginxのリバースプロキシproxy_pass(ロードバランサ―の設定はupstream)はserverとして, jsnote_api_1もしくは単にapiを参照しなくてはならない. nginxのリバースプロキシの設定はnginxの.confに書かれている. 上記 解決法 で作成したnginxのimageの中に適切なリバースプロキシの設定が書かれていない場合, 作者がconf.d/default.confの中の設定を書き直し, もう一度imageを作り直してDocker Hubにアップロードするか, アプリケーションの利用者がdocker-composeの中でvolumesで設定を上書きする必要がある.