Django2環境をDocker使って整える

# はじめに

興味本位で新しいフレームワークを試してみようと始めた『[Laravel環境構築](https://qiita.com/imp555sti/items/affa9800ce20a4d34c45)』

Docker構築にも慣れてきたので
改めてDjango2環境の開発環境としてDockerで作成してみました。


## 環境
- Windows10 Pro
- docker for windows (docker desctop community) (ver.2.1.0.2)
- VSCode

## Django2環境
Dockerの構成はこんな感じ

![nginx-python-postgres.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324255/15d65a39-2650-b467-22af-22b96cc83f40.png)


- django2_nginx:1.17.3 (based `nginx:1.17.3`)
- django2_python:3.7.4 (based `python:3.7.4`)
- django2_postgres:9.6.15 (based `postgres:9.6.15`)


# dockerの定義

ローカルのディレクトリ構成はこんな感じにしました。

```text
├─ laravel/
　　　├─ postgres/
　　　│　　└─ Dockerfile       # postgres用Dockerfile
　　　├─ nginx/
　　　│　　├─ etc/
　　　│　　│　　├─ conf.d/
　　　│　　│　　│　　└─ default.conf    # nginxサイト定義
　　　│　　│　　└─ nginx.conf          # nginx基本定義
　　　│　　└─ Dockerfile               # nginx用Dockerfile
　　　├─ python/
　　　│　　├─ apps/
　　　│　　│　　│　：
　　　│　　│　　└─ setup.sh    # migrate,createsuperuser用スクリプト
　　　│　　└─ Dockerfile      # php-fpm用Dockerfile
　　　├─ .gitignore           # pythonのキャッシュ除外用gitignore
　　　├─ docker-compose.yaml  # 全てのdocker定義
```

## docker-compose.yaml
全ての要、docker-compose.yamlはこんな形になりました。
ポートが微妙におかしいのは、先日の[Laravevl](https://qiita.com/imp555sti/items/affa9800ce20a4d34c45)と共存したいためです(笑)

- nginxはポート8088でlistenしています。
- nginxはdjango2プロジェクトの`apps/static`を`/opt/apps/static`に腹持ちさせました。
- postgresは5432番でlistenしています。
- postgresはデータ永続化のためdbvolumeコンテナを作成しました。
- pythonは`/opt/apps`にdjango2プロジェクトをあらかじめ生成したものを腹持ちさせています。
- Django2はgunicornによりnginxからの通信を3031番で待ち受けします。

```yaml:docker-compose.yaml
version: '3.3'
services:
  postgres:
    container_name: django2_postgres
    build: ./postgresql/
    image: django2_postgres:9.6.15
    restart: always
    ports:
      - 5432:5432
    volumes:
      - dbvolume:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=sample_db
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres@1234
      - PGDATA=/var/lib/postgresql/data

  python:
    container_name: django2_python
    build: ./python/
    image: django2_python:3.7.4
    links:
      - postgres
    volumes:
      - ./python/apps:/opt/apps
      - ./python/app/static:/opt/apps/static
    expose:
      - 3031
    environment:
      - DJANGO_ADMIN_USER=admin
      - DJANGO_ADMIN_EMAIL=admin@localhost.localdomain
      - DJANGO_ADMIN_PASSWORD=django2@1234
    command: >
      /bin/bash -c
      "./setup.sh && 
       gunicorn wsgi -b 0.0.0.0:3031"
    depends_on:
      - postgres

  nginx:
    container_name: django2_nginx
    build: ./nginx/
    image: django2_nginx:1.17.3
    ports:
      - 8088:80
    links:
      - python
    depends_on:
      - python
    volumes:
      - ./python/app/static:/opt/apps/static

volumes:
  dbvolume:
```

それでは各Dockerの定義を見ていきましょう!

## postgres:9.6.15用Docker定義

TZ変更のみのDockerfileとしています。

```docker:postgres/Dockerfile
FROM postgres:9.6.15

RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

## nginx:1.17.3用Docker定義

TZ変更およびログファイルのstdout,strerrリンク、設定ファイルのCOPYをするDockerfileとしています。

```docker:nginx/Dockerfile
FROM nginx:1.17.3

RUN ln -sf /usr/share/zoneinfo/Japan /etc/localtime

RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stderr /var/log/nginx/error.log && \
    ln -sf /dev/stdout /var/log/nginx/host.access.log

COPY ./etc/nginx.conf /etc/nginx/nginx.conf
COPY ./etc/conf.d /etc/nginx/conf.d
```

nginx.confの設定は、gzip、upstreamサーバ設定を行っています。

```conf:nginx/etc/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log debug;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    server_tokens off;

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    gzip  on;
    gzip_types image/png image/gif image/jpeg text/javascript text/css;
    gzip_min_length 1000;
    gzip_proxied any;
    gzip_comp_level 9;

    upstream django2_server  {
        server python:3031 fail_timeout=0;
    }

    include /etc/nginx/conf.d/*.conf;
}
```

default.confの設定は割とシンプル

```conf:nginx/etc/conf.d/default.conf
server {
    listen       80;
    server_name  _;

    root /opt/apps;
    index index.php;

    #charset koi8-r;
    access_log  /var/log/nginx/host.access.log  main;

    location /static/ {
        root /opt/apps/static;
    }

    location / {
        try_files $uri @proxy_to_django2;
    }

    location @proxy_to_django2 {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_redirect off;
        proxy_pass http://django2_server;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    location ~ /\. {
        deny  all;
    }
}
```

## python:3.7.4用Docker定義

TZ変更は他サーバーのDockerfileと同様ですが、
pythonモジュールのインストールを`requirements.txt`により指定し行っています。

WORKDIRはDjango2プロジェクトの`apps`フォルダとしています。

```docker:python/Dockerfile
FROM python:3.7.4

ENV APP_PATH /opt/apps

RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

WORKDIR $APP_PATH

COPY ./apps/requirements.txt $APP_PATH/

RUN pip install --no-cache-dir -r $APP_PATH/requirements.txt
```

インストールするモジュールは以下の通りです。
作成するプロジェクトに応じて追加してください。

```text:python/apps/requirements.txt
django==2.2.5
djangorestframework
django-filter
psycopg2
gunicorn
```

この`setup.sh`によりDBのマイグレーションおよびスーパーユーザ作成を行っています。
ユーザ名、メールアドレス、パスワードは`docker-compose.yaml`の環境変数で指定しています。

```bash:python/apps/setup.sh
#!/bin/bash

echo "##### setup django2 app #####"
echo "-> migrate database"
python manage.py makemigrations --noinput
python manage.py migrate --noinput

echo "-> create super user"
python manage.py shell <<EOF
from django.contrib.auth.models import User
if not User.objects.filter(username='$DJANGO_ADMIN_USER'):
    User.objects.create_superuser('$DJANGO_ADMIN_USER', '$DJANGO_ADMIN_EMAIL', '$DJANGO_ADMIN_PASSWORD')
    print('    create super user: $DJANGO_ADMIN_USER')
else:
    print('    user: $DJANGO_ADMIN_USER already exists, nothing to do')
```

pythonが動作した際に作成されるキャッシュ達をgitにcommmitしないためのおまじない(^^♪

```text:python/.gitignore
__pycache__/
*.py[cod]
```

# Dockerの起動

ここまで準備ができたらdockerを起動します。

```console
C:\Users\tak>cd C:\Git\docker\django2
C:\Git\docker\django2>docker-compose build
C:\Git\docker\django2>docker-compose up
```

いやDockerによる開発環境、手軽に作れて壊せて便利ですね！