【Django初心者向け】Djangoの開発環境をDocker Composeでさくっと作る(ローカル環境編)

こちらの投稿は[Django Advent Calendar 2021](https://qiita.com/advent-calendar/2021/django)の24日目の記事です。

#はじめに
Djangoのアドベントカレンダーに初心者向けの記事が少ないように感じたので、「年末年始の休みに初めてDjangoに触ってみよう」と思ってる人達に向けて記事を書いてみました。
####書いた人
IT業界の経験は10年以上あるものの、ずっとメインフレームコンピュータなどのレガシーな環境でCOBOLの開発をやってきて、最近はCOBOLすら書いていなくて完全にエクセルで資料作ってる人です。DjangoもPythonも業務で使用した経験はなく初心者です。

####記事を書こうと思った経緯
いつの間にか公開してから２年が経っていたのですが、以前Djangoで初めてWebアプリを作りました。時間が経ってしまったのでかなりの部分を忘れているので、Djangoの開発を振り返るついでに記事を書いてみようと思いました。
以前作成したアプリのリンクは以下です。

Web
[starchart](https://django-starchart.herokuapp.com/)

Github
[hayatek/star-chart](https://github.com/hayatek/star-chart)

Githubからリポジトリのスター数を取ってきてグラフにして比較するというアプリです。
フレームワークや言語の人気度はGithubのスター数だけでは計れないというのは重々承知しているのですが、一つの指標にはなるかなと思い作ったものです。
例えば、上記のアプリでDjangoとFlaskとRailsのスター数の推移を見てみます。

![グラフ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256640/b2c0b1e1-ffbc-7c97-0924-0b543070b4a9.png)

スター数の伸びだけを見るとFlaskやRailsよりもDjangoは伸びていることがわかります。

初めてのWebアプリ作成でDocker、GraphQLのAPIにアクセス、バッチの定期実行など色々詰め込んで大変だったのですが、大したメンテもせずに奇跡的に2年間稼働してくれています。この時にDjangoを使ってみた経験から初心者でも分かり易かったのでオススメです。

####初めてのDjangoアプリ開発を阻むもの
とは言うものの自分もそうだったのですが、「Djangoでアプリを作ろう」と思っても意外とその前の**Pythonのインストールの方法が何通りも(venv、pyenv、anaconda...)あったりしてそこで躓いたりします。**
Dockerを使うとそこら辺の悩みも解決できるのとDockerを使うこと自体はそんなにハードルが高くないと思います。

今回は時間がないのもあり、Dockerを使ってDjangoを動かすところまでに絞ってもう一度やってみようと思います。
冒頭で書いた通り自分自身もDjango初心者なので、間違いや足りない部分があれば補足頂けると助かります。それではいきましょう！

#今回作るもの
DockerとDocker Composeを使ってローカル環境でDjangoを稼働するところまでをやっていきます。

- Django
- Docker
- Docker-compose

#参考にした動画
英語ですが、以前アプリを作った時もこちらの方のUdemy動画を参考にしてかなり分かりやすかったので途中までこちらの動画に沿ってやっていきます。
[Deploying Django with Docker Compose](https://www.youtube.com/watch?v=mScd-Pc_pX0&t=228s)
上記のブログ記事(英文)は[こちら](https://londonappdeveloper.com/deploying-django-with-docker-compose/)
#前提
筆者の環境がMacのためMacをベースに解説させて頂きます。(恐らくWindowsでも同じようにできるはずですが、確かめる術がなくすみません。。)
MacOS Catalina(10.15.7)
Docker Desktop(4.1.1)

###Docker Desktopのインストール
事前にDocker Desktopのインストールが必要です。
Docker Desktop Mac版インストール方法の解説はこちらが詳しいです。

[【入門】はじめての Docker Desktop for Mac のインストールと CentOS の仮想環境構築のセットアップ]
(https://qiita.com/gahoh/items/92217e0a887bb81e3155)


#まずはさくっと開発環境を作る
それでは早速やっていきましょう。

本当にさくっと環境を作るためにあらかじめ用意したテンプレートをクローンしてDocerFile、docker-compose、requirements.txtファイルをローカルにコピーします。

テンプレートファイル(Github)
[hayatek/docker-django-app](https://github.com/hayatek/docker-django-app)

上記のリポジトリをクローンします。

```
git clone git@github.com:hayatek/docker-django-app.git
```

そうするとdocker-django-appフォルダ（プロジェクトのルートフォルダ）は以下の構成になります。

![project root.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256640/7bc76331-d814-9a3b-306d-258d5b9be319.png)
###同階層に.dockerignoreファイルを作成
.dockerignoreファイルを以下の内容で作成します。
これはDockerをビルドする時に不要なファイルをDockerイメージに追加しないようにするためものです。これはなくてもDockerは動きます。

```.dockerignore.
# Git
.git
.gitignore

# Docker
.docker

# Python
app/__pycache__/
app/*/__pycache__/
app/*/*/__pycache__/
app/*/*/*/__pycache__/
.env/
.venv/
venv/

# Local PostgreSQL data
data/
```
#プロジェクトのビルド
ターミナルでプロジェクトのルートフォルダに移動したら以下を実行をしてください。Dockerイメージをビルドします。

```
cd docker-django-app #プロジェクトのルートフォルダに移動
docker-compose build
```
エラーがなければDocker環境の構築は完了です。

次にDjangoのプロジェクトを作成します。

```
docker-compose run --rm app sh -c "django-admin startproject app ."
```

Djangoのプロジェクトを作成した後のプロジェクト構成は以下の通りです。

![Tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256640/0646f443-8a5d-7992-baa0-039b6646ff44.png)

###SECRET_KEYの設定
appフォルダの中に作成された**settings.py**を編集します。
1. import osを追加
2. SECRET_KEYをos.environ.get()関数経由でのアクセスにする

```settings.py
import os #追加
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY') #変更
```
今回はローカル環境のためSECRET_KEYはdocker-compose.ymlに以下の通り設定しています。

```docker-compose.yml
environment:
      - SECRET_KEY=devsecretkey
```

#アプリの作成
Django上でcore(アプリ名は任意の名前で大丈夫です。)というアプリを作成します。
ターミナルで以下のコマンドを実行します。

```
docker-compose run --rm app sh -c "python manage.py startapp core"
```

settings.pyのINSTALLED_APPSにcoreというアプリを追加します。

```settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'core', #追加
]
```
データベースの記述もpostgreを使うように変更します。

```settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'HOST': os.environ.get('DB_HOST'),
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASS'),
    }
}
```

###Modelの設定
次にapp/core/models.pyを開きます。
"# Create your models here."を削除して以下を追加します。

```app/core/models.py
from django.db import models


class Sample(models.Model):              #追加
    attachment = models.FileField()　　　 #追加
```

app/core/admin.pyを開いて以下を追加します。

```app/core/admin.py
from django.contrib import admin
from core.models import Sample #追加

admin.site.register(Sample)　#追加
```

上記の設定が完了したらターミナルで以下のコマンドを実行します。

```
docker-compose run --rm app sh -c "python manage.py makemigrations"
```
####Wait for DB
Dockerを使ってDjangoとPostgreを動かすと以下の問題があるようです。

>Even though adding the depends_on block to the app service ensures that the db service starts before the app, it doesn’t ensure that the database has been initialised.
This can lead to an issue where Django crashes because it tries to connect to the database before it’s fully up and running.
アプリサービスにdepends_onブロックを追加すると、アプリの前にdbサービスが起動するようになりますが、データベースが初期化されたことは保証されません。
これは、Django が完全に起動する前にデータベースに接続しようとしてクラッシュする 問題を引き起こしかねません。

この問題を回避するために以下の設定をしていきます。
まず下記のパスに空の__init__.pyファイルを作成します。

app/core/management/__init__.py
app/core/management/commands/__init__.py

またapp/core/management/commands/配下に以下の内容でwait_for_db.pyを作成します。

```wait_for_db.py
"""
Django command to wait for the database to be available.
"""
import time

from psycopg2 import OperationalError as Psycopg2OpError

from django.db.utils import OperationalError
from django.core.management.base import BaseCommand


class Command(BaseCommand):
    """Django command to wait for database."""

    def handle(self, *args, **options):
        """Entrypoint for command."""
        self.stdout.write('Waiting for database...')
        db_up = False
        while db_up is False:
            try:
                self.check(databases=['default'])
                db_up = True
            except (Psycopg2OpError, OperationalError):
                self.stdout.write('Database unavailable, waiting 1 second...')
                time.sleep(1)

        self.stdout.write(self.style.SUCCESS('Database available!'))
```
Wait for DBの設定はこれで完了です。

####Docker Composeを実行
ターミナルで以下のコマンドでDocker Composeを実行します。

```
docker-compose up
```
ブラウザで**http://127.0.0.1:8000/**を入力します。

![Djnago.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256640/0ad23ae3-c703-1b79-eb73-8b4b49a3a83a.png)

Djangoが4.0で稼働しました！

#解説
とりあえずDjangoが動いたところでそれぞれのファイルの内容を説明します。
![Tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256640/0646f443-8a5d-7992-baa0-039b6646ff44.png)
**requirements.txt**
まずrequirements.txtには使用するライブラリなどのバージョンを定義します。今回はせっかくなので2021年12月現在最新のバージョン4.0を使います。

```requirements.txt
Django==4.0
```
**Dockerfile**
次にDockerfileです。
[Docker HUb](https://hub.docker.com/layers/python/library/python/3.10.1-alpine3.15/images/sha256-2bc97c9353ddefb8f7dd6e4b60711e4f9eff1c31ee3f7b4a19abe85a259b7421?context=explore)にあるPythonのalpineイメージを使用します。Pythonのインストールもこの中に含まれています。
Runステートメントに下記の通り追記します。

```Dockerfile.
FROM python:3.10.1-alpine3.15

ENV PYTHONUNBUFFERED 1

COPY ./requirements.txt /requirements.txt
COPY ./app /app

WORKDIR /app
EXPOSE 8000

RUN python -m venv /py && \
    /py/bin/pip install --upgrade pip && \
    apk add --update --no-cache postgresql-client && \
    apk add --update --no-cache --virtual .tmp-deps \
        build-base postgresql-dev musl-dev && \
    /py/bin/pip install -r /requirements.txt && \
    apk del .tmp-deps && \
    adduser --disabled-password --no-create-home app

ENV PATH="/py/bin:$PATH"

USER app
```
**docker-compose**
次はdocker-composeファイルです。
command:にwat_for_dbの記載とローカルとコンテナ共に8000番ポートを指定しています。
db:ではpostgres:13-alpineを使用しています。

```docker-compose.yml
version: '3.9'

services:
  app:
    build:
      context: .
    command: >
      sh -c "python manage.py wait_for_db &&
             python manage.py migrate &&
             python manage.py runserver 0.0.0.0:8000"
    ports:
      - 8000:8000
    volumes:
      - ./app:/app
    environment:
      - SECRET_KEY=devsecretkey
      - DB_HOST=db
      - DB_NAME=devdb
      - DB_USER=devuser
      - DB_PASS=changeme
    depends_on:
      - db
  
  db:
    image: postgres:13-alpine
    environment:
      - POSTGRES_DB=devdb
      - POSTGRES_USER=devuser
      - POSTGRES_PASSWORD=changeme
```
Postgreの設定に伴ってrequirements.txtにも以下のpostgreドライバーを追加しています。

```requirements.txt
psycopg2>=2.8.6,<2.9
```
#終わりに
色々と説明が足りてない気がしますが、Djangoは稼働しましたでしょうか。アプリが稼働したら後はアプリの中身を作っていくのみです。
皆様お疲れ様でした。
また、最後まで記事をお読み頂きありがとうございました。