9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DockerでNode.js環境構築する際、コンテナ生成時にpackage.jsonからパッケージを展開する方法

Posted at

環境

  • Windows10 Home 20H2
  • WSL2インストール済み
  • Docker Desktop for Windows
  • VSCode

参考にさせて頂いた記事、サイト

当記事のきっかけ

前の記事で、自分なりにdocker使えるような気がし始めてきたところですが、疑問が生じました。
前の記事(※)では、空のnode.jsコンテナがあり、そのコンテナ内でnpm initでpackage.jsonを初期化、順次必要なパッケージをインストールしていき、package.jsonに依存関係を記録していきました。

あらかじめ依存関係を記録したpackage.jsonや初期のコードがあり、それをコンテナに展開させるにはどうするのかを、自分なりに試そうと思いました。
自分がDockerについて調べて、やっと使えるようになった気がした手順 - Qiita

やりたいこと

  • 初期のコード、依存関係が登録されたpackage.jsonが先にある。
  • コンテナ生成時に必要なパッケージ、初期コードがコンテナに展開される。
  • コンテナを削除しても、ソースは残る。(永続化)

実践

###ファイル構造(ホスト)

(root)
  |
  |--src/ (←このフォルダ以下をコンテナにマウントする。)
  |   |--dist/(※distフォルダ以下は、typescriptをトランスパイルした際に作成される。初めは存在しない。)
  |   |   |--server.js
  |   |
  |   |--nodo_modules(※npm install実行時にpackage.jsonをもとに展開される。初めは存在しない。)
  |   |
  |   |--src/
  |   |   |-server.ts
  |   |
  |   |--nodemon.json
  |   |--package.json
  |   |--tsconfig.json
  |   |--webpack.config.json
  |
  |--docker.compose.yml

dcoker-compose.yml

docker-compose.yml
version: '3'
services: 
  web-server:
    image: node:14
    container_name: docker-node-c1
    volumes:
      - ./src:/app
    ports:
      - 3000:3000
    working_dir: /app
    command: >
      bash -c "npm install && npm run build && npm run dev"

その他ファイルを準備する

(root)/src/src/server.ts
import * as Express from 'express';

const app = Express();

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

const portNo = process.env.PORT || 3000;

app.listen(portNo, () => {
  console.log(`app running on port ${portNo}.`);
});

export default app;
(root)/src/nodemon.json
{
  "watch": [
    "dist"
  ], 
  "ext": "ts, js, json", 
  "exec": "node ./dist/server.js"
}
(root)/src/package.json
{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "dev": "nodemon -L",
    "start": "node ./dist/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1"
  },
  "devDependencies": {
    "@types/express": "^4.17.12",
    "nodemon": "^2.0.7",
    "ts-loader": "^9.2.3",
    "typescript": "^4.3.2",
    "webpack": "^5.39.0",
    "webpack-cli": "^4.7.2",
    "webpack-node-externals": "^3.0.0"
  }
}
(root)/src/tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true, 
    "noImplicitAny": true, 
    "module": "es2015", 
    "target": "es2017", 
    "jsx": "react", 
    "lib": ["es2018", "dom"], 
    "moduleResolution": "node", 
    "removeComments": true, 
    "strict": true, 
    "noUnusedLocals": false, 
    "noUnusedParameters": false, 
    "noImplicitReturns": true, 
    "noFallthroughCasesInSwitch": true, 
    "strictFunctionTypes": false 
  }
}
webpack.config.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  mode: 'development', 
  target: 'node', 
  externals: [ nodeExternals() ], 
  devtool: 'eval-source-map', 
  module: {
    rules: [
      {
        loader: 'ts-loader', 
        test: /\.ts$/, 
        exclude: [/node_modules/], 
        options: { configFile: 'tsconfig.json'}
      }
    ]
  }, 
  resolve: {
    extensions: ['.ts', '.js', '.json']
  }, 
  entry: './src/server.ts', 
  output: {
    filename: 'server.js', 
    path: path.resolve(__dirname, 'dist')
  }, 
  node: {
    __dirname: false
  }
};

コンテナを生成する

ターミナルで以下のコマンドを実行します。

docker compose up

VSCodeのエクスプローラ部を確認すると、srcフォルダにnode-modulesフォルダが生成され、配下にnpm installによりウンロードされたファイルが表示されます。
初期コードのTypeScriptファイルserver.tsがトランスパイルされ、distフォルダが生成され、server.jsファイルが出来上がります。
生成されたコンテナ内でnode.jsサーバが起動します。
ブラウザにて、localhost:3000にアクセスすると、"Hello World."が表示されます。

実験

コードの修正が反映するかの実験

server.tsを修正します。

