【Mac&Windows】Docker,Django,Nginx,PostgreSQLで環境構築

## はじめに
- 参考文献にある記事を参考に環境構築を進めていました
- しかしながら、AppleシリコンのMacBookとWindows環境の両方で動作するには少し修正が必要かもしれないと思い、本記事を投稿します
- 参考した記事に含まれる内容も多く含まれますが、ご了承ください

## 前提
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326131/ff18071e-b5cb-e2ad-4797-4b2cf58040be.png)



- フレームワークはDjango
- DBはPostgreSQL
- アプリケーションサーバはGunicorn
- WebサーバはNginx
- DB側のコンテナ名はdb、django側のコンテナ名はapp、nginx側はwebにします
- 開発用はdocker-compose.yml(Django+PostgreSQL)として作成します
- 本番用はdocker-compose.prod.yml(Django+PostgreSQL+Nginx)として作成します
- 作成するプロジェクト名はdjangopjにしていますが別のプロジェクト名で作成する際はdjangopjと置き換えて作成してください

## ディレクトリ構成
初回作成時のディレクトリ構成は以下の通りです
containersフォルダを作成し、その中にdjango、nginxのフォルダを作成してください
また、nginxのフォルダの中にconf.dフォルダも作成します

```terminal
tree
.
├── containers
│   ├── django
│   │   └── Dockerfile
│   └── nginx
│       ├── Dockerfile
│       └── conf.d
│           └── default.conf
├── .gitignore
├── .env
├── .env.prod
├── entrypoint.sh
├── docker-compose.prod.yml
├── docker-compose.yml
└── requirements.txt
```

## 各ファイルに必要なコードを記入しよう
### Dockerfile(django)

```Dockerfile:containers/django/Dockerfile
# syntax=docker/dockerfile:1
# Pythonのイメージを指定
FROM python:3.9
# PYTHONDONTWRITEBYTECODEとPYTHONUNBUFFEREDはオプション
# pycファイル(および__pycache__)の生成を行わないようにする
ENV PYTHONDONTWRITEBYTECODE=1
# 標準出力・標準エラーのストリームのバッファリングを行わない
ENV PYTHONUNBUFFERED=1
# コンテナのワークディレクトリを/codeに指定
WORKDIR /code
# ローカルのrequirements.txtをコンテナの/codeフォルダ直下に置く
COPY ../../requirements.txt /code/
# コンテナ内でpipをアップグレード
RUN pip install --upgrade pip
# pip install -r requirements.txtを実行
RUN pip install -r requirements.txt
# ソースコードをコンテナにコピー
COPY ../.. /code/
# ヒストリを保存するディレクトリを作成
RUN mkdir /root/myhistory
# ヒストリファイルを作成
RUN touch /root/myhistory/.history
# ヒストリファイルのパーミッションを変更
RUN chmod 600 /root/myhistory/.history
```
### nginx
```Dockerfile:containers/nginx/Dockerfile
FROM nginx:1.21-alpine

# ローカルのdefault.confをコンテナにコピー
COPY containers/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
```

