【Docker】Django + React + Nginx + MySQL 環境を構築する

Docker

今回の進め方として、
まずローカルで Django REST Framework + React +SQLight 環境を構築し、
後半ではWebサーバを Nginx + Gunicorn、データベースを MySQL に変更し、すべてDockerで構築していきます。

なお、Windows環境のため、Macを使用されている方はコマンドを適宜読み替えていただければと思います。

DjangoでAPIを作成

適当な作業ディレクトリで以下を実行

mkdir backend frontend  
cd backend
type nul > requirements.txt


「requirements.txt」の中身

asgiref==3.3.4
coverage==5.5
django==3.0.7
django-cors-headers==3.7.0
djangorestframework==3.10
pytz==2021.1
sqlparse==0.4.1
gunicorn==20.1.0


仮想環境の作成、有効化

python -m venv venv
venv\Scripts\activate


パッケージのインストール

pip install -r requirements.txt


Djangoプロジェクト、アプリの作成

django-admin startproject backend .
django-admin startapp api


「settings.py」の修正

"""
Django settings for backend project.

Generated by 'django-admin startproject' using Django 3.0.7.

For more information on this file, see
Django settings | Django documentation | Django
For the full list of settings and their values, see
Settings | Django documentation | Django
""" import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'unp9y_gh5-*mq6sgu%0c^^46=&d206*#!q$%%hux0w8m6gc=9#' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', #追加 'rest_framework', 'api.apps.ApiConfig', 'corsheaders', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', #追加 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'backend.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'backend.wsgi.application' # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ LANGUAGE_CODE = 'ja' #修正 TIME_ZONE = 'Asia/Tokyo' #修正 USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ STATIC_URL = '/static/' #追加 REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', ] } CORS_ALLOWED_ORIGINS = [ "http://127.0.0.1:3000", "http://localhost:3000", "http://127.0.0.1", "http://localhost", ]


モデルの作成

from django.db import models

class Task(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name
python manage.py makemigrations
python manage.py migrate


データベースにサンプルデータの保存

mkdir fixtures
type nul > fixtures/tasks.json
[
    {   
        "model": "api.task",
        "fields": {
            "id": 1,
            "name": "task1"
        }
    },
    {   
        "model": "api.task",
        "fields": {
            "id": 2,
            "name": "task2"
        }
    },
    {   
        "model": "api.task",
        "fields": {
            "id": 3,
            "name": "task3"
        }
    }
]
python manage.py loaddata fixtures/tasks.json



「serializers.py」を作成

type nul > api\serializers.py
from rest_framework import serializers
from .models import Task

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ('id', 'name')


「views.py」の修正

from rest_framework import generics
from .models import Task
from .serializers import TaskSerializer

class TaskList(generics.ListAPIView):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer


「api」ディレクトリに「urls.py」を作成

type nul > api\urls.py
from django.urls import path
from .views import TaskList

urlpatterns = [
    path('task/', TaskList.as_view())
]


「backend」ディレクトリの「urls.py」を修正

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls'))
]


サーバーを起動

python manage.py runserver

「http://127.0.0.1:8000/api/task」にアクセスすると、一覧が取得できます。

Reactでフロントを作成

新しくコマンドラインを立ち上げてReactプロジェクトを作成、サーバーを起動

cd frontend
npx create-react-app .
npm start


「App.js」を以下のように修正して「http://127.0.0.1:3000/」にアクセスすると、
さきほどDjango側で作成したデータを取得できていることがわかります。

import React, { useEffect, useState } from 'react'

function App() {
    const [tasks, setTasks] = useState([])

    useEffect(() => {
        const apiUrl = 'http://127.0.0.1:8000/api/'
        fetch(`${apiUrl}task/`)
            .then(data => data.json())
            .then(res => {
                setTasks(res)
            })
    }, [setTasks])
    return (
        <ul>
            {tasks.map(task => (
                <li key={task.id}>{task.name}</li>
            ))}
        </ul>
    );
}

