コンテナーを利用した Node.js 開発

前提条件

Node.js アプリケーションのコンテナー化 を完了していること。

概要

本節ではコンテナー化したアプリケーションに対する開発環境の構築方法を学びます。 ここでは以下を行います。

  • ローカルデータベースを追加して、そのデータを維持します。
  • 開発環境として実行するためにコンテナー設定を行います。
  • コンテナー化したアプリケーションのデバッグを行います。

ローカルデータベースの追加とデータ維持

コンテナーでは、ローカルサービスとしてたとえばデータベースを構築することができます。 本節では compose.yaml ファイルを編集して、データベースサービスを追加してデータ保存のためのボリュームを定義します。

  1. IDE またはテキストエディターを使って compose.yaml ファイルを開きます。

  2. データベースに関する命令部分のコメントを解除します。 書き換えた compose.yaml ファイルは以下のようになります。

    重要

    本節においては、指示があるまで docker compose up は実行しないでください。 作業の途中においてコマンド実行してしまうと、データベースが誤って初期化されてしまう場合があります。

    compose.yaml
    # Comments are provided throughout this file to help you get started.
    # If you need more help, visit the Docker Compose reference guide at
    # https://docs.docker.com/go/compose-spec-reference/
    
    # Here the instructions define your application as a service called "server".
    # This service is built from the Dockerfile in the current directory.
    # You can add other services your application may depend on here, such as a
    # database or a cache. For examples, see the Awesome Compose repository:
    # https://github.com/docker/awesome-compose
    services:
      server:
        build:
          context: .
        environment:
          NODE_ENV: production
        ports:
          - 3000:3000
    
        # The commented out section below is an example of how to define a PostgreSQL
        # database that your application can use. `depends_on` tells Docker Compose to
        # start the database before your application. The `db-data` volume persists the
        # database data between container restarts. The `db-password` secret is used
        # to set the database password. You must create `db/password.txt` and add
        # a password of your choosing to it before running `docker-compose up`.
    
        depends_on:
          db:
            condition: service_healthy
      db:
        image: postgres
        restart: always
        user: postgres
        secrets:
          - db-password
        volumes:
          - db-data:/var/lib/postgresql/data
        environment:
          - POSTGRES_DB=example
          - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
        expose:
          - 5432
        healthcheck:
          test: ["CMD", "pg_isready"]
          interval: 10s
          timeout: 5s
          retries: 5
    volumes:
      db-data:
    secrets:
      db-password:
        file: db/password.txt

    メモ

    Compose ファイル内の命令について詳しく学ぶには Compose ファイルリファレンス を参照してください。

  3. IDE またはテキストエディターを使って src/persistence/postgres.js を開きます。 中を覗いてみると、このアプリケーションは Postgres データベースを利用しており、データベースへの接続に環境変数を利用していることがすぐにわかります。 compose.yaml ファイルでは、まだそれらの環境変数を定義していません。

  4. データベースの設定を行う環境変数を追加します。 書き換えた compose.yaml ファイルは以下のようになります。

    compose.yaml
    # Comments are provided throughout this file to help you get started.
    # If you need more help, visit the Docker Compose reference guide at
    # https://docs.docker.com/go/compose-spec-reference/
    
    # Here the instructions define your application as a service called "server".
    # This service is built from the Dockerfile in the current directory.
    # You can add other services your application may depend on here, such as a
    # database or a cache. For examples, see the Awesome Compose repository:
    # https://github.com/docker/awesome-compose
    services:
      server:
        build:
          context: .
        environment:
          NODE_ENV: production
          POSTGRES_HOST: db
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD_FILE: /run/secrets/db-password
          POSTGRES_DB: example
        ports:
          - 3000:3000
    
        # The commented out section below is an example of how to define a PostgreSQL
        # database that your application can use. `depends_on` tells Docker Compose to
        # start the database before your application. The `db-data` volume persists the
        # database data between container restarts. The `db-password` secret is used
        # to set the database password. You must create `db/password.txt` and add
        # a password of your choosing to it before running `docker-compose up`.
    
        depends_on:
          db:
            condition: service_healthy
      db:
        image: postgres
        restart: always
        user: postgres
        secrets:
          - db-password
        volumes:
          - db-data:/var/lib/postgresql/data
        environment:
          - POSTGRES_DB=example
          - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
        expose:
          - 5432
        healthcheck:
          test: ["CMD", "pg_isready"]
          interval: 10s
          timeout: 5s
          retries: 5
    volumes:
      db-data:
    secrets:
      db-password:
        file: db/password.txt
  5. server サービス配下に secrets セクションを追加します。 これにより、アプリケーションが利用するデータベースパスワードを安全に取り扱います。 書き換えた compose.yaml ファイルは以下のようになります。

    compose.yaml
    # Comments are provided throughout this file to help you get started.
    # If you need more help, visit the Docker Compose reference guide at
    # https://docs.docker.com/go/compose-spec-reference/
    
    # Here the instructions define your application as a service called "server".
    # This service is built from the Dockerfile in the current directory.
    # You can add other services your application may depend on here, such as a
    # database or a cache. For examples, see the Awesome Compose repository:
    # https://github.com/docker/awesome-compose
    services:
      server:
        build:
          context: .
        environment:
          NODE_ENV: production
          POSTGRES_HOST: db
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD_FILE: /run/secrets/db-password
          POSTGRES_DB: example
        ports:
          - 3000:3000
    
        # The commented out section below is an example of how to define a PostgreSQL
        # database that your application can use. `depends_on` tells Docker Compose to
        # start the database before your application. The `db-data` volume persists the
        # database data between container restarts. The `db-password` secret is used
        # to set the database password. You must create `db/password.txt` and add
        # a password of your choosing to it before running `docker-compose up`.
    
        depends_on:
          db:
            condition: service_healthy
        secrets:
          - db-password
      db:
        image: postgres
        restart: always
        user: postgres
        secrets:
          - db-password
        volumes:
          - db-data:/var/lib/postgresql/data
        environment:
          - POSTGRES_DB=example
          - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
        expose:
          - 5432
        healthcheck:
          test: ["CMD", "pg_isready"]
          interval: 10s
          timeout: 5s
          retries: 5
    volumes:
      db-data:
    secrets:
      db-password:
        file: db/password.txt
  6. docker-nodejs-sample ディレクトリ内に db という名前のディレクトリを生成します。

  7. db ディレクトリ内に password.txt という名前のファイルを生成します。 このファイルにデータベースパスワードを記述します。

    ここまでにより docker-nodejs-sample ディレクトリ内は少なくとも以下のようになっているはずです。

    ├── docker-nodejs-sample/
    │ ├── db/
    │ │ └── password.txt
    │ ├── spec/
    │ ├── src/
    │ ├── .dockerignore
    │ ├── .gitignore
    │ ├── compose.yaml
    │ ├── Dockerfile
    │ ├── package-lock.json
    │ ├── package.json
    │ └── README.md
  8. IDE またはテキストエディターを使って password.txt ファイルを開きます。 そしてパスワードを任意に取り決めて記述します。 このパスワードは 1 行内に記述し、これ以外の行を記述してはなりません。 改行文字や隠し文字などは一切含めないようにしてください。

  9. ここまでに修正したファイルは、すべて適切に保存したことを確認します。

  10. 以下のコマンドを実行してアプリケーションを起動します。

    $ docker compose up --build
    
  11. ブラウザーを開きます。 アプリケーションが起動していることを確認するため http://localhost:3000 にアクセスします。

  12. データが保持されることを確認するため、todo リストにアイテムをいくつか追加します。

  13. todo リストへの追加をある程度行ったら、端末上において ctrl+c を入力してアプリケーションを停止します。

  14. 端末上において docker compose rm を実行してコンテナーを削除します。

    $ docker compose rm
    
  15. docker compose up を実行してアプリケーションを再度起動します。

    $ docker compose up --build
    
  16. ブラウザーにおいて http://localhost:3000 の表示を更新します。 todo リスト内のアイテムが保持されていることを確認します。 コンテナーを削除した後の再起動であってもデータが保持されているはずです。