(root)/src/server.ts
//...省略...
app.get('/', (req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
  //res.send('Hello World.');
  res.send('hogehoge.');
});
//...省略...

VSCodeで別ターミナルを開き、コンテ内に入りトランスパイルします。

# コンテナ内に入る
docker exec -it docker-node-c1 /bin/bash

# 以下のような表示になる。
root@c34ee48d4afb:/app# 

# トランスパイルを実行する。
# nodemonが変更を検知し、node.jsサーバが再起動します。
root@c34ee48d4afb:/app# npm run build

ブラウザをリロードし、表示が"hogehoge."になることを確認します。

コンテナ再起動後も修正が保持されているかの実験

docker compose upを実行したターミナルでctrl+cを押下し、コンテナを停止します。
ブラウザでlocalhost:3000にアクセスすると、
"このサイトにアクセスできません"
等の表示になります。

再度、docker compose upを実行し、コンテナを起動します。
ブラウザでlocalhost:3000にアクセスすると、"hogehoge."と表示され、コンテナにコードの修正が保持されていることが確認できます。

以上です。

奮闘記

ああでもない、こうでもないと、試した記録です。

Dockerfileを使用する

ホストに完成package.json、ソースコードを持ち、それらをコンテ内にコピーしたイメージを考えました。

フォルダ構成
(root)
  |
  |--src/ (←このフォルダ以下をデータコンテナにマウントする。)
  |   |--dist/(※distフォルダ以下は、typescriptをトランスパイルした際に作成される。初めは存在しない。)
  |   |   |--server.js
  |   |
  |   |--nodo_modules(※npm install実行時にpackage.jsonをもとに展開される。初めは存在しない。)
  |   |
  |   |--src/
  |   |   |-server.ts
  |   |
  |   |--nodemon.json
  |   |--package.json
  |   |--tsconfig.json
  |   |--webpack.config.json
  |
  |--Dockerfile

Dockerfile

Dockerfile
FROM node:14

# コンテナ内の作業フォルダはapp
WORKDIR /app

# ./srcフォルダをコンテナの/appにコピーする。
# 依存関係が記録済みのpackage.json、その他ソースコードをコピーされる。
COPY ./src ./

# コンテナ内でコピーされたpackage.jsonを元に、パッケージがインストールされる。
RUN npm install 

# typescriptをトランスパイルする。
RUN npm run build

# nodemonでnode.jsサーバを起動する。
CMD npm run dev

このDockerfileをもとにイメージ"docker-node-image1"を作成します。
Dockerfileがあるディレクトリで以下のコマンドを実行します。

docker build . -t docker-node-image1

イメージ"docker-node-image1"をもとにコンテナ"docker-node-c1"を作成します。

docker run --name docker-node-c1 -p 3000:3000 docker-node-image1

# コンテナの生成、パッケージのインストール、タイプスクリプトのビルド、nodemonの起動まで行われます。
# 当方のターミナルでは以下のような表示になります。
> app@1.0.0 dev /app
> nodemon -L

[nodemon] 2.0.7
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): dist/**/*
[nodemon] watching extensions: ts,js,json
[nodemon] starting `node ./dist/server.js`
app running on port 3000.

ブラウザでlocalhost:3000にアクセスし、コンテナが起動していることを確認します。

続いてコンテナ内に入り、ファイルが展開されているかを確認します。
別のターミナルを開き、以下のコマンドを実行します。

docker exec -it docker-node-c1 /bin/bash
# 以下のような表示になります。
root@932173c904c6:/app#

# dirコマンドでディレクトリを確認します。
root@932173c904c6:/app#dir

# 結果
dist          nodemon.json       package.json  tsconfig.json
node_modules  package-lock.json  src           webpack.config.js   

コンテナ内にホストからコピーしたファイルが展開されていることが確認できました。
確認ができたので、このコンテナは削除します。

docker rm docker-node-c1

ここで私は、「よし、あとはコンテナ内のappフォルダにホストのフォルダをマウントすれば成功だ!」と思い、以下のコマンドを実行しました。

# -vオプションの"C:\hoge\fuga"の部分はプロジェクトフォルダのルートです。
# ※-vオプション部分のホスト側のパス部分、フルパスでないと期待した挙動になりませんでした(/srcというような相対パスでは不可でした)。
docker run -p 3000:3000 -v C:\hoge\fuga\src:/app --name docker-node-c1 docker-node-image1

成功を確信していましたが結果はエラーでした。

# 結果
> app@1.0.0 dev /app
> nodemon -L

