takaya030の備忘録

PHP、Laravel、Docker などの話がメインです

Docker で Node.js + Express の開発環境を作成する

VirtualBox + Docker 環境で Node.js + Express を動作させたときの手順メモ

検証環境

Windows10 Home Edition
VirtualBox 5.2.22
Docker version 18.05.0-ce, build f150324
docker-machine version 0.14.0, build 89b8332
docker-compose version 1.20.1, build 5d8c71b

パッケージインストールのためのイメージ作成

VirtualBox の共有フォルダ内にパッケージをインストールしようとすると、シンボリックリンクが作成できずエラーになるため Docker イメージ内にインストールする

node.Dockerfile

FROM node:8-alpine
MAINTAINER takaya030

ENV HOME=/home/node

RUN mkdir $HOME/app
RUN chown -R node:node $HOME/*

USER node
WORKDIR $HOME/app

RUN yarn add express && \
    yarn add @types/express \
        typescript ts-loader tslint tslint-loader tslint-config-airbnb \
        webpack webpack-cli \
        webpack-node-externals \
        --dev

docker-compose.yml

version: "3"
services:
  node:
    build:
      context: .
      dockerfile: ./node.Dockerfile
    image: takaya030/node:8-alpine

イメージのビルド

$ docker-compose build node

作成したイメージから package.jsonyarn.lock を抽出

イメージからコンテナを作成し、 docker cp でホストOS側にコピーする

$ docker run --name temp-node takaya030/node:8-alpine /bin/true
$ docker cp temp-node:/home/node/app/package.json ./package.json
$ docker cp temp-node:/home/node/app/yarn.lock ./yarn.lock

node.Dockerfile の変更

インストールされるパッケージのバージョンを固定するため node.Dockerfile を変更する

--- a/node.Dockerfile   Sun Feb 10 18:26:36 2019
+++ b/node.Dockerfile   Sun Feb 10 19:20:49 2019
@@ -4,14 +4,10 @@
 ENV HOME=/home/node

 RUN mkdir $HOME/app
+COPY package.json yarn.lock $HOME/app/
 RUN chown -R node:node $HOME/*

 USER node
 WORKDIR $HOME/app

-RUN yarn add express && \
-       yarn add @types/express \
-               typescript ts-loader tslint tslint-loader tslint-config-airbnb \
-               webpack webpack-cli \
-               webpack-node-externals \
-               --dev
+RUN yarn install
+
+ENV PATH=$PATH:./node_modules/.bin

変更後の node.Dockerfile でイメージビルドできるか確認

$ docker-compose build node

各種設定ファイル

以下の各ファイルはこちらのサイトから引用させて頂きました qiita.com

webpack.config.dev.js

const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
    mode: 'development',
    entry: './src/server.ts',     // src下に書いていくので src/server.tsにしとく
    target: 'node',               // Module not found: Error: Can't resolve 'fs'とかいっぱい出たら、この行書き忘れ
    externals: [nodeExternals()], 
    devtool: 'inline-source-map',
    module: {
        rules: [
            {
                enforce: 'pre',
                loader: 'tslint-loader',
                test: /\.ts$/,
                exclude: [
                    /node_modules/
                ],
                options: {
                    emitErrors: true
                }
            },
            {
                loader: 'ts-loader',
                test: /\.ts$/,
                exclude: [
                    /node_modules/
                ],
                options: {
                    configFile: 'tsconfig.dev.json'
                }
            }
        ]
    },
    resolve: {
        extensions: [ '.ts', '.js' ]
    },
    output: {
        filename: 'server.js',
        path: path.resolve(__dirname, 'dist')
    }
};

webpack.config.prod.js

const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
    mode: 'production',
    entry: './src/server.ts',     // src下に書いていくので src/server.tsにしとく
    target: 'node',               // Module not found: Error: Can't resolve 'fs'とかいっぱい出たら、この行書き忘れ
    externals: [nodeExternals()], 
    module: {
        rules: [
            {
                enforce: 'pre',
                loader: 'tslint-loader',
                test: /\.ts$/,
                exclude: [
                    /node_modules/
                ],
                options: {
                    emitErrors: true
                }
            },
            {
                loader: 'ts-loader',
                test: /\.ts$/,
                exclude: [
                    /node_modules/
                ],
                options: {
                    configFile: 'tsconfig.dev.json'
                }
            }
        ]
    },
    resolve: {
        extensions: [ '.ts', '.js' ]
    },
    output: {
        filename: 'server.js',
        path: path.resolve(__dirname, 'dist')
    }
};

tsconfig.dev.json

{
  "compilerOptions": {
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "lib": ["es2018", "dom"],
    "moduleResolution": "node",
    "removeComments": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "strictFunctionTypes": false
  }
}

tsconfig.prod.json

{
  "compilerOptions": {
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "lib": ["es2018", "dom"],
    "moduleResolution": "node",
    "removeComments": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "strictFunctionTypes": false
  }
}

tslint.json

{
    "extends": "tslint-config-airbnb",
    "rules": {
        "ter-indent": [true, 4]
    }
}

src/server.ts

こちらはサンプルのソースコードになります

import * as Express from 'express';

const app = Express();

app.get(
    '/',
    (req: Express.Request, res: Express.Response) => {
        return res.send('Hello world.');
    });

app.listen(
    3000,
    () => {
        console.log('Example app listening on port 3000!');
    });

export default app;

ビルドと実行

docker-compose.yml の変更

ビルドと実行のための設定を追加します

--- a/docker-compose.yml        Mon Feb 11 11:43:19 2019
+++ b/docker-compose.yml        Sun Feb 10 23:14:41 2019
@@ -5,3 +5,9 @@
       context: .
       dockerfile: ./node.Dockerfile
     image: takaya030/node:8-alpine
+    volumes:
+      - .:/home/node/app
+      - /home/node/app/node_modules
+    ports:
+      - "3000:3000"
+    command: node dist/server.js

ビルド

$ docker-compose run --rm node webpack --config webpack.config.dev.js

実行

$ docker-compose up -d

web ブラウザで http://192.168.99.100:3000 を開いて "Hello World." が表示されれば成功です

参考サイト

postd.cc