export default App;

Dockerで構築

新しくコマンドラインを立ち上げて「docker-compose.yml」を作成

type nul > docker-compose.yml
version: '3'

services:
  backend:
    build:
      context: ./backend
    command: gunicorn backend.wsgi --bind 0.0.0.0:8000
    ports:
      - "8000:8000"
    depends_on:
      - mysql

  frontend:
    build:
      context: ./frontend
    volumes:
      - react_build:/react/build

  nginx:
    image: nginx:latest
    ports:
      - 80:8080
    volumes:
      - ./nginx/nginx-setup.conf:/etc/nginx/conf.d/default.conf:ro
      - react_build:/var/www/react
    depends_on:
      - backend
      - frontend

  mysql:
    image: mysql:5.7.22
    restart: always
    environment:
      MYSQL_DATABASE: sample
      MYSQL_USER: dbuser
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - 33066:3306

volumes:
  react_build:
  db_data:

djangoの「settings.py」のデータベース情報を修正

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'sample',
        'USER': 'dbuser',
        'PASSWORD': 'password',
        'HOST': 'mysql',
        'PORT': '3306',
    }
}


「nginx-setup.conf」の作成

mkdir nginx
type nul > nginx\nginx-setup.conf
upstream django {
    server backend:8000;
}

server {
  listen 8080;

  location / {
    root /var/www/react;
    try_files $uri /index.html;
  }

  location /api/ {
    proxy_pass http://django;
    proxy_set_header Host $http_host;
  }

  location /admin/ {
    proxy_pass http://django;
    proxy_set_header Host $http_host;
  }
}


Djangoプロジェクトに「Dockerfile」「.dockerignore」を作成

type nul > backend\Dockerfile
type nul > backend\.dockerignore

FROM python:3.8
ENV PYTHONUNBUFFERED 1
WORKDIR /backend
COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
COPY . .

venv


「requirements.txt」に「mysqlclient」を追加

.
.
.
mysqlclient==2.0.1


さきほどSQLiteに「task1」「task2」「task3」を入れたため、
データベースの変更がわかりやすいようtask名を変えておきます。

[
    {   
        "model": "api.task",
        "fields": {
            "id": 1,
            "name": "task4"
        }
    },
    {   
        "model": "api.task",
        "fields": {
            "id": 2,
            "name": "task5"
        }
    },
    {   
        "model": "api.task",
        "fields": {
            "id": 3,
            "name": "task6"
        }
    }
]


Reactプロジェクトに「Dockerfile」を作成

type nul > frontend\Dockerfile
FROM node:14.18-alpine
WORKDIR /react
COPY . .
RUN npm run build


「App.js」のAPIURLのポートを削除

import React, { useEffect, useState } from 'react'

function App() {
    const [tasks, setTasks] = useState([])

    useEffect(() => {
        const apiUrl = 'http://127.0.0.1/api/' #修正
        fetch(`${apiUrl}task/`)
            .then(data => data.json())
            .then(res => {
                setTasks(res)
            })
    }, [setTasks])
    return (
        <ul>
            {tasks.map(task => (
                <li key={task.id}>{task.name}</li>
            ))}
        </ul>
    );
}

export default App;


コンテナの起動

docker-compose build
docker-compose up


Docker DeskTopなどでコンテナの起動状況を確認しておきましょう。
(「frontend」コンテナ以外が running になっていればOK)


問題がなければ、データベースを変更したため新しくデータを入れなおします。
コマンドラインを立ち上げて以下を実行

docker-compose exec backend bash

# コンテナ内
python manage.py migrate
python manage.py loaddata fixtures/tasks.json


そして「http://127.0.0.1/(3000ポート無し)」にアクセスすると、
先ほど保存したデータが表示されているかと思いますので、これでコンテナ同士の連携が取れていることが確認できました。



今回は以上になります。
ご覧いただきありがとうございました!

コメント

コンタクトフォーム

    タイトルとURLをコピーしました