【Docker】Docker Compose の基本
Docker Compose とは
これまでのDockerの記事では、docker container run
コマンドを用いて1つ1つコンテナを作成していました。
しかし複数コンテナを使用する場合、例えば開発メンバーが同じ環境を作ろうとすると、同じようにコマンドを実行していく必要があります。
すべての開発メンバーがDockerに関する知識があることが望ましいですが、そうでないケースも多々あるかと思います。
Docker Composeは、YAMLを使用して複数コンテナを定義することで、コンテナの起動、停止、管理を簡易化することができます。 また本番や開発などの環境の切り替えやCI/CDなどにも利用できます。
ここでは簡単な三層構造のアプリケーションを作成することで、Docker Composeの基本的な部分を説明していきます。
docker-compose.yml
始めにDBとしてMySQLのコンテナを定義していきます。
docker container run
では以下のようにしてコンテナを起動します。
$ docker volume create db-store
$ docker container run --name db -e MYSQL_ROOT_PASSWORD=password --mount type=volume,src=db-store,dst=/var/lib/mysql -d mysql
簡単に補足をしておきます。
-e
オプションで環境変数MYSQL_ROOT_PASSWORD
を設定しています。
これはルートユーザーのパスワードになります。
--mount
オプションで予め作成したdb-store
ボリュームを/var/lib/mysql
にマウントしています。
これによりデータが永続化されます。
これをDocker Composeで定義していきます。
Docker Composeによる定義は、docker-compose.yml
という名前のファイルにしていきます。
ここでは/my-app
ディレクトリ直下にdocker-compose.yml
を作成したとします。
作成したファイルに以下を記述します(YAML自体の書式については割愛します)。
services: # コンテナの定義
db:
image: mysql
environment:
- MYSQL_ROOT_PASSWORD=password
volumes:
- db-store:/var/lib/mysql
volumes: # ボリュームの作成
db-store:
services
はサービスとしてコンテナの定義を行います。
db
はサービス名になります。
コンテナの定義内容としては、docker container run
で指定した情報と同じです。
image
: 使用するイメージを表す(MySQLを使用するためmysql
を設定)environment
: 環境変数を表す(ルートユーザーのパスワード設定のためにMYSQL_ROOT_PASSWORD
を設定)volumes
: データマウントの設定を表す(db-store
ボリュームを/ver/lib/mysql
にマウント)
DBを利用するためにはボリュームを作成する必要があります。
これは、services
と同じ階層に記述したvolumes
に記述することでできます。
起動/停止
ではDocker Composeを使用してコンテナを起動します。
Docker Composeでコンテナを起動するには、docker-compose.yml
のあるディレクトリでdocker compose up
コマンドを実行します。
アタッチモードで起動するため、終了する場合はCtrl-C
を入力します。
デタッチモードで起動したい場合は-d
オプションを指定してください。
$ docker compose up -d
[+] Running 11/11
✔ db 10 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿] 0B/0B Pulled 11.3s
✔ ea4e27ae0b4c Pull complete 1.5s
✔ 837904302482 Pull complete 0.7s
✔ 3c574b61b241 Pull complete 0.7s
✔ 654fc4f3eb2d Pull complete 1.6s
✔ 32da9c2187e3 Pull complete 1.3s
✔ dc99c3c88bd6 Pull complete 2.0s
✔ 970181cc0aa6 Pull complete 3.7s
✔ d77b716c39d5 Pull complete 2.3s
✔ 9e650d7f9f83 Pull complete 4.3s
✔ acc21ff36b4b Pull complete 8.2s
[+] Running 3/3
✔ Network my-app_default Created 0.0s
✔ Volume "my-app_db-store" Created 0.0s
✔ Container my-app-db-1 Started 0.1s
まず必要なイメージがPullされます。 元々Pullされている場合は省略されます。
その後、ネットワーク、ボリューム、コンテナが作成され、コンテナが起動します。
ネットワーク、ボリューム、コンテナは、それぞれmy-app_default
、my-app_db-store
、my-app-db-1
という名前で作成されます。
各名前にmy-app
とあるようにカレントディレクトリの名前が使用されます。
ネットワークについては省きますが、コンテナ、ボリュームはdocker-compose.yml
で定義したものが使用されています。
Docker Composeで起動したコンテナを停止させるには、docker compose down
コマンドを実行します。
$ docker compose down
[+] Running 2/2
✔ Container my-app-db-1 Removed 1.0s
✔ Network my-app_default Removed 0.0s
Removed
とあるようにコンテナとネットワークは削除されます。
ボリュームは削除されないため、不要な場合はdocker volume rm
で直接削除するか、--volumes
オプションを指定します。
また使用したイメージもそのまま残ります。
不要な場合はdocker image rm
で直接削除するか、--rmi all
オプションを指定します。
--rmi all
は、docker-compose.yml
で使用しているすべてのイメージを削除します。
ただし、他のコンテナで使用しているイメージは削除できません。
$ docker compose down --volumes --rmi all
[+] Running 4/4
✔ Container my-app-db-1 Removed 1.0s
✔ Volume my-app_db-store Removed 0.0s
✔ Image mysql:latest Removed 0.2s
✔ Network my-app_default Removed 0.0s
複数コンテナの定義
上記ではDB用のコンテナのみを作成しました。
以下の説明では、AP、WEB用のコンテナを1つのdocker-compose.yml
で定義していきます。
DBコンテナ
DBコンテナは基本的に上記のものを使用するのですが、少しだけ追記します。
services:
db:
image: mysql:latest
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_USER=user # 追記
- MYSQL_PASSWORD=password # 追記
- MYSQL_DATABASE=mydb # 追記
volumes:
- db-store:/var/lib/mysql
- ./db/script:/docker-entrypoint-initdb.d # 追記
volumes:
db-store:
環境変数に追記したMYSQL_USER
、MYSQL_PASSWORD
は、ユーザーを作成するためのものです。
APコンテナからDBにアクセスするための認証情報として使用します。
もう1つ環境変数に追記したMYSQL_DATABASE
はデータベースを作成するためのものです。
ここではmydb
というデータベースを作成しています。
バインドマウントの追記は、初期データを設定するためのものです。
/docker-entrypoint-initdb.d
にSQLファイルを配備すると、コンテナ起動時にSQLを実行してくれます。
ここではホスト側の./db/script
内にSQLファイルを作成しておけば、初期データが設定されることになります。
では以下のようにファイルを作成し、docker compose up
でコンテナを起動します。
my-app/
┝ db/
│ └ script/
│ ┝ 1.message.sql
│ └ 2.user.sql
└ docker-compose.yml
CREATE TABLE IF NOT EXISTS message (
id INT AUTO_INCREMENT PRIMARY KEY,
message VARCHAR(256)
);
INSERT INTO message (message) VALUES
('Hello.'),
('Good morning.'),
('Good evening.');
CREATE TABLE IF NOT EXISTS user (
id INT PRIMARY KEY,
name VARCHAR(20)
);
INSERT INTO user (id, name) VALUES
(1, 'Taro'),
(2, 'Jiro'),
(3, 'Saburo');
SQLが実行されていることを確認するために、コンテナ内に入ってデータの確認をします。
これまで通りdocker container exec
を使用しても良いのですが、Docker Composeを使用している場合はdocker compose exec
を使用することができます。
$ docker compose exec db bash
bash-4.4# mysql --user=user --password=password
mysql> use mydb
mysql> select * from message;
+----+---------------+
| id | message |
+----+---------------+
| 1 | Hello. |
| 2 | Good morning. |
| 3 | Good evening. |
+----+---------------+
3 rows in set (0.00 sec)
mysql> select * from user;
+----+--------+
| id | name |
+----+--------+
| 1 | Taro |
| 2 | Jiro |
| 3 | Saburo |
+----+--------+
3 rows in set (0.01 sec)
上記の通り、ユーザーが作成されていること、データベースが作成されていること、初期データが登録されていることが確認できました。
初期データの登録について2点補足しておきます。
1つは実行順についてです。 ファイル名からなんとなく察しがついているかもしれませんが、ファイルの並び順で実行されます。 そのため実行順が重要となる場合は、例の通り先頭に数字を入れるなどして実行順を制御します。
もう1つは実行条件についてです。 データベースが存在している場合、SQLは実行されません。 つまり、初期データを登録しなおしたい場合は、ボリュームを削除する必要があります。
APコンテナ
APコンテナはNode.js(Express)で作成します。
API用に/api
ディレクトリを作成し、以下のファイルを作成します。
my-app/
┝ api/
│ ┝ Dockerfile
│ ┝ index.js
│ └ package.json
┝ db/
└ docker-compose.yml
{
"name": "api",
"version": "1.0.0",
"description": "API Container.",
"main": "index.js",
"scripts": {
"start": "nodemon index.js" //サーバー起動用コマンド
},
"dependencies": {
"express": "^4.18.2", //Expressを利用する
"mysql2": "^3.9.2", //MySQL接続用
"nodemon": "^3.1.0" //Node.js起動用(ホットリロード)
}
}
const express = require("express");
const mysql = require("mysql2");
const app = express();
// DB接続情報
const db = mysql.createConnection({
host: "db", //DBのサービス名を指定
user: "user",
password: "password",
database: "mydb",
port: 3306,
});
app.get("/api/message", (req, res) => {
const query = "SELECT * FROM message";
db.query(query, (err, result) => {
if (err) {
res.status(500).send("Error");
} else {
res.json(result);
}
});
});
app.get("/api/user", (req, res) => {
const query = "SELECT * FROM user";
db.query(query, (err, result) => {
if (err) {
res.status(500).send("Error");
} else {
res.json(result);
}
});
})
const PORT = 3000;
const HOST = '0.0.0.0';
app.listen(PORT, HOST);
Expressの説明は本件からずれてしまうため、ポイントだけ説明します。
サーバーはnpm run start
で起動できるよう、package.json
のscripts
に記載しています。
実際にはnodemon
を使用しており、ソースの変更が即座に判定されるようにしています(ホットリロード)。
index.js
にはAPIの処理を定義しており、データベースで作成した各テーブルのすべてのデータをJSON形式で返す/api/message
、/api/user
を作成しています。
DBコンテナに接続するために、host
にはサービス名であるdb
を指定します。
通常のコンテナでは、同一ネットワーク上であればコンテナ名で名前解決ができましたが、Docker Composeではサービス名で名前解決してくれます。
次にAPコンテナを作成するためのDockerfileを作成します。 DBコンテナとは異なり、そのままイメージを使用するのではなく、パッケージのインストールやサーバーの起動といった処理をDockerfileで定義します。
FROM node:latest
# /api のファイルを /app にコピー
WORKDIR /app
COPY . .
# パッケージのインストール
RUN npm install
# サーバーの起動
CMD ["npm", "run", "start"]
ではDocker ComposeにAPコンテナの定義を記述していきます。
services:
api:
build: ./api # ビルドコンテキスト
volumes:
- ./api:/app
ports: # 動作確認用
- 3000:3000
depends_on: # 依存関係の設定
db:
condition: service_healthy
db:
image: mysql
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_USER=user
- MYSQL_PASSWORD=password
- MYSQL_DATABASE=mydb
volumes:
- db-store:/var/lib/mysql
- ./db/script:/docker-entrypoint-initdb.d
healthcheck: # 起動チェック用の処理
test: "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD" # 確認処理
interval: 5s # 実行感覚
timeout: 5s # タイムアウト
retries: 10 # リトライ回数
volumes:
db-store:
api
としてAPコンテナを定義しています。
DBコンテナでは使用していない記述について説明をします。
build
: Dockerfileを使用する場合にビルドコンテキストを指定するports
: ホストとのポートの関連付け(動作確認用 後に削除)depends_on
: 依存関係を表し、コンテナの起動順を制御
depends_on
は記載の通り、コンテナの起動順を制御するためのものです。
APコンテナはDBコンテナのMySQLに接続しにいくため、MySQL自体が起動していないとエラーになります。
そのためMySQLが起動していることを確認してから、APコンテナを起動するようにします。
depends_on
には、依存関係にあるコンテナと起動条件であるcondition
を設定します。
condition
のservice_healthy
は、対象コンテナが正常であることをチェックしてから起動することを表します。
ただし、正常であるかどうかをどのように判断するかは、対象コンテナにhealthcheck
で定義する必要があります。
healthcheck
には、test
としてチェック用のコマンドを定義します。
今回はMySQLを使用しているため、mysqladmin ping
コマンドで起動を確認します。
その他の項目についてはDockerfileのコメントの通りで、詳細は割愛します。
ではコンテナを起動して動作を確認します。
起動後、ブラウザからhttp://localhost:3000/message
、http://localhost:3000/user
にアクセスしてDBに登録されているデータが表示されることを確認します。
$ docker compose up -d
[+] Building 0.1s (9/9) FINISHED docker:desktop-linux
=> [api internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 119B 0.0s
=> [api internal] load .dockerignore 0.0s
=> => transferring context: 34B 0.0s
=> [api internal] load metadata for docker.io/library/node:latest 0.0s
=> [api 1/4] FROM docker.io/library/node:latest 0.0s
=> [api internal] load build context 0.0s
=> => transferring context: 71.18kB 0.0s
=> CACHED [api 2/4] WORKDIR /app 0.0s
=> CACHED [api 3/4] COPY . . 0.0s
=> CACHED [api 4/4] RUN npm install 0.0s
=> [api] exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:8d49ea4a5a67f999cae5fd0a44db91505ed73267dac3a19e4e 0.0s
=> => naming to docker.io/library/my-app-api 0.0s
[+] Running 4/4
✔ Network my-app_default Created 0.0s
✔ Volume "my-app_db-store" Created 0.0s
✔ Container my-app-db-1 Healthy 0.0s
✔ Container my-app-api-1 Started 0.0s
[{"id":1,"message":"Hello."},{"id":2,"message":"Good morning."},{"id":3,"message":"Good evening."}]
[{"id":1,"name":"Taro"},{"id":2,"name":"Jiro"},{"id":3,"name":"Saburo"}]
Dockerfileから作成されたイメージはmy-app-api
という名前で作成されます。
前述の通り、docker compose down
では通常イメージが削除されませんが、Dockerfileで作成したイメージを削除したい場合は--rmi local
を指定します。
$ docker compose down --volumes --rmi local
補足
上記ではDockerfileを使用しましたが、以下のようにDocker Composeだけでも同様の構成を定義できます。
services:
api:
image: node: latest
working_dir: /app # 作業ディレクトリの指定
volumes:
- ./api:/app
ports:
- 3000:3000
command: sh -c "npm install && npm run start" # 実行コマンド(パッケージインストール + サーバー起動)
depends_on:
db:
condition: service_healthy
db:
image: mysql
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_USER=user
- MYSQL_PASSWORD=password
- MYSQL_DATABASE=mydb
volumes:
- db-store:/var/lib/mysql
- ./db/script:/docker-entrypoint-initdb.d
healthcheck:
test: "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"
interval: 5s
timeout: 5s
retries: 10
volumes:
db-store:
WEBコンテナ
最後にWEBコンテナを作成します。 APIから取得したデータを表示するページをNGINXを使って表示できるようにします。 要領はこれまでと同じです。
/web
ディレクトリを作成し、以下のファイルを作成します。
my-app/
┝ api/
┝ db/
┝ web/
│ ┝ html/
│ │ └ index.html
│ └ nginx/conf
└ docker-compose.yml
<input type="button" value="Get Message" onclick="getMessage()" />
<div id="result"></div>
<script>
getMessage = async () => {
const response = await fetch("/api/message");
const data = await response.text();
document.getElementById('result').innerText = data;
}
</script>
server {
listen 80;
location /api {
proxy_pass http://api:3000;
}
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
index.html
は表示するWEBページになります。
Get Message
ボタンを押すことで、APIで定義した/api/message
にアクセスして取得したデータを表示するようにしています。
nginx.conf
はNGINXの設定ファイルです。
注目すべきは4〜6行目の記述です。
WEBページからAPIにアクセスするということは、ホストからAPIコンテナにアクセスすることになります。
ポートを設定すればアクセスはできるのですが、次はCORSの問題があります。
CORSでは、WEBページから異なるオリジン(プロトコル、ホスト、ポートのセット)へのアクセスが制限されます。
そこでリバースプロキシを利用します。
WEBページから直接APIにアクセスするのではなく、一度WEBサーバーを経由してAPIにアクセスするようにします。
WEBサーバー、つまりWEBコンテナからAPI(APコンテナ)にアクセスするため、アクセス情報としてサービス名が利用できます。
またサーバー同士の通信となるためCORSの問題も解決できます。
4〜6行目の記述によって、WEBサーバーの/api
へアクセスした場合は、APコンテナの3000番ポートに転送されるという設定になります。
ではこれらのファイルを用いてWEBコンテナの定義をします。
services:
web:
image: nginx:latest
volumes:
- ./web/html:/usr/share/nginx/html
- ./web/nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- 8080:80
depends_on:
api:
condition: service_healthy
api:
build: ./api
volumes:
- ./api:/app
depends_on:
db:
condition: service_healthy
healthcheck:
test: "curl -f http://localhost:3000/health"
interval: 10s
timeout: 10s
retries: 3
db:
image: mysql
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_USER=user
- MYSQL_PASSWORD=password
- MYSQL_DATABASE=mydb
volumes:
- db-store:/var/lib/mysql
- ./db/script:/docker-entrypoint-initdb.d
healthcheck:
test: "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"
interval: 5s
timeout: 5s
retries: 10
volumes:
db-store:
volumes
には先ほど作成したindex.html
とnginx.conf
をNGINXの該当ディレクトリ、ファイルにバインドマウントしています。
WEBコンテナは、APコンテナが起動を確認してから起動したいため、depends_on
を定義しています。
APコンテナの起動の確認方法として、チェック用のAPI(/health
)を一つ用意し、レスポンスが帰ってきた場合に正常として判断します。
app.get("/health", (req, res) => {
res.status(200).send("OK");
});
ではコンテナを起動して動作確認を行います。
すべてのコンテナが起動したら、http://localhost:8080
にアクセスし、index.html
が表示されることを確認します。
その後Get Message
ボタンを押し、/api/message
の内容が表示されることを確認します。
その他のコマンド
最後に上記で説明できなかったDocker Composeの一部のコマンドについて補足しておきます。
images / ps
images
はDocker Composeで使用しているイメージの一覧を表示します。
CONTAINER REPOSITORY TAG IMAGE ID SIZE
my-app-api-1 my-app-api latest b76b16435ed3 1.11GB
my-app-db-1 mysql latest e68e2614955c 638MB
my-app-web-1 nginx latest 760b7cbba31e 192MB
ps
はDocker Composeで作成したコンテナの一覧を表示します。
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
my-app-api-1 my-app-api "docker-entrypoint.s…" api 24 seconds ago Up 18 seconds (healthy)
my-app-db-1 mysql "docker-entrypoint.s…" db 24 seconds ago Up 23 seconds (healthy) 3306/tcp, 33060/tcp
my-app-web-1 nginx:latest "/docker-entrypoint.…" web 24 seconds ago Up 7 seconds 0.0.0.0:8080->80/tcp
up / down
up
とdown
は上記でも説明した通り、コンテナの作成・起動、停止・削除をするコマンドですが、
引数でサービス名を指定することで個別に実行できます。
$ docker compose up -d web
$ docker compose down web
create / rm
create
はコンテナの作成を行います。up
と異なり起動はされません。
rm
はコンテナの削除を行います。down
と異なり停止はされず、またネットワークなどの関連情報も削除されません。
それぞれサービス名を指定することで、個別に実行可能です。
$ docker compose create
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
my-app-api-1 my-app-api "docker-entrypoint.s…" api 10 seconds ago Created
my-app-db-1 mysql "docker-entrypoint.s…" db 10 seconds ago Created
my-app-web-1 nginx:latest "/docker-entrypoint.…" web 10 seconds ago Created
$ docker compose rm
start / stop
start
はコンテナの起動を行います。
stop
はコンテナの停止を行います。
それぞれサービス名を指定することで、個別に実行可能です。
$ docker compose start
$ docker compose stop
pause / unpause
pause
はコンテナを一時停止します。
unpause
は一時停止したコンテナを再開します。
それぞれサービス名を指定することで、個別に実行可能です。
$ docker compose pause
$ docker compose unpause
logs
logs
はDocke Composeで起動しているコンテナのログを表示します。
サービス名を指定することで、コンテナ毎のログを出力できます。
$ docker compose logs web
ocker compose logs api
api-1 |
api-1 | > api@1.0.0 start
api-1 | > nodemon index.js
api-1 |
api-1 | [nodemon] 3.1.0
api-1 | [nodemon] to restart at any time, enter `rs`
api-1 | [nodemon] watching path(s): *.*
api-1 | [nodemon] watching extensions: js,mjs,cjs,json
api-1 | [nodemon] starting `node index.js`
ls
Docker Composeで起動しているプロジェクトの一覧を表示します。
$ docker compose ls
NAME STATUS CONFIG FILES
my-app running(3) /Users/Jhon/my-app/docker-compose.yml