DockerでFastAPI+Next.js+MySQLの環境を構築してみる(Windows)

フロントアプリ作成

アプリケーションの定義ファイル(YAML)作成

type nul > docker-compose.yml

下記の内容で編集。

services:
  frontend:
    image: node:22-alpine
    working_dir: /app
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app

フロントアプリ作成

docker compose run --rm frontend sh -c "npx create-next-app@latest . --typescript"

アプリケーションの定義ファイル(YAML)編集

services:

  db:
    build: ./db
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"

  backend:
    build:
      context: ./backend
    environment:
      DATABASE_URL: mysql+pymysql://odaneko:odaneko@db:3306/odaneko
    ports:
      - "8000:8000"

  frontend:
    build:
      context: ./frontend
    ports:
      - "3000:3000"
    environment:
      BACKEND_API_URL: http://backend:8000
    volumes:
      - ./frontend:/app
      - /app/node_modules
      - /app/.next

volumes:
  mysql-data:

backendコンテナ(FastAPI)作成

Dockerファイル作成

ディレクトリ作成。

mkdir backend

Dockerファイル作成。

type nul > backend/Dockerfile

下記の内容で編集。

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

requirements.txt作成

type nul > backend/requirements.txt

fastapi
uvicorn[standard]
sqlalchemy
pymysql

.dockerignore

type nul > backend/.dockerignore

__pycache__
*.pyc
.venv
.git
.gitignore

frontendコンテナ(Next.js)作成

Dockerファイル作成

ファイル作成。

type nul > frontend/Dockerfile

下記の内容で編集。

FROM node:22-alpine

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

EXPOSE 3000

CMD ["npm", "run", "start"]

.dockerignore

type nul > frontend/.dockerignore

node_modules
.next
.git
.gitignore
Dockerfile
npm-debug.log*

DBコンテナ(MySQL)作成

Dockerファイル作成

ディレクトリ作成。

mkdir db

ファイル作成。

type nul > db/Dockerfile

下記の内容で編集。

FROM mysql/mysql-server:8.0

ENV MYSQL_DATABASE=odaneko \
  MYSQL_USER=odaneko \
  MYSQL_PASSWORD=odaneko \
  MYSQL_ROOT_PASSWORD=odaneko \
  TZ=Asia/Tokyo

COPY ./my.cnf /etc/my.cnf
RUN chmod 644 /etc/my.cnf

type nul > db/my.cnf

[mysqld]
# default
skip-host-cache
skip-name-resolve
datadir = /var/lib/mysql
socket = /var/lib/mysql/mysql.sock
secure-file-priv = /var/lib/mysql-files
user = mysql

pid-file = /var/run/mysqld/mysqld.pid

# character set / collation
character_set_server = utf8mb4
collation_server = utf8mb4_ja_0900_as_cs_ks

# timezone
default-time-zone = SYSTEM
log_timestamps = SYSTEM

# Error Log
log-error = mysql-error.log

# Slow Query Log
slow_query_log = 1
slow_query_log_file = mysql-slow.log
long_query_time = 1.0
log_queries_not_using_indexes = 0

# General Log
general_log = 1
general_log_file = mysql-general.log

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4

API作成

APIの処理が記載されたプログラムを作成。

type nul > backend/main.py

下記の内容で編集。
DB接続確認を行うAPIです。

from fastapi import FastAPI
from sqlalchemy import create_engine, text
import os

app = FastAPI()

DATABASE_URL = os.environ["DATABASE_URL"]

engine = create_engine(DATABASE_URL)

@app.get("/db-test")
def db_test():
    with engine.connect() as connection:
        result = connection.execute(text("SELECT 1 AS result"))
        row = result.fetchone()

    return {
        "result": row.result
    }

フロントページ作成

アプリ作成

ページ作成

mkdir frontend/app/db-test

type nul > frontend/app/db-test/page.tsx

"use client";

import { useState } from "react";

export default function Home() {
  const [result, setResult] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  const executeTest = async () => {
    setLoading(true);
    setError("");
    setResult(null);

    try {
      const response = await fetch("/api/db-test");

      if (!response.ok) {
        throw new Error("API request failed");
      }

      const data = await response.json();

      setResult(data.result);
    } catch (error) {
      console.error(error);
      setError("APIの呼び出しに失敗しました");
    } finally {
      setLoading(false);
    }
  };

  return (
    <main>
      <h1>Python API 接続テスト</h1>

      <button onClick={executeTest} disabled={loading}>
        {loading ? "実行中..." : "DB検索を実行"}
      </button>

      {result !== null && (
        <p>
          DB検索結果:{result}
        </p>
      )}

      {error && (
        <p>
          エラー:{error}
        </p>
      )}
    </main>
  );
}

API作成

mkdir frontend/app/api/db-test

type nul > frontend/app/api/db-test/route.ts

import { NextResponse } from "next/server";

export async function GET() {
  const response = await fetch(
    `${process.env.BACKEND_API_URL}/db-test`
  );

  const data = await response.json();

  return NextResponse.json(data);
}

コンテナビルド・起動

docker compose up -d --build

Dockerデスクトップでも起動を確認。

環境が構築できたか確認

下記のURLを開いて、テストページが表示されるか確認。
http://localhost:3000/db-test

以上になります。
お読み頂き、ありがとうございました。