あいつの日誌β

働きながら旅しています。

Nginx + Node.js + React.js. + Mongodb を Docker で構築する

あらすじ

Nginx + Node.js + React.js + Mongodb で Web Application の構築手順を説明する必要があるのですが、Dockerfile をドキュメントとして扱う事になりました。

% docker --version
Docker version 17.06.2-ce, build cec0b72

% node -v
v8.1.0

% create-react-app --version
1.4.0

% mongo --version | head -n 1                                                                           MongoDB shell version v3.4.9

準備

一応 Todos アプリのような感じの Web API を backend で用意して React がそのデータを描画する、までの簡単なサンプルアプリを作成しつつ Docker について説明します。

先ずは以下のコマンドを実行して雛形を作っておきます。

cd 
mkdir practice-docker-provision && cd $_
mkdir backend mongodb mongo_seed nginx
create-react-app front
touch docker-compose.yml

mongodb

create docker-dompose.yml

version: "3"
services:
  mongodb:
    image: mongo:latest
    environment:
      - MONGO_DATA_DIR=/data/db
      - MONGO_LOG_DIR=/dev/null
    ports:
      - 27017:27017
    command:
      - mongod

docker-compose run --build を実行して mongod を起動します。起動したらアクセスできる事を確認します。

% mongo --quiet                                                                                                                   > show dbs
admin  0.000GB
local  0.000GB
> exit

mongo_seed

次に mongod を起動する度に database へ初期データを import するようにします。

mongod を起動した後、mongod へ初期データをインサートします。

mkdir mongo_seed/json
touch mongo_seed/Dockerfile mongo_seed/json/todos.json

create mongo_seed/json/todos.json

[
  { title: "todo 1", done: false},
  { title: "todo 2", done: false},
  { title: "todo 3", done: false}
]

create mongo_seed/Dockerfile

FROM mongo:latest

COPY json json

CMD mongoimport --host mongodb --db myapp --collection todos --drop --jsonArray --file ./json/todos.json

edit docker-compose.yml

     command:
       - mongod
+
+  mongo_seed:
+    build: mongo_seed
+    links:
+      - mongodb
+    depends_on:
+      - mongodb

docker-compose up --build を実行してから動作確認をします。

% mongo --quiet
> show dbs
admin  0.000GB
local  0.000GB
myapp  0.000GB
> use myapp
switched to db myapp
> db.todos.find()
{ "_id" : ObjectId("59d1fd6e73e8aa5ae594a04c"), "title" : "todo 1", "done" : false }
{ "_id" : ObjectId("59d1fd6e73e8aa5ae594a04d"), "title" : "todo 3", "done" : false }
{ "_id" : ObjectId("59d1fd6e73e8aa5ae594a04e"), "title" : "todo 2", "done" : false }
> exit
%

Node.js

cd ~/practice-docker-provision/backend
yarn init -y
yarn add express mongoose --save
touch app.js models.js

create app.js

var express = require('express');
var app = express();
var mongoose = require('mongoose');
var databaseUrl = process.env.MONGO_DATABASE || "mongodb://localhost/myapp"
var Todo = require('./models').Todo;

mongoose.connect(databaseUrl, {useMongoClient: true});

app.get('/api/todos', function(req, res) {
  Todo.find().exec((err, todos) => {
    if (err) {
      res.send(err)
      return
    }   
    res.json(todos)
  })  
});

app.listen(3000);

edit models.js

var mongoose = require('mongoose');

const Todo = mongoose.model('Todo', {
  title: {
    type: String,
    default: "", 
  },  
  done: {
    type: Boolean,
    default: false
  }
});

module.exports = { Todo: Todo }

edit backend/Dockerfile

FROM node:8

WORKDIR /usr/src/app

COPY package.json .
RUN yarn install

COPY . .

edit package.json

{
  "name": "backend",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "node app.js"
  },  
  "dependencies": {
    "express": "^4.16.1",
    "mongoose": "^4.11.14"
  }
}

edit docker-compose.yml

version: "3"
services:
  mongodb:
    image: mongo:latest
    environment:
      - MONGO_DATA_DIR=/data/db
      - MONGO_LOG_DIR=/dev/null
    ports:
      - 27017:27017
    command:
      - mongod

  mongo_seed:
    build: mongo_seed
    links:
      - mongodb
    depends_on:
      - mongodb

  backend:
    build: "backend"
    environment:
      - NODE_ENV=production
      - MONGO_DATABASE=mongodb://mongodb/myapp
    ports:
      - "3000:3000"
    links:
      - mongodb
    depends_on:
      - mongodb

docker-compose up --build を実行してから動作確認をします。

% curl -s http://localhost:3000/api/todos | jq .                                                 [
  {
    "_id": "59d201da7e61015c1651b11f",
    "done": false,
    "title": "todo 1"
  },
  {
    "_id": "59d201da7e61015c1651b120",
    "done": false,
    "title": "todo 3"
  },
  {
    "_id": "59d201da7e61015c1651b121",
    "done": false,
    "title": "todo 2"
  }
]

React.js + Nginx

React.js を build します。

cd practice-docker-provision/front
yarn run build

front/build にファイルが生成されている事を確認して下さい。

次に Nginx の config と Dockerfile を準備します。

create nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    # include /etc/nginx/conf.d/*.conf;

    server {
      listen 80;

      # server_name localhost;

      gzip on;
      gzip_types *;

      location /api/ {
        proxy_pass http://backend:3000/api/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection ‘upgrade’;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
      }

      location / {
        root /app;
        index index.html;
        try_files $uri $uri/ /index.html;
      }

   }
}

create nginx/Dockerfile

FROM nginx:1.13.0

RUN mkdir /app

COPY ./nginx.conf /etc/nginx/nginx.conf

CMD ["nginx", "-g", "daemon off;"]

edit docker-compose.yml

       - mongodb
     depends_on:
       - mongodb
+
+  nginx:
+    build: "nginx"
+    ports:
+      - "8080:80"
+    volumes:
+      - ./front/build:/app:ro

React.js から backend の API への通信をする

すでに Docker の設定は完了していますが、最後に React.js を修正します

cd ~/practice-docker-provision/front
yarn add axios --save

edit src/App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import axios from "axios";

class App extends Component {

  constructor() {
    super();
    this.state = {
      todos: []
    };
  }

  async componentDidMount() {
    const res = await axios.get("/api/todos")
    this.setState({todos: res.data || []})
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <ul>
        {this.state.todos.map((todo, index) => (
          <li key={index}>{todo.title}</li>
        ))}
        </ul>
      </div>
    );
  }
}

export default App;

再度 yarn build して動作確認で終了です。