開発用コンテナーの設定と起動

バインドマウントを利用すれば、ソースコードをコンテナー内にマウントすることができます。 コードに対して変更を行って保存すれば、即座にコンテナー側でそれを利用することができます。 これはつまり、ファイルシステム内の変更を監視しそれに反応する処理を行う、nodemon のようなプロセスでも実行できるということです。 バインドマウントに関する詳細は ストレージ概要 を参照してください。

バインドマウントの追加に加えて、Dockerfile と compose.yaml ファイルにおいて、開発のための依存パッケージのインストール設定を行い、また開発ツールを実行します。

開発向けの Dockerfile 修正

IDE またはテキストエディターを使って Dockerfile を開きます。 Dockerfile にはこの時点で開発用の依存パッケージをインストールするものでなく、nodemon も実行していません。 ここからは Dockerfile を修正して、開発用依存パッケージのインストールと nodemon の実行を行うようにします。

ここでは本番環境向けの Dockerfile、開発向けの Dockerfile をそれぞれ作成するのではなく、1 つの Dockerfile をマルチステージにより両方に利用するものとします。

Dockerfile を以下のように修正して、以下のようなマルチステージの Dockerfile とします。

Dockerfile
# syntax=docker/dockerfile:1

ARG NODE_VERSION=18.0.0

