🙆

[Docker入門#2]Hello Worldアプリケーション作成

commits7 min read

関連記事

Hello Worldアプリケーション作成

はじめに

Hello Worldを表示するWEBアプリケーションのイメージの作成からコンテナの実行までを行いDockerの使い方を学ぶ。
今回はWEBアプリケーションの言語はNode.js、フレームワークにExpressを使用する。
以下の流れでコンテナを作成・実行していく。

hellorworld-1.png

アプリケーションのディレクトリ構成

今回作成するアプリケーションのディレクトリ構成は以下になる。

~/myimage
|-- .dockerignore
|-- Dockerfile
|-- node_modules/
|-- package-lock.json
|-- package.json
`-- server.js

最新のNode.jsをインストール

Node.jsでアプリケーションを作成する上で最新のNode.jsを使用したい。
DockerホストにnというNode.jsのバージョン管理パッケージをインストールし、nパッケージで最新のNode.jsとnpmをインストールする。
まずパッケージを更新する。

$ sudo apt update;
$ sudo apt upgrade

Node.jsとnpmをインストールする。

$ sudo apt install nodejs npm;

nパッケージをインストールする。

$ sudo npm -g install n;

最新バージョンのNode.jsとnpmをインストールする。

$ sudo n latest;

既存のNode.jsとnpmをアンインストールする。

$ sudo apt purge nodejs npm;

Node.jsとnpmの更新を反映させる為に再ログインする。

$ exec $SHELL -l;

Node.jsとnpmのバージョンを確認する。

$ node -v;
v16.4.1

$ npm -v;
7.18.1

以上で、最新のNode.jsがインストールされた。

Node.jsアプリケーションを作成する

Dockerホストに作業ディレクトリを作成する。

$ mkdir ~/myimage;
$ cd  ~/myimage/;

Dockerホストの作業ディレクトリで、 package.jsonファイルを作成し依存関係をインストールする。

$ npm init -y \
  && npm i express;

Expressフレームワークを使ってHello Worldを表示するWEBアプリケーションをserver.jsに作成する。

server.js
const express = require('express');

// Constants
const PORT = 3000;
const HOST = '0.0.0.0';

// App
const app = express();
app.get('/', (req, res) => {
  res.send('Hello World');
});

app.listen(PORT, HOST);

Dockerfile作成

Dockerfileとは、Docker上で動作させるコンテナの構成情報を記述するためのファイル。
イメージの雛形であり、ベースイメージ、ライブラリのインストール等の追加処理、アプリケーションコードの定義等を記述しDockerイメージの基となる。

hellorworld-2.png

以下のDockerfileを作成する。

Dockerfile
FROM node:16

# アプリケーションディレクトリを作成する
WORKDIR /var/www

# アプリケーションの依存関係をインストールする
COPY package*.json ./
RUN npm install

# アプリケーションコードをバンドルする
COPY . .

# 外部に公開するポートを明示する
EXPOSE 3000

# アプリケーションを実行する
CMD ["node", "server.js"]

Dockerfileに定義した命令を詳しく解説していく。

  • FROM node:16
    ベースとなるイメージを指定する。
    今回は、DockerHubから入手できるNode.js 16のイメージを使用する。
    ※このイメージにはNode.jsとnpmが既にインストールされている為、改めてインストールする必要はない。
  • WORKDIR /var/www
    アプリケーションコードを置くイメージ上の作業ディレクトリを指定する。
    イメージに作業ディレクトリが存在しない場合は、自動で生成される。
  • COPY package*.json ./
  • RUN npm install
    Dockerホストの作業ディレクトリにあるpackage.jsonとpackage-lock.jsonをイメージの作業ディレクトリにコピーし、npm installでアプリケーションの依存関係をインストールする。
    依存関係のインストールと、アプリケーションコードのバンドルを分けている理由は、Dockerビルドをする際、Dockerfileで実行済みのコマンドはキャッシュが使用される。
    ただし、ADDやCOPYコマンドでビルド媒体などをコンテナにコピーする際、コピー元のファイルに変更があった場合は、キャッシュは使用されず、新しい媒体がコンテナ内にコピーされ、それ以降の命令ではキャッシュは使用されなくなる。
    そのため、変更が少ない依存関係のインストールをDockerfileの最初の方で行うことで、依存関係のインストールに変更がない限りは2回目以降の依存関係のインストールはキャッシュが使用されDockerビルドの時間が短縮できる。
  • COPY . .
    DockerホストにあるアプリケーションのソースコードをDockerイメージにバンドルする。
  • EXPOSE 3000
    WEBアプリケーションがLISTENするポートを指定する。
    EXPOSE命令は実際にはポートを公開する訳ではなく、どのポートが公開されるかという情報を明示するだけである点に注意する。
    実行したコンテナのポートを実際に公開するには、docker container runで-pオプションを使用してDockerホストとコンテナのポートをマッピングする。
  • CMD ["node", "server.js"]
    アプリケーションを実行するコマンドを定義する。

.dockerignoreファイル作成

Dockerホストからイメージにディレクトリやファイルを送る際に、イメージに送りたくないディレクトリやファイルを.dockerignoreに定義できる。

今回は誤ってDockerホストにある依存パッケージがイメージ内にインストールされているパッケージに上書きされてしまう事態を防ぎたいので、node_modulesを設定する。

.dockerignore
node_modules

イメージを構築

Dockerfileがあるディレクトリに移動し、次のコマンドを実行してDockerイメージを構築する。

  • -t イメージ名[:タグ]
    イメージの名前とオプションのタグを指定する。
    名前を付けた方が名前からイメージの操作を行え作業効率が良いのと、またイメージを管理しやすいので名前を付けておく。
$ docker image build -t myimage .;

myimageイメージが構築されている事を確認する。

$ docker image ls;
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
myimage      latest    8436f501e7d0   4 seconds ago   912MB

コンテナを作成・実行

コンテナの作成と実行を行う。

  • -p 外部からアクセスされるポート番号:コンテナ側のポート番号
    -p 8000:3000でローカルのポート8000(localhost:8000)にアクセスすると、
    dockerコンテナの内部でポート3000で動いているアプリケーションに接続できるようになる。
  • -d
    バックグラウンドでコンテナを実行する。
    バックグラウンドで実行しない場合、コンソールがコンテナのログに専有され他のコマンドを打つことができなくなる。
    今回はコンテナ実行後にコンソールを使用したいのと、特にコンテナの挙動を見る必要がないのでバックグラウンドでコンテナを実行する。
  • --name コンテナ名
    コンテナの名前を指定する。
    名前を付けた方が名前からコンテナの操作を行え作業効率が良いので名前を付けておく。
  • --rm
    コンテナ終了時にコンテナを自動的に削除する。
$ docker container run -p 8000:3000 -d --rm --name mycontainer myimage;
c7bb9243ba1af7da82e1a74da794ca47a6f879ff20f7b890ae49ab4d5b3cf88a

実行したコンテナを確認する。

$ docker container ls;
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                                       NAMES
c7bb9243ba1a   myimage   "docker-entrypoint.s…"   12 seconds ago   Up 11 seconds   0.0.0.0:8000->3000/tcp, :::8000->3000/tcp   mycontainer

無事コンテナが実行されている事が確認できた。

テスト

Docker化したアプリケーションをテストする。
curlでDockerホストの公開ポートにHTTPリクエストを実行する。

$ curl localhost:8000;
Hello World

Hello Worldがレスポンスされた事を確認できた。

ここまでの手順を図解

  • Dockerfileを作成する。
  • Dockerfileを元にbuildしてイメージを作成する。
  • イメージからコンテナを作成し実行する。

hellorworld-3.png

  • コンテナ実行時(run、-pオプションでポートフォワーディングの設定をする。
    下図のように、コンテナの3000ポートを開放し、8000ポートを紐づける事で外部からのアクセスを可能にした。

hellorworld-4.png

  • curlで8000ポートでHTTPリクエストを実行する。
    Hello Worldとレスポンスが返された。

hellorworld-5.png

このようにポートフォワーディングでポートの紐付けを行う事で、仮想環境に対してアクセスを行えるようになる。

余談だがdocker container runコマンドの-pオプションでDockerホストのポートを設定をしない場合は、よしなに空いているポート番号を割り振り、そのポート番号とコンテナのポートとの紐付けを行ってくれる。

$ docker container run -p 3000 -d --rm --name mycontainer myimage;
365022f7f6b5b0eb8446e151ac9305ccc35a753fe7ddf8b2d109e4398fa66e53

docker continer lsコマンドでPORTSを確認してみる。

$ docker container ls;
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS              PORTS                     NAMES
365022f7f6b5   myimage   "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:60658->3000/tcp   mycontainer

Dockerホストに自動的に空いているポート60658が割り振られた事が分かる。

最後に後始末として、確認を終えられたコンテナとイメージを削除しておこう。

docker container stop mycontainer \
  && docker image rm myimage;

以上で、DockerでHello Worldアプリケーションの作成は完了。
お疲れさまでした。