🐷

TiDBローカル環境構築⑤

に公開

TiDBのローカル環境の構築を5回目行なっていきます。
前回TiDB環境のDjango構築が出来ましたが、今回はDockerでREST APIの構築を行っていきます。

コンテナ環境のツリー構成

DB構築時のツリー構造は以下のようにしてます。
今回はcoreフォルダの追加とその配下のファイル追加、後はapi/Dockerfileの編集、api/entrypoint.shの追加、setting.pyの変更を行いました。

.
├── .env.api
├── docker-compose.yml
└── src
    ├── infra
    │   └── db
    │       ├── Dockerfile
    │       ├── data
    │       │   └── test_db.sql
    │       ├── docker-compose.yml
    │       └── entrypoint.sh
    └── www
        └── api
            ├── Dockerfile
            ├── docker-compose.yml
            ├── entrypoint.sh
            ├── project
            │   ├── core
            │   │   ├── __init__.py
            │   │   ├── admin.py
            │   │   ├── apps.py
            │   │   ├── models.py
            │   │   ├── serializers.py
            │   │   ├── urls.py
            │   │   └── views.py
            │   ├── manage.py
            │   └── project
            │       ├── __init__.py
            │       ├── asgi.py
            │       ├── settings.py
            │       ├── urls.py
            │       └── wsgi.py
            └── requirements.txt

ソースコードを作成

各ファイルの追加と編集を行います。
Python 3.11-slim をベースに、依存パッケージと仮想環境を構築し、entrypoint.sh でアプリ起動準備を行う Django 用 Docker イメージを作成する設定に変更しました。

./src/www/api/Dockerfile
FROM python:3.11-slim

WORKDIR /app/project

RUN apt-get update && apt-get install -y --no-install-recommends netcat-openbsd && rm -rf /var/lib/apt/lists/*

COPY requirements.txt ./

RUN python -m venv /venv \
 && /venv/bin/pip install --no-cache-dir -r requirements.txt

COPY . .

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENV VIRTUAL_ENV=/venv
ENV PATH="/venv/bin:${PATH}"

EXPOSE 8000
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/bin/bash"]



Django アプリ起動前にDB接続待機とマイグレーションを実行するスクリプトを作成します。

./src/www/api/entrypoint.sh
#!/bin/sh
set -e

echo "Waiting for DB..."
until nc -z "$DB_HOST" "$DB_PORT"; do
  sleep 1
done

echo "Running migrations..."
python manage.py makemigrations core || true
python manage.py migrate --noinput
exec "$@" 



./src/www/api/project/core/init.pyはパッケージとして認識させるための空ファイルの為何も書いてませんが、作成します。



Django管理画面の作成を行います。

./src/www/api/project/core/admin.py
from django.contrib import admin
from .models import Customer

@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
    list_display = ("id", "name", "email", "created_at")
    search_fields = ("name", "email")
    ordering = ("-id",)



Djangoアプリ「core」の設定クラスを定義し、デフォルトの主キー型をBigAutoFieldに設定して一ます。

./src/www/api/project/core/app.py
from django.apps import AppConfig

class CoreConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "core"



顧客の名前・メールアドレス・作成日時を管理するCustomerモデルを定義し、文字列表現に名前とメールを返す処理になります。

./src/www/api/project/core/models.py
from django.db import models

class Customer(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField(unique=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.name} <{self.email}>"



Customerモデルの全フィールドをシリアライズ・デシリアライズするためのCustomerSerializerを定義します。

./src/www/api/project/core/serializers.py
from rest_framework import serializers
from .models import Customer

class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ["id", "name", "email", "created_at"]



CustomerViewSetをルーターに登録し、/customersエンドポイントを提供するURL設定を定義します。

./src/www/api/project/core/url.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import CustomerViewSet

router = DefaultRouter()
router.register(r"customers", CustomerViewSet)

urlpatterns = [
    path("", include(router.urls)),
]



CustomerモデルのCRUD機能を提供するViewSetを定義し、新しい順でデータを取得する設定を実装しました。

./src/www/api/project/core/views.py
from rest_framework import viewsets
from .models import Customer
from .serializers import CustomerSerializer

class CustomerViewSet(viewsets.ModelViewSet):
    queryset = Customer.objects.all().order_by("-id")
    serializer_class = CustomerSerializer



setting.pyにREST APIのモジュールの追加を行います。

./src/www/api/project/project/setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # 新しく追加
    'core', # 新しく追加
]

# 新しく追加
REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.AllowAny"],
}


これは、Django のAPI エンドポイント /api/ を紐づけるルーティング設定を追加します。

./src/www/api/project/project/url.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    # 新しく追加
    path("api/", include("core.urls"))
]



上記でREST API環境の構築は完了しました。


以下コマンドを実行し、API用のコンテナにアクセスします。

docker exec -it ubuntu-dev-container sh



以下のコマンドでDjangoのサーバーを起動します。

python manage.py runserver 0.0.0.0:8000



別ターミナル開きます。
テストで以下のコマンドを実行します。

curl -X POST http://127.0.0.1:8000/api/customers/ \
  -H "Content-Type: application/json" \
  -d '{"name":"テスト三郎","email":"saburo@example.com"}'

サーバーのログで以下が出力されてたら疎通OKになりました

[10/Aug/2025 23:43:51] "POST /api/customers/ HTTP/1.1" 201 106

詰まった箇所

./src/www/api/entrypoint.sh で exec "$@" を記載しない場合、DB待機とマイグレーション処理が終わった時点でシェルスクリプトが終了し、フォアグラウンドで動くメインプロセスが存在しなくなるため、Docker コンテナは数秒後に停止してしまいます。

まとめ

  • 目的:TiDBローカル環境でDjango REST APIをDocker上に構築
  • 変更点:core アプリ追加、Dockerfile 編集、entrypoint.sh 作成、settings.py 修正
  • Dockerfile:Python仮想環境構築+依存インストール+起動スクリプト組み込
  • entrypoint.sh:DB待機→マイグレーション→アプリ起動(exec "$@" 必須)
  • アプリ実装:Customer モデルとCRUD APIを作成し /api/customers/ で操作可能に
  • 動作確認:curl でPOSTし201レスポンスでOK
  • 注意点:exec "$@" を書かないとコンテナが自動終了
コラボスタイル Developers

Discussion