sh: 1: nodemon: not found
npm ERR! code ELIFECYCLE
npm ERR! syscall spawn
npm ERR! file sh
npm ERR! errno ENOENT
npm ERR! app@1.0.0 dev: `nodemon -L`
npm ERR! spawn ENOENT
npm ERR!
npm ERR! Failed at the app@1.0.0 dev script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2021-07-01T15_54_06_909Z-debug.log

エラーメッセージの
Local package.json exists, but node_modules missing, did you mean to install?
をGoogle翻訳にかけると、

ローカルのpackage.jsonは存在しますが、node_modulesがありません。インストールするつもりでしたか?

とのことです。Dockerfileで「RUN npm install」を指定してたのになぜ?と思いました。
おそらくコンテナ生成時、以下のようなことが起こったのではないかと想像しています。

  • イメージdocker-node-image1からコンテナを生成すると、/appディレクトリ内にはnode_modues、distフォルダが存在する。
  • docker runコマンドの-v オプションにより、ホストの./srcフォルダの内容がコンテ内の/appに上書きされる。この時、ホスト側の./srcフォルダにはnode_modules、distフォルダはないので、コンテナ内の/appディレクトリのnode_moduels、distも消える。

エラーは発生しましたが、dokcer ps -aコマンドを確認すると、コンテナは生成されていました。
後のために削除します。

# コンテナ一覧確認
docker ps -a

# 結果 エラーは発生したが、コンテナ"docker-node-c1"は生成されている。
CONTAINER ID   IMAGE                                              COMMAND                  CREATED          STATUS                      PORTS     NAMES
5429c0746eb9   docker-node-image1                                 "docker-entrypoint.s…"   21 minutes ago   Exited (1) 21 minutes ago             docker-node-c1   
# 削除する。
docker rm docker-node-c1

次は、コンテナ生成後、bashを起動し、そこでnpm install等を試しました。

docker run -it -p 3000:3000 -v C:\hoge\fuga\src:/app --name docker-node-c1 docker-node-image1 /bin/bash

# コンテナ生成後、コンテナ内に入り、以下のような表示になります。
root@2cde3c6232ad:/app# 

# ここでnpmコマンドを実行します。
root@2cde3c6232ad:/app# npm install
root@2cde3c6232ad:/app# npm run build
root@2cde3c6232ad:/app# npm run dev

ホスト側の./srcフォルダにnode_modules、distが現れ、node.jsサーバも立ち上がり、localhost:3000にアクセスすることができました。

コンテナ停止、再起動、を確認。

# コンテナ停止
docker stop docker-node-c1

# コンテナ再起動
docker start docker-node-c1

# コンテナ内に入り、node.jsサーバを起動
docker exec -it docker-node-c1 /bin/bahs

# 以下はコンテナ内
# nodemonでnode.jsサーバを起動。
root@932173c904c6:/app#npm run dev

期待した挙動になりました。
実は、この部分記事の下書きを書きながら気づきました。これで、当初やりたかったことができてしまいました。

以上を振り返ると、

  1. Dockerイメージにpackage.json、初期コードを持たせている。
  2. Docerfileの"RUN npm install"でコンテナ生成時にnpm installを行っている。
  3. しかし、docker run -vオプションで、ホストのsrcを上書きしている。
    ということは、この例では、Dockerfileでソースのコピー等不要でした。
    もっと言うと、Dockerfile用いてイメージの作成は不要でした。
    Dockerhubに上がっているNode.jsのイメージを用いてコンテナを生成し、ホストのsrcフォルダをマウントすれば、やりたいことができました。
# 先の例(再掲)
# doker run -it -p 3000:3000 -v C:\hoge\fuga\src:/app --name docker-node-c1 docker-node-image1 /bin/bash

# イメージでdocker-node-image1ではなくDockerhub上のイメージ"node:14"を用いる。
doker run -it -p 3000:3000 -v C:\hoge\fuga\src:/app --name docker-node-c1 node:14 /bin/bash

# コンテナ生成後、コンテナ内のbashが起動し、以下のような表示になります。
root@638e25fd74f5:/# 

# appディレクトリに移動します。
root@638e25fd74f5:/# cd app

# パッケージをインストールします。
root@638e25fd74f5:/app# npm install

# 以下トランスパイルや、npm start等の実行…省略
# コンテナの削除
docker rm docker-node-c1

当例ではDockerfile用いる必要ありませんでしたが、個人的には実際にDockerfile記述し、挙動を確認、体験してみるという意味では、大変意義がありました。

感想

何度も、コンテナの生成、破棄をコマンドを実行し、行いました。
自分は、ドキュメントを読んで理屈を理解するよりも、実際に実行してみて、感覚を掴むほうが理解が進みました。いや、完全に理解はしていないと思いますが、dockerいいなと思いました。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?