Dockerで3層アーキテクチャを構築しよう
このハンズオンでは、Dockerを使って3層アーキテクチャのノートアプリを構築する方法について実際にハンズオン形式で手を動かしながら体験します。
- 3層アーキテクチャの概念と各層の役割
- データベース層(MySQL)コンテナの構築
- アプリケーション層(Python FastAPI)コンテナの構築
- プレゼンテーション層(HTML/CSS/JavaScript)コンテナの構築
- Dockerネットワークによるコンテナ間通信
1. 事前準備
Gitの基礎知識を習得していることを前提とします。自信のない方は先に以下の講座を実施してください。
この講座のハンズオンでは、以下のツールやアカウントが必要です。まだ準備できていない場合は、リンク先の手順に沿って準備をお願いします。
2. 3層アーキテクチャとは
この講座では、アプリケーションを3つの層に分割して設計する「3層アーキテクチャ」という設計パターンをベースに、コンテナによるWebアプリケーションをデプロイしていきます。
| 層 | 役割 | 具体例 | よくある構成 |
|---|---|---|---|
| プレゼンテーション層 | フロントエンド | ユーザが直接触れる部分。ユーザからの入力を受け付けてアプリケーション層に渡す | HTML/CSS、React、Next.js など |
| アプリケーション層 | バックエンド | ビジネスロジックを処理する層。リクエストを受け取り、データベース層と通信して結果を返す | FastAPI(Python)、Rails(Ruby)、Go など |
| データ層 | データベース | データを永続的に保存する層。アプリケーション層からのリクエストに応じてデータの読み書きを行う | MySQL、PostgreSQL、MongoDB など |
今回のハンズオンでは、プレゼンテーション層にHTML/CSS/JavaScript(Nginx)、アプリケーション層にPythonのFastAPI、データ層にMySQLを使用します。
3層アーキテクチャには多くの利点があります。まず、各層を別々のチームで独立して開発できるため、大規模なプロジェクトでも効率的に作業を進められます。また、1つの層を変更しても他の層に影響しにくいため、保守性が向上します。さらに、負荷が高い層だけを増強できるスケーラビリティも大きなメリットです。セキュリティの面でも、データベースを直接外部に公開せず、アプリケーション層経由でアクセスさせることで、より安全なシステムを構築できます。
3層アーキテクチャを構築するには、各層に対応した環境を用意する必要があります。これらを個別にインストールするとバージョンの競合や設定の問題が発生しがちですが、コンテナを使えば各層を独立した環境として簡単に構築できます。コンテナは「各層を独立させる」という3層アーキテクチャの考え方と非常に相性が良いため、現代のWebアプリケーション開発で広く採用されています。
今回構築する3層アーキテクチャの全体構成を以下の図に示します。
flowchart LR
User["ユーザ<br>ブラウザ"]
subgraph Docker
direction LR
subgraph note-network
API["api-container<br>FastAPI<br>ポート 8000"]
DB["db-container<br>MySQL<br>notedb"]
API -->|SQL| DB
end
Front["front-container<br>Nginx<br>ポート 3000"]
end
User -->|"http://localhost:3000"| Front
User -->|"http://localhost:8000<br>(API呼び出し)"| API
3. ハンズオンの概要
このハンズオンでは、3つのコンテナを作成し、Dockerネットワークで接続してシンプルな「ノートアプリ」を構築します。
3.1 今回の構成
今回は、プレゼンテーション層にHTML/CSS/JavaScript(Nginx)、アプリケーション層にPythonのFastAPI、データベース層にMySQLを用いた3層アーキテクチャを構成していきます。
| 層 | 技術 | 特徴 |
|---|---|---|
| プレゼンテーション層 | HTML/CSS/JavaScript(Nginx) | シンプルな静的ファイル構成。Nginxで高速に配信 |
| アプリケーション層 | FastAPI | Pythonの高速なWebフレームワーク。シンプルな記述でREST APIを構築できる |
| データベース層 | MySQL | 世界で最も普及しているオープンソースのリレーショナルデータベース |
3.2 画面構成
ノート一覧画面のみのシンプルな構成です。画面上部にノート追加フォーム、下部にノート一覧を配置します。各ノートには「詳細」「編集」「削除」ボタンがあります。

