はじめに
今回はDockerのお勉強がてら、複数コンテナを立てて簡単なアプリケーションを動かしてみました。
実際にどんなことをしたのか書いていこうと思います。
今回作ったもの
- Apache2を利用したWebコンテナ
- Node.js + Expressを利用したAPコンテナ
- MySQLを利用したDBコンテナ
それぞれのコンテナをつなげて簡単なアプリケーションを動かしました。
以下はファイル構成になります。
greet-app-docker
│ .env
│ docker-compose.yml
│
├───api
│ Dockerfile
│ greeting.js
│ package.json
│ package-lock.json
│
├───db
│ Dockerfile
│ init.sql
│
└───web
│ Dockerfile
│
└───src
greet.js
index.html
それぞれの作業を詳しく
Webコンテナ作成
1. Dockerfile作成
webディレクトリを作成し、まずはDockerfileを作成します。
FROM ubuntu
RUN apt update
RUN apt install -y apache2
COPY ./src/ /var/www/html/
CMD ["apache2ctl", "-D", "FOREGROUND"]
Ubuntuイメージを使用し、Apache2をインストールします。
画面表示に使うindex.htmlと、ボタン操作で発火するJavaScript(greet.js)はsrcディレクトリにまとめて置いているので、srcディレクトリをApache2の公開ディレクトリである/var/www/html/にコピーします。
最後にApache2をフォアグラウンドモードで起動してDockerfileの記述は完了です。
2. index.htmlを作成
srcディレクトリにindex.htmlを作成し、アプリの画面を実装します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>挨拶アプリ</title>
</head>
<body>
<h2>挨拶アプリ</h2>
<label for="greet-select">言語を選択してください</label>
<select id="greet-select">
<option value="ja">日本語</option>
<option value="en">英語</option>
<option value="ch">中国語</option>
</select>
<br />
<button
id="greeting"
onclick="greet(document.getElementById('greet-select').value)"
>
挨拶する
</button>
<div id="message"></div>
<script src="greet.js"></script>
</body>
</html>
機能はシンプルで、プルダウンリストで言語を選択し、「挨拶する」ボタンを押すことで次に作成するgreet関数が発火し、戻り値を画面表示するだけです。
3. greet.jsを作成
index.html同様にsrcディレクトリにgreet.jsを作成します。
const greet = async (lang) => {
const response = await fetch(
`http://localhost:8080/?lang=${encodeURIComponent(lang)}`,
);
const result = await response.json();
document.getElementById('message').innerHTML = `<h2>${result[0].word}</h2>`;
};
これはAPIをフェッチしてレスポンスのwordをHTMLファイルのID = messageにinnerHTMLする関数を記述しています。
後述するAPコンテナは8080番ポートで待ち受けているため、ブラウザからはlocalhost:8080宛てにfetchすることでAPIを呼び出せます。アプリを作り終えた後に記事を書いているため、先にfetch先を書いていますが、実体は次のAPコンテナ作成で作るものになります。
APコンテナ作成
1. Dockerfile作成
apiディレクトリを作成し、Dockerfileを作成します。
FROM node
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD [ "node", "greeting.js" ]
Node.jsイメージを使用し、/usr/src/appディレクトリを作成します。
次にpackage.jsonとpackage-lock.jsonをコピーし、npm installでライブラリをインストールします。
ポートに8080を指定し、Node.jsサーバーを起動してDockerfileの記述は終了です。
2. greeting.jsを作成
greeting.jsファイルを作成し、画面から引数を受け取り、DBコンテナから対象のデータを取得・画面に返すAPIを作成します。
const express = require('express');
const cors = require('cors');
const mysql = require('mysql2');
const app = express();
app.use(cors());
app.get('/', (req, res) => {
const connection = mysql.createConnection({
host: process.env.DB_HOST || 'db',
user: 'mysql',
password: 'mysql',
database: 'mysql_study',
charset: 'utf8mb4',
});
connection.connect();
connection.query(
'SELECT * FROM greetings WHERE lang = ?',
[req.query.lang],
(error, results) => {
if (error) {
res.status(500).json({ error: error.message });
return;
}
res.status(200).json(results);
},
);
});
const PORT = 8080;
const HOST = '0.0.0.0';
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
greeting.jsでは以下のライブラリを使用します。
- express:Node.js上でAPIサーバーを構築するためのフレームワーク。
app.get('/', ...)のようにルーティングを定義したり、res.status(200).json(results)のようにレスポンスを返したりする処理を簡潔に書けるようにするために使用しています。 - cors:Webコンテナ(
localhost:80)から配信されるindex.htmlは、APコンテナ(localhost:8080)に対してfetchでアクセスします。ポート番号が異なるとブラウザからは別オリジンとみなされ、そのままではCORS制約によりリクエストがブロックされてしまいます。これを回避し、異なるオリジンからのアクセスを許可するために使用しています。 - mysql2:DBコンテナで稼働しているMySQLに接続し、SQLクエリを実行してデータを取得するために使用しています。
DBコンテナ作成
1. Dockerfile作成
dbディレクトリを作成し、Dockerfileを作成します。
FROM mysql
COPY ./init.sql /docker-entrypoint-initdb.d/
EXPOSE 3306
MySQLイメージを使用し、後述するinit.sqlファイルを/docker-entrypoint-initdb.d/にコピーします。このディレクトリに置かれたSQLファイルは、MySQL公式イメージの仕組みにより、データディレクトリが空の状態でコンテナが初回起動した際に自動的に実行されます。
2. init.sql作成
テーブルの定義をするためにinit.sqlファイルを作成します。
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS greetings (
id INT AUTO_INCREMENT PRIMARY KEY,
lang VARCHAR(2) NOT NULL,
word VARCHAR(100) NOT NULL
);
INSERT INTO greetings (lang, word) VALUES ('ja', 'こんにちは');
INSERT INTO greetings (lang, word) VALUES ('en', 'Hello');
INSERT INTO greetings (lang, word) VALUES ('ch', '你好');
コンテナ作成時にlang列とword列を持ったgreetingsテーブルを作成し、初期データを投入するための処理を記載しています。
.env作成
dbコンテナ作成時にMySQLのルートパスワードやデータベース、ユーザーを自動作成できるようにするために必要な情報を.envファイルに記載していきます(テーブル自体は前述のinit.sqlで作成されます)。
MYSQL_ROOT_PASSWORD=mysql
MYSQL_DATABASE=mysql_study
MYSQL_USER=mysql
MYSQL_PASSWORD=mysql
ルートユーザーのパスワード、データベース名、ユーザー、パスワードを記載します。
docker-compose.yml作成
作成した3つのコンテナを起動するためのdocker-compose.ymlファイルを作成します。
services:
db:
build: ./db
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
ports:
- '3306:3306'
healthcheck:
test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost']
interval: 5s
timeout: 3s
retries: 10
web:
build: ./web
ports:
- '80:80'
depends_on:
- api
api:
build: ./api
ports:
- '8080:8080'
environment:
DB_HOST: db
depends_on:
db:
condition: service_healthy
dbエリア
.envファイルで記載した変数を使用してenvironmentを記載します。
ポートは3306をマッピングします。
webエリア
ポートは80をマッピングします。
また、APコンテナが起動していないと画面からのfetchが失敗してしまうため、depends_onでAPコンテナ→Webコンテナの順番で起動されるようにしています。
apiエリア
ポートは8080をマッピングします。
また、DB_HOSTはdbコンテナを指定し、healthcheckが通ってから(DBが実際に接続を受け付けられる状態になってから)APコンテナが起動するようにしています。
動作確認
コマンドプロンプトで以下のコマンドを入力して、各コンテナを起動します。
docker compose up
起動が確認出来たらlocalhost:80に行ってみます。
Webコンテナのindex.htmlで作成した画面が表示されています。
言語を選択して挨拶するボタンを押してみます。
Helloが返ってきました。他の言語でもDBコンテナで設定したものが返ってきます。
これで動作確認は終了です。
最後に
Dockerfileやリソースを1から作るのは今回が初めてだったのですが、書き方さえわかってしまえばとても簡単にコンテナ作成から起動まで行うことができました。
以前WSL2を使って仮想環境に同じようなアプリを作成したのですが、Dockerを使用したほうが簡単だったなと思いました。