FROM node:${NODE_VERSION}-alpine as base
WORKDIR /usr/src/app
EXPOSE 3000

FROM base as dev
RUN --mount=type=bind,source=package.json,target=package.json \
    --mount=type=bind,source=package-lock.json,target=package-lock.json \
    --mount=type=cache,target=/root/.npm \
    npm ci --include=dev
USER node
COPY . .
CMD npm run dev

FROM base as prod
RUN --mount=type=bind,source=package.json,target=package.json \
    --mount=type=bind,source=package-lock.json,target=package-lock.json \
    --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev
USER node
COPY . .
CMD node src/index.js

この Dockerfile においては FROM node:${NODE_VERSION}-alpine ステートメントに as base としてラベルをつけています。 こうすることで、このビルドステージを別のビルドステージにて参照できるようになります。 そして新たなビルドステージに対しては dev というラベルをつけて、開発向けの依存パッケージのインストールを行い、npm run dev を使ってコンテナーを起動させます。 最終的に prod というラベルのステージを作り出し、開発用依存パッケージは持たずに、node src/index.js を使ってアプリケーションを起動します。 マルチビルドステージの詳細については マルチステージビルド を参照してください。

次は Compose ファイルを修正して、新たなビルドステージを用いるようにします。

開発向けの Compose ファイル修正

dev ステージを Compose から実行するには compose.yaml ファイルの変更が必要です。 IDE またはテキストエディターを使って compose.yaml ファイルを開きます。 そして target: dev 命令を加えることで、マルチステージ Dockerfile 内の dev ステージを用いるように指定します。

また server サービスへのバインドマウントを行うためのボリュームを新たに追加します。 このアプリケーションでは、ローカルマシンの ./src を、コンテナー内の /usr/src/app/src にマウントします。

そしてデバッグ用にポート 9229 を開放します。

以下が修正を行った Compose jファイルです。 コメントはすべて解除されています。

compose.yaml
services:
  server:
    build:
      context: .
      target: dev
    ports:
      - 3000:3000
      - 9229:9229
    environment:
      NODE_ENV: production
      POSTGRES_HOST: db
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD_FILE: /run/secrets/db-password
      POSTGRES_DB: example
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - db-password
    volumes:
      - ./src:/usr/src/app/src
  db:
    image: postgres
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt

開発用コンテナーの実行とアプリケーションのデバッグ

以下のコマンドを実行して、修正した Dockerfilecompose.yaml ファイルを使ったアプリケーションを実行します。

$ docker compose up --build

ブラウザーを開きます。 アプリケーションが起動していることを確認するため http://localhost:3000 にアクセスします。

ローカルマシン内にあるアプリケーションのソースファイルは、変更すると同時に実行コンテナー内にも即座に反映されることになります。

IDE またはテキストエディターを使って docker-nodejs-sample/src/static/js/app.js を開きます。 そして 109 行めにあるボタンテキストを Add Item から Add に変更します。

+                         {submitting ? 'Adding...' : 'Add'}
-                         {submitting ? 'Adding...' : 'Add Item'}

ブラウザーにおいて http://localhost:3000 の表示を更新します。 更新したボタンテキストが変更されていることを確認します。

アプリケーションに対してインスペクタークライアントを接続して、デバッグを行うことができます。 インスペクタークライアントの詳細については Node.js ドキュメント を参照してください。

まとめ

本節では Compose ファイルの設定を通じて、簡単なデータベースを追加しデータの保持を行う方法を見てきました。 またマルチステージ Dockerfile の生成方法と、開発向けのバインドマウントの設定方法について学びました。

関連情報

次のステップ

次の節では、Docker を用いたユニットテストの実行方法について学びます。