### default.conf(Nginx用の設定ファイル)
```nginx:containers/nginx/conf.d/default.conf
# Django(Gunicorn)の8000番ポートとつなぐ
upstream django {
    # サーバにDjangoのコンテナ名を指定。今回はapp
    # ポートはDjangoのコンテナの8000番ポート
    server app:8000;
}

server {
    # HTTPの80番ポートを指定
    listen 80;
    server_name 0.0.0.0;

    # プロキシ設定
    # 実際はNginxのコンテナにアクセスしてるのをDjangoにアクセスしてるかのようにみせる
    location / {
        proxy_pass http://django;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect off;
    }

    # djangoの静的ファイル(HTML、CSS、Javascriptなど)を管理
    location /static/ {
		alias /static/;
	}
}
```
### docker-compose.yml(開発用)
```docker-compose.yml
services:
  db:
    image: postgres
    ports:
      - "5432:5432"
    # M1チップでも動くように
    # Intel Macの場合あってもなくても動く
    platform: linux/x86_64
    volumes:
      - db_data:/var/lib/postgresql/data
    env_file:
        - .env
  app:
    build:
      context: .
      dockerfile: containers/django/Dockerfile
    # ボリュームを指定
    # ローカルのカレントディレクトリをコンテナの/codeにマウントする
    # ローカルの/staticをコンテナの/staticにマウントする
    # ローカルのapp-bashhistoryをコンテナの/root/myhistoryにマウントする
    volumes:
      - .:/code
      - ./polls/static:/static
      - app-bashhistory:/root/myhistory
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      - db
    # シェルスクリプトを実行
    command: >
      sh -c "
      python manage.py makemigrations --noinput &&
      python manage.py migrate --noinput &&
      python manage.py collectstatic --noinput &&
      if [ \$DEBUG = 'True' ]; then
        python manage.py runserver 0.0.0.0:8000;
      else
        gunicorn djangopj.wsgi:application --bind 0.0.0.0:8000;
      fi
      "
volumes:
  db_data:
  static:
  app-bashhistory:
```
### docker-compose.prod.yml(本番用)
```docker-compose.prod.yml
services:
  db:
    image: postgres
    ports:
      - "5432:5432"
    # M1チップでも動くように
    # Intel Macの場合あってもなくても動く
    platform: linux/x86_64
    volumes:
      - db_data:/var/lib/postgresql/data
    env_file:
        - .env.prod
  app:
    build:
      context: .
      dockerfile: containers/django/Dockerfile
    # ボリュームを指定
    # ローカルのカレントディレクトリをコンテナの/codeにマウントする
    # ローカルの/staticをコンテナの/staticにマウントする
    # ローカルのapp-bashhistoryをコンテナの/root/myhistoryにマウントする
    volumes:
      - .:/code
      - ./polls/static:/static
      - app-bashhistory:/root/myhistory
    # 8000番ポートをNginx側が接続できるよう開く
    expose:
      - "8000"
    env_file:
      - .env.prod
    depends_on:
      - db
    # シェルスクリプトを実行
    command: >
      sh -c "
      python manage.py makemigrations --noinput &&
      python manage.py migrate --noinput &&
      python manage.py collectstatic --noinput &&
      if [ \$DEBUG = 'True' ]; then
        python manage.py runserver 0.0.0.0:8000;
      else
        gunicorn djangopj.wsgi:application --bind 0.0.0.0:8000;
      fi
      "
  web:
    # コンテナ名をwebに指定
    container_name: web
    # NginxのDockerfileをビルドする
    build:
      # ビルドコンテキストはカレントディレクトリ
      context: .
      dockerfile: containers/nginx/Dockerfile
    # ボリュームを指定
    # ローカルの/staticをコンテナの/staticにマウントする
    volumes:
      - ./polls/static:/static
    # ローカルの80番ボートをコンテナの80番ポートとつなぐ
    ports:
      - "80:80"
    # 先にappを起動してからwebを起動する
    depends_on:
      - app
volumes:
  db_data:
  static:
  app-bashhistory:
```
### requirements.txt
- Django
- psycopg2
- Gunicorn

をDjangoのコンテナにインストールするので記載します
```requirements.txt
Django>=3.0,<4.0
psycopg2>=2.8
gunicorn>=19.9.0,<20.0
```
### .env(開発用)
PostgreSQLのrootユーザのパスワードなどをdocker-compose.ymlやDjangoのsettings.pyに書くのは危険なので.envファイルを使います
今回は開発用のためDEBUG=Trueにする必要があります
.gitignore(後述)があることで.envファイルはGitHubに上がることはありません
今回は以下のような内容にします
```.env
POSTGRES_DB=postgres
POSTGRES_NAME=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
# SECRET_KEYに任意の値を入力
SECRET_KEY=django
ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
# 開発環境のためTrue
DEBUG=True
```
### .env.prod(本番用)
続いて本番環境用の環境変数を設定します
実際に本番環境の環境変数はAWSのパラメータストアなどに保存しますが今回は.env.prodを使います
本番用のためDEBUG=Falseにする必要があります