3.3 APIの構成
アプリケーション層は「API」として機能を提供します。以下のようなAPIのエンドポイントを用意します。
| エンドポイント | メソッド | 説明 |
|---|---|---|
| /notes | GET | ノート一覧を取得 |
| /notes/{id} | GET | ノート詳細を取得 |
| /notes | POST | 新しいノートを作成 |
| /notes/{id} | PUT | ノートを更新 |
| /notes/{id} | DELETE | ノートを削除 |
3.4 データベース項目
データベース層で用意するデータベースのテーブル構成です。ノートを管理するシンプルな構成です。
| 項目 | 型 | 説明 |
|---|---|---|
| id | INT | ノートの一意識別子(自動採番) |
| title | VARCHAR(100) | ノートのタイトル |
| content | TEXT | ノートの内容 |
| created_at | TIMESTAMP | 作成日時 |
| updated_at | TIMESTAMP | 更新日時 |
4. プロジェクトの準備
4.1 サンプルリポジトリのクローン
このハンズオンで使用するノートアプリケーションのソースコードは、GitHubのサンプルリポジトリに用意しています。コマンドプロンプト(Windows)またはターミナル(Mac)を開き、任意の場所で以下のコマンドを実行します。
git clone https://github.com/gevanni-academy/sample-note-api.git
クローンが完了したら、Visual Studio Codeを起動し、「ファイル」→「フォルダーを開く」から、クローンしたsample-note-apiフォルダを開いてください。以降の操作は、Visual Studio Codeのターミナルから行います。
4.2 フォルダ構成の確認
クローンしたリポジトリのフォルダ構成は以下のようになっています。
sample-note-api
├── api/
│ ├── Dockerfile
│ ├── main.py
│ ├── models.py
│ ├── database.py
│ ├── requirements.txt
│ └── routers/
│ └── notes.py
└── front/
├── Dockerfile
├── index.html
├── style.css
└── script.js
各フォルダは3層アーキテクチャの各層に対応しています。
| フォルダ | 層 | 内容 |
|---|---|---|
| api/ | アプリケーション層 | FastAPIのソースコード、Dockerfile、requirements.txt |
| front/ | プレゼンテーション層 | HTML/CSS/JavaScriptのフロントエンド、Nginx用のDockerfile |
データベース層はMySQLの公式イメージをそのまま使うため、ソースコードのフォルダはありません。
| 💡 ポイント |
|---|
リポジトリにはcompose.yamlやdb/フォルダも含まれていますが、これらは後続の講座で使用するファイルです。この講座では使用しないため、無視してください。 |
| 💡 ポイント |
|---|
| PythonやFastAPIの詳しい説明は省略します。コードの詳細については「PythonでREST APIを作ろう」講座に記載があります。この講座では、Dockerの操作に集中します。 |
5. データベース層の構築
最初に、データベース層となるMySQLコンテナを構築します。
5.1 イメージの取得
MySQLの公式イメージをDocker Hubから取得します。
docker pull mysql:latest
| 💡 ポイント |
|---|
latestは最新バージョンを指すタグです。本番環境では、mysql:8.0のようにバージョンを明示的に指定することが推奨されます。 |
イメージが取得されたことを確認します。
docker image ls
以下のようにmysqlが表示されていれば成功です。
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql latest xxxxxxxxxxxx x days ago xxx MB
5.2 コンテナの起動
MySQLコンテナを起動します。
docker container run -d --name db-container \
-v notedb-data:/var/lib/mysql \
--env MYSQL_ROOT_PASSWORD=rootpass \
--env MYSQL_DATABASE=notedb \
--env MYSQL_USER=noteuser \
--env MYSQL_PASSWORD=notepass \
mysql:latest
| オプション | 値 | 説明 |
|---|---|---|
| -d | - | バックグラウンドでコンテナを実行する |
| --name | db-container | コンテナに名前を付ける |
| -v notedb-data:/var/lib/mysql | - | データ永続化用のボリュームをマウントする |
| --env MYSQL_ROOT_PASSWORD | rootpass | MySQLのrootユーザのパスワード(必須) |
| --env MYSQL_DATABASE | notedb | 起動時に自動作成するデータベース名 |
| --env MYSQL_USER | noteuser | アプリケーション用のユーザ名 |
| --env MYSQL_PASSWORD | notepass | アプリケーション用ユーザのパスワード |
| 💡 ポイント |
|---|
-v notedb-data:/var/lib/mysqlでボリュームをマウントすることで、コンテナを削除してもデータベースのデータが保持されます。コンテナを再作成しても、同じボリュームをマウントすればデータを引き継ぐことができます。 |
| 📝 MySQL環境変数について |
|---|
MySQLの公式イメージは、環境変数を使って初期設定を行うことができます。MYSQL_DATABASEを指定すると、コンテナ起動時にデータベースが自動作成されます。MYSQL_USERとMYSQL_PASSWORDで作成したユーザは、MYSQL_DATABASEで指定したデータベースへのアクセス権限が自動的に付与されます。 |
コンテナが起動していることを確認します。
docker container ls
STATUSがUpになっていれば、コンテナは正常に起動しています。
5.3 テーブルの作成
MySQLコンテナに接続し、ノート用のテーブルを作成します。
docker container exec -it db-container mysql -u noteuser -pnotepass notedb
mysql> の待ち受け状態になれば、MySQLにログインできています。
MySQLのプロンプトが表示されたら、以下のSQLを実行してテーブルを作成します。
CREATE TABLE notes (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
テーブルが作成されたことを確認します。
SHOW TABLES;
以下のようにnotesテーブルが表示されれば成功です。
+------------------+
| Tables_in_notedb |
+------------------+
| notes |
+------------------+
MySQLからログアウトします。
exit
| 💡 ポイント |
|---|
実はこのアプリケーションには、APIコンテナ起動時にテーブルを自動作成する仕組み(lifespan関数)が組み込まれています。ここで手動でテーブルを作成したのは、MySQLコンテナに接続してSQLを実行する操作を体験するためです。 |
| 💡 ポイント |
|---|
起動したdb-containerは、この後のハンズオンでも使用するため、削除せずに残しておいてください。 |
6. アプリケーション層の構築
次に、アプリケーション層となるFastAPIコンテナを構築します。クローンしたリポジトリのapiフォルダにソースコードとDockerfileが用意されています。
| 📝 REST APIとは |
|---|
| REST APIは、HTTPメソッド(GET、POST、PUT、DELETEなど)を使ってデータをやり取りする仕組みです。URLでリソース(データ)を指定し、メソッドで操作を指定します。例えば「GET /notes」でノート一覧を取得、「POST /notes」で新規ノートを作成します。Webアプリケーションでフロントエンドとバックエンドが通信する標準的な方法として広く使われています。 |
6.1 Dockerfileの確認
api/Dockerfileを開いてみましょう。以下の内容が記述されています。
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py models.py database.py ./
COPY routers/ ./routers/
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
このDockerfileで行っている各命令について、1行ずつ解説します。
FROM python:3.11-slim
ここでは、ベースイメージとしてPython 3.11の軽量版(slim)を使用することを指定しています。slimは不要なパッケージを省いた軽量なイメージで、コンテナサイズを小さくできます。
WORKDIR /app
ここでは、コンテナ内の作業ディレクトリを/appに設定しています。以降の命令(COPY、RUNなど)はこのディレクトリを基準に実行されます。
COPY requirements.txt .
ここでは、ホストマシンのrequirements.txtをコンテナの作業ディレクトリ(/app)にコピーしています。末尾の.は現在の作業ディレクトリを意味します。
RUN pip install --no-cache-dir -r requirements.txt
ここでは、requirements.txtに記載されたライブラリをインストールしています。--no-cache-dirオプションを付けることで、キャッシュを保存せずイメージサイズを削減しています。
COPY main.py models.py database.py ./
COPY routers/ ./routers/
ここでは、アプリケーションのソースコードをコンテナの作業ディレクトリにコピーしています。main.py、models.py、database.pyを作業ディレクトリ直下に、routers/フォルダをサブフォルダごとコピーしています。
EXPOSE 8000
ここでは、コンテナがポート8000で待ち受けることを宣言しています。これはドキュメント的な役割であり、実際のポート公開はdocker runの-pオプションで行います。
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
ここでは、コンテナ起動時に実行するコマンドを指定しています。uvicornサーバを使ってmain.py内のapp(FastAPIアプリケーション)をポート8000で起動します。
6.2 イメージのビルド
Visual Studio Codeのターミナルからapiフォルダに移動し、Dockerイメージをビルドします。
cd api
docker image build -t api-image .
| オプション | 値 | 説明 |
|---|---|---|
| -t | api-image | イメージに名前を付ける |
| . | - | 現在のディレクトリのDockerfileを使用する |
イメージが作成されたことを確認します。
docker image ls
以下のようにapi-imageが表示されていれば成功です。
REPOSITORY TAG IMAGE ID CREATED SIZE
api-image latest xxxxxxxxxxxx x seconds ago xxx MB
mysql latest xxxxxxxxxxxx x days ago xxx MB
6.3 コンテナの起動
APIコンテナを起動します。apiフォルダで以下のコマンドを実行します。
Macの場合:
docker container run -d -p 8000:8000 --name api-container \
-v $(pwd):/app \
-e DB_HOST=db-container \
-e DB_USER=noteuser \
-e DB_PASSWORD=notepass \
-e DB_NAME=notedb \
api-image
Windowsの場合(PowerShell):
docker container run -d -p 8000:8000 --name api-container -v ${PWD}:/app -e DB_HOST=db-container -e DB_USER=noteuser -e DB_PASSWORD=notepass -e DB_NAME=notedb api-image
| オプション | 値 | 説明 |
|---|---|---|
| -d | - | バックグラウンドでコンテナを実行する |
| -p | 8000:8000 | ホストの8000番ポートをコンテナの8000番ポートに転送する |
| --name | api-container | コンテナに名前を付ける |
| -v $(pwd):/app | - | 現在のフォルダをコンテナの/appにバインドマウントする |
| -e DB_HOST | db-container | 接続先のデータベースコンテナ名 |
| -e DB_USER | noteuser | データベース接続用のユーザ名 |
| -e DB_PASSWORD | notepass | データベース接続用のパスワード |
| -e DB_NAME | notedb | 接続先のデータベース名 |
| 💡 ポイント |
|---|
-v $(pwd):/appでバインドマウントすることで、現在のフォルダ(apiフォルダ)がコンテナ内の/appにマウントされます。ソースコードを変更した場合、コンテナを再起動すると変更が反映されます。 |
コンテナが起動していることを確認します。
docker container ls
STATUSがUpになっていれば、コンテナは正常に起動しています。
6.4 APIの動作確認
APIサーバが起動したことを確認します。
curl http://localhost:8000/health
以下のレスポンスが返ってくれば、APIサーバは正常に動作しています。
{"status":"healthy"}
| 💡 ポイント |
|---|
| この時点では、APIコンテナとDBコンテナは同じネットワークに属していないため、データベースとの接続は失敗します。次のセクションでネットワークを設定し、コンテナ間通信を可能にします。 |
7. ネットワークの設定
APIコンテナとDBコンテナが通信できるように、Dockerネットワークを作成して両方のコンテナを接続します。
7.1 ネットワークの作成
ノートアプリ用のネットワークを作成します。
docker network create note-network
ネットワークが作成されたことを確認します。
docker network ls
以下のようにnote-networkが表示されていれば成功です。
NETWORK ID NAME DRIVER SCOPE
xxxxxxxxxxxx note-network bridge local
xxxxxxxxxxxx bridge bridge local
xxxxxxxxxxxx host host local
xxxxxxxxxxxx none null local
7.2 コンテナをネットワークに接続
既に起動しているコンテナを、作成したネットワークに接続します。
まず、DBコンテナを接続します。
docker network connect note-network db-container
次に、APIコンテナを接続します。
docker network connect note-network api-container
7.3 ネットワーク接続の確認
ネットワークの詳細を確認し、両方のコンテナが接続されていることを確認します。
docker network inspect note-network
以下のような出力結果が表示されます。Containersセクションに、db-containerとapi-containerの両方が表示されていれば、正しく接続されています。
[
{
"Name": "note-network",
"Id": "a1b2c3d4e5f6...",
"Created": "2026-01-15T10:00:00.000000000Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"abc123...": {
"Name": "api-container",
"EndpointID": "def456...",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
},
"xyz789...": {
"Name": "db-container",
"EndpointID": "ghi012...",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
7.4 API動作確認
ネットワーク接続が完了したら、APIからデータベースへの接続をテストします。
まず、ノートを1件作成します。
curl -X POST http://localhost:8000/notes \
-H "Content-Type: application/json" \
-d '{"title": "Docker Notes", "content": "Learning the basics of containers"}'
以下のようなレスポンスが返れば、ノートの作成に成功しています。
{"title":"Docker Notes","content":"Learning the basics of containers","id":1,"created_at":"2026-01-01T10:00:00","updated_at":"2026-01-01T10:00:00"}
次に、作成したノートを取得します。
curl http://localhost:8000/notes
先ほど作成したノートが取得できれば、APIコンテナからDBコンテナへの通信が正常に行われています。
[{"title":"Docker Notes","content":"Learning the basics of containers","id":1,"created_at":"2026-01-01T10:00:00","updated_at":"2026-01-01T10:00:00"}]
8. プレゼンテーション層の構築
最後に、プレゼンテーション層となるWebフロントエンドコンテナを構築します。クローンしたリポジトリのfrontフォルダにHTML、CSS、JavaScriptファイルとDockerfileが用意されています。
8.1 Dockerfileの確認
front/Dockerfileを開いてみましょう。以下の内容が記述されています。
FROM nginx:alpine
COPY . /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
各命令について解説します。
FROM nginx:alpine
ここでは、軽量なNginxイメージをベースとして使用しています。Alpineベースのイメージはサイズが小さく、起動も高速です。
COPY . /usr/share/nginx/html/
ここでは、frontフォルダ内のすべてのファイル(index.html、style.css、script.js)をNginxのデフォルトの公開ディレクトリにコピーしています。
EXPOSE 80
ここでは、コンテナがポート80で待ち受けることを宣言しています。
CMD ["nginx", "-g", "daemon off;"]
ここでは、Nginxをフォアグラウンドで実行するコマンドを指定しています。daemon off;オプションにより、コンテナが終了せずに動作し続けます。
8.2 イメージのビルド
ターミナルでfrontフォルダに移動してからDockerイメージをビルドします。
cd ../front
docker image build -t front-image .
イメージが作成されたことを確認します。
docker image ls
以下のようにfront-imageが表示されていれば成功です。
REPOSITORY TAG IMAGE ID CREATED SIZE
front-image latest xxxxxxxxxxxx x seconds ago xxx MB
api-image latest xxxxxxxxxxxx x minutes ago xxx MB
mysql 8.0 xxxxxxxxxxxx x days ago xxx MB
8.3 コンテナの起動
フロントコンテナを起動します。frontフォルダで以下のコマンドを実行します。
Macの場合:
docker container run -d -p 3000:80 --name front-container \
-v $(pwd):/usr/share/nginx/html \
front-image
Windowsの場合(PowerShell):
docker container run -d -p 3000:80 --name front-container -v ${PWD}:/usr/share/nginx/html front-image
| オプション | 値 | 説明 |
|---|---|---|
| -d | - | バックグラウンドでコンテナを実行する |
| -p | 3000:80 | ホストの3000番ポートをコンテナの80番ポートに転送する |
| --name | front-container | コンテナに名前を付ける |
| -v $(pwd):/usr/share/nginx/html | - | 現在のフォルダをコンテナにバインドマウントする |
| 💡 ポイント |
|---|
-v $(pwd):/usr/share/nginx/htmlでバインドマウントすることで、現在のフォルダ(frontフォルダ)がコンテナ内にマウントされます。ホスト側でindex.htmlを編集すると、ブラウザを再読み込みするだけで変更が反映されます。 |
コンテナが起動していることを確認します。
docker container ls
STATUSがUpになっていれば、コンテナは正常に起動しています。
| 💡 ポイント |
|---|
フロントエンドコンテナはnote-networkに接続していません。これは、フロントエンドのJavaScriptがユーザのブラウザ上で動作するためです。ブラウザからAPIへのリクエストは、http://localhost:8000(ホストマシン)を経由して行われ、Dockerネットワークを通じた直接通信は行いません。そのため、フロントエンドコンテナをDockerネットワークに参加させる必要はありません。 |
8.4 動作確認
Webブラウザを開き、以下のURLにアクセスします。
http://localhost:3000
「ノートアプリ」というページが表示され、先ほどcurlで作成したノートが一覧表示されれば成功です。
以下の機能を試してみてください。
- ノート作成 - 「新規ノート」欄にタイトルと内容を入力し、「追加」ボタンをクリック
- 詳細表示 - 「詳細」ボタンをクリックすると、ノートの全文と作成日・更新日が表示される
- 編集 - 「編集」ボタンをクリックすると、タイトルと内容を編集できる
- 削除 - 「削除」ボタンをクリックすると、ノートが削除される
9. 不要リソースの削除
ハンズオンが終わったら、作成したリソースを削除します。
9.1 コンテナの停止
まず、起動中のすべてのコンテナを停止します。
docker container stop front-container api-container db-container
9.2 コンテナの削除
停止したコンテナを削除します。
docker container rm front-container api-container db-container
コンテナが削除されたことを確認します。
docker container ls -a
front-container、api-container、db-containerが一覧から消えていれば、削除は完了です。
9.3 イメージの削除
作成したイメージを削除します。
docker image rm front-image api-image mysql:latest
イメージが削除されたことを確認します。
docker image ls
front-image、api-image、mysql:latestが一覧から消えていれば、削除は完了です。
9.4 ボリュームの削除
データベース用に作成したボリュームを削除します。
docker volume rm notedb-data
ボリュームが削除されたことを確認します。
docker volume ls
notedb-dataが一覧から消えていれば、削除は完了です。
9.5 ネットワークの削除
作成したDockerネットワークを削除します。
docker network rm note-network
ネットワークが削除されたことを確認します。
docker network ls
note-networkが一覧から消えていれば、削除は完了です。
NETWORK ID NAME DRIVER SCOPE
xxxxxxxxxxxx bridge bridge local
xxxxxxxxxxxx host host local
xxxxxxxxxxxx none null local
9.6 プロジェクトフォルダの削除(任意)
クローンしたプロジェクトフォルダが不要な場合は、Visual Studio Codeでフォルダを閉じた後、エクスプローラー(Windows)やFinder(Mac)からsample-note-apiフォルダを削除してください。
10. まとめ
このハンズオンでは、Dockerを使って3層アーキテクチャのノートアプリを構築する方法を体験しました。
- 3層アーキテクチャは、プレゼンテーション層・アプリケーション層・データ層の3つに分離する設計パターン
- データベース層として、MySQLコンテナを構築しノート用のテーブルを作成した
- アプリケーション層として、Dockerfileからイメージをビルドし、FastAPIコンテナを起動した
- Dockerネットワークを作成し、コンテナ間通信を可能にした
- プレゼンテーション層として、Dockerfileからイメージをビルドし、Nginxコンテナを起動した
- 同じネットワーク内のコンテナは、コンテナ名で通信できる
- 環境変数を使うことで、設定を柔軟に変更できる