```.env.prod
POSTGRES_DB=postgres
POSTGRES_NAME=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
# SECRET_KEYについては本番環境では推測されない値に変更しておきましょう
SECRET_KEY=xdmjx=9l@x)-jitznpb^%yjn6h=7g)$%e8_+1s)o+8o79csa4d
ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
# 開発環境のためFalse
DEBUG=False
```
### .gitignore
GitHubの公式サイトからPythonの.gitignoreを作成します
.envと.env.prodは.gitignoreにないため追記します
また、migrationとstaticは実際の開発ではGitの管理下に置く必要がないため、こちらも.gitignoreに追記します(migrattionとstaticはプログラムによって生成されることがある)
```.gitignore
.env
.env.prod

# ignore static files
static/
# ignore migration files
migrations/
```
### docker-composeでDocker imageを作成しよう(初回)
プロジェクトを新規作成する際はプロジェクト名と作成するディレクトリを指定して以下のコマンドを実行します
今回はdjangopjのプロジェクトをカレントディレクトリに作成します
```terminal:terminal
docker-compose -f docker-compose.prod.yml run app django-admin startproject djangopj .
```

実行するとローカルのディレクトリ構成は以下のようになります
data/db内はファイルが非常に多いので省略します
```terminal
tree
.
├── containers
│   ├── django
│   │   └── Dockerfile
│   └── nginx
│       ├── Dockerfile
│       └── conf.d
│           └── default.conf
├── djangopj
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── .env
├── .env.prod
├── .gitignore
├── docker-compose.prod.yml
├── docker-compose.yml
├── manage.py
├── requirements.txt
└── static
```



### settings.pyのDATABASESを変更
DjangoのデフォルトのDBはSQliteのため、PostgreSQLに変更する必要がある
```settings.py
from pathlib import Path
# osのモジュールをインポート
import os

# [・・・]

# SECRET_KEYを.envから取得
SECRET_KEY = os.environ.get("SECRET_KEY")

# DEBUGを.envから取得
# envファイルにTrue、Falseと書くとDjangoがString型と認識してしまいます
# os.environ.get("DEBUG") == "True"を満たすとboolean型のTrueになり、
# env内のDEBUGがTrue以外ならFalseになります
DEBUG = os.environ.get("DEBUG") == "True"

# ALLOWED_HOSTSを.envから取得
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS").split(" ")

# [・・・]

# PostgreSQLのパラメータを.envから取得
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('POSTGRES_NAME'),
        'USER': os.environ.get('POSTGRES_USER'),
        'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
        'HOST': 'db',
        'PORT': 5432,
    }
}


# [・・・]

# 言語を日本語に設定
LANGUAGE_CODE = "ja"
# タイムゾーンをAsia/Tokyoに設定
TIME_ZONE = "Asia/Tokyo"

# [・・・]

# STATIC_ROOTを設定
# Djangoの管理者画面にHTML、CSS、Javascriptが適用されます
STATIC_ROOT = "/static/"
STATIC_URL = "/static/"
```

### ローカル環境起動
```
docker compose -f docker-compose.prod.yml up --build -d
```
- -f オプションをつけると指定したファイルを使ってコンテナを起動します
- --build オプションをつけるとイメージを再ビルドします
- -d オプションをつけるとバックグラウンドで起動します

### localhostにアクセスしてみよう
ホストからNginxのポートに接続します
ブラウザにアクセスし、NginxのポートからDjangoのポートへアクセスできます
以下のページが表示されます

このようにNot Foundと表示される場合はルーティングの設定をしてないからでコンテナ自体はうまく起動しています
![スクリーンショット 2022-10-16 21.03.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625980/9c45c85e-c291-7157-d7d4-1c5e8c1a8655.png)

localhost/adminにアクセスし、以下の画面が出たら成功です

:::note
![スクリーンショット 2022-10-16 21.06.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625980/0befd4ff-54f8-27ed-8c05-233f132cae34.png)
:::

DEBUG=Trueに設定した場合は下記の画像が表示されます

:::note
![スクリーンショット 2022-08-17 21.10.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625980/ba5f46c3-2f7d-5fd9-3a21-9294b85be146.png)
:::

## 参考文献
https://qiita.com/shun198/items/f6864ef381ed658b5aba