Django環境をDockerで構築（Django+Nginx+PostgreSQL)

# この記事で書くことと注意点
- Django4.2LTSをDockerコンテナで構築する。
- プロキシは通しません。
プロキシのある環境で扱う場合は、環境変数(http_proxyなど)を書いて対応してください。
- Gunicornを使ってHTTPをコンテナネットワーク内で内部公開し、それをNginxでhttpsとして外部公開(リバースプロキシ)する。
- 構築メモのつもりで書いてるので、後々加筆修正するところも出てくるかも。

# Docker&docker-composeインストール
こちらの記事を参照ください。RHEL系ならこれでいけます。
Ubuntuとかはやり方違うと思います。

https://qiita.com/Nats72/items/660798a9146baf68e376

# コンテナ構築
一旦、このようにしたい(`docker-compose up -d`する前の状態)
```console
# tree
.
├── code
├── containers
│   ├── django
│   │   ├── dockerfile
│   │   ├── init.sh
│   │   ├── requirements.txt
│   │   └── supervisord.conf
│   ├── nginx
│   │   ├── default.conf
│   │   └── dockerfile
│   └── pgsql
│       └── dockerfile
├── data
├── docker-compose.yml
└── logs
```

## プロジェクトフォルダ作成
```console
$ su
# mkdir django-project
# cd django-project
```

## 各種ファイル保管用ディレクトリを作成
```console
# mkdir -p containers/django containers/nginx containers/pgsql
# mkdir -p code data logs
```

## コンテナビルド(docker-compose)用のファイルを作成
```console
# touch docker-compose.yml
# touch .env
# chmod 666 docker-compose.yml .env 
```
```yml:docker-compose.yml
version: "3.9"
services:
  db:
    container_name: ${POSTGRES_CONTAINER_NAME}
    build:
      context: .
      dockerfile: ./containers/pgsql/dockerfile
    platform: linux/x86_64
    volumes:
      - ./data:/var/lib/postgresql/data
    expose:
      - ${POSTGRES_PORT}
    env_file:
      - .env
    restart: always
    logging:
      driver: json-file
      options:
        max-size: 1m
        max-file: '3'
    networks:
      django_network:
        ipv4_address: ${POSTGRES_ADDR}

  app:
    container_name: ${DJANGO_CONTAINER_NAME}
    build:
      context: .
      dockerfile: ./containers/django/dockerfile
    volumes:
      - ./code:/code
      - ./logs/django:/log
    expose:
      - ${DJANGO_PORT}
    tty: true
    stdin_open: true
    privileged: true
    entrypoint: "/tmp/init.sh"
    env_file:
      - .env
    restart: always
    depends_on:
      - db
    logging:
      driver: json-file
      options:
        max-size: 1m
        max-file: '3'
    networks:
      django_network:
        ipv4_address: ${DJANGO_ADDR}

  web:
    container_name: ${NGINX_CONTAINER_NAME}
    build:
      context: .
      dockerfile: ./containers/nginx/dockerfile
    tty: true
    stdin_open: true
    volumes:
      - ./code:/code
      - ./containers/nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./logs/nginx:/var/log/nginx
    ports:
      - ${NGINX_HTTP_PORT}:80
    restart: always
    depends_on:
      - app
    environment:
      TZ: Asia/Tokyo
    logging:
      driver: json-file
      options:
        max-size: 1m
        max-file: '3'
    networks:
      django_network:
        ipv4_address: ${NGINX_ADDR}

networks:
  django_network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: ${NETWORK_SUBNET}
```
```.env
TZ = Asia/Tokyo
NETWORK_SUBNET = 172.16.0.0/24

DJANGO_CONTAINER_NAME = app
DJANGO_PORT = 80
DJANGO_ADDR = 172.16.0.2

NGINX_CONTAINER_NAME = web
NGINX_HTTP_PORT = 80
NGINX_ADDR = 172.16.0.3

POSTGRES_CONTAINER_NAME = db
POSTGRES_PORT = 5432
POSTGRES_ADDR = 172.16.0.4
POSTGRES_USER = admin
POSTGRES_PASSWORD = password
POSTGRES_DB = appdb
```

## Djangoコンテナ用のファイルを作成
```console
# touch containers/django/dockerfile containers/django/requirements.txt
# touch containers/django/supervisord.conf containers/django/init.sh
# chmod 666 containers/django/*
```

### dockerfile
Django用コンテナのイメージを生成するための設定。
```dockerfile:containers/django/dockerfile
FROM python:3.12-bookworm
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
RUN apt-get update && apt-get -y upgrade
RUN apt -y install iproute2 iputils-ping net-tools traceroute tcpdump
RUN apt -y install postgresql16-client
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash -
RUN apt install -y nodejs npm
RUN mkdir /code
WORKDIR /code
COPY ./containers/django/requirements.txt /code/
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
RUN apt-get install -y supervisor
COPY ./containers/django/supervisord.conf /etc/supervisor/supervisord.conf
COPY ./containers/django/init.sh /tmp/
RUN chmod +x /tmp/init.sh
```
| 設定 | 補足説明 |
|:-|:-|
| ベースイメージ | DjangoはPythonコンテナをベースに作る。2023/12時点で最新のものとした。 |
| PYTHONDONTWRITEBYTECODE=1 | \_\_pycache\_\_、.pycを作らせない。  |
| PYTHONUNBUFFERED=1 | 標準出力、標準エラー出力のバッファリングを行わない。 |
| Node.jsインストール | TailwindCSSを使うために必要となる。 |
| pipインストール | requirements.txtに書かれたライブラリをインストールする。<br>codeディレクトリは、volumeマウントで上書きされるので、コンテナが立ち上がるときにはrequirements.txtごと上書きされる。 |
| supervisor | 元となるイメージファイルには、systemctlがないためプロセス管理できない。そのため、supervisorを入れてコンテナ内でプロセス管理させる。 |
| init.sh | これをinitプロセス(PID1)として起動させる。 |

### requirements.txt
Djangoイメージを作るときにインストールさせておきたいライブラリを記述。
dockerfile内に、`RUN pip install ～～～`って書いてもいいのだが、
pipインストールしたいものだけ分けて書いてあった方が分かりやすいと思ってこうした。
いずれも2023/12時点の最新バージョンとした。
```text:containers/django/requirements.txt
Django==4.2
gunicorn==21.2.0
djangorestframework==3.14.0
django-import-export==3.3.4
django_apscheduler==0.6.2
django_extensions==3.2.3
django-debug-toolbar==4.2.0
django-cors-headers==4.2.0
psycopg2-binary==2.9.9
mkdocs==1.5.3
```
| 設定 | 補足説明 |
|:-|:-|
| Django | 最新LTS版 |
| restframework | RESTfulAPIを作るライブラリ |
| import-export | Django管理サイトでDBの中身をインポートしたり、エクスポートできるようになるライブラリ |
| apscheduler | 定期実行に使うことができるライブラリ(cron使うでもいいと思う) |
| extensitons | 色々な拡張機能が入ったライブラリ |
| debug-toolbar | デバッグ用の拡張機能が入ったライブラリ |
| django-cors-headers | CORSを設定するためのライブラリ |
| psycopg2-binary | PostgreSQLに接続するためのライブラリ |
| mkdocs | 静的ドキュメントを作るためのライブラリ |

```conf:containers/django/supervisord.conf
[unix_http_server]
file=/var/run/supervisor.sock
serverurl=unix:///var/run/supervisor.sock

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock

[supervisord]
nodaemon=true
logfile=/var/log/supervisor/supervisord.log
logfile_maxbytes=50MB
logfile_backups=3
pidfile=/var/run/supervisord.pid
childlogdir=/code/log/supervisor

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[program:gunicorn]
command=gunicorn --config /code/config/gunicorn_settings.py
autostart=true
autorestart=true
startsecs=10
startretries=3
stdout_logfile=/code/log/supervisor/gunicorn_stdout.log
stderr_logfile=/code/log/supervisor/gunicorn_stderr.log

[include]
files = /etc/supervisor/conf.d/*.conf
```
#### [supervisord]
supervisordプロセス自体の設定。
| 設定 | 補足説明 |
|:-|:-|
| logfile | supervisordプロセスのログ出力 |
| logfile_maxbytes | 指定サイズ以上になったらローテーション |
| logfile_backups | ローテーションで残すログファイル数 |
| nodaemon | trueにするとデーモン化しない(dockerで動かす場合はtrue) |
| pidfile | PIDファイルのパス |
| childlogdir | 子プロセスのログ・ファイル保存先パス |

#### [program:gunicorn]
gunicorn用に設定。
| 設定 | 補足説明 |
|:-|:-|
| command | プロセス起動コマンド |
| numprocs | 起動するプロセス数 |
| autostart | 自動起動設定 |
| autorestart | プロセス終了時の再起動設定 |
| startsecs | 起動に指定秒数以上かかる場合は起動失敗とみなす |
| startretries | 起動失敗した場合のリトライ回数 |
| pidfile | PIDファイルのパス |
| childlogdir | 子プロセスのログ・ファイル保存先パス |

### コンテナ起動時の設定
```sh:containers/django/init.sh
#!/bin/bash
if [ -f /code/config/gunicorn_settings.py ]; then
    /usr/bin/supervisord
fi
cd /code
npm init -y
npm install -D tailwindcss@3.3.6 postcss-cli@11.0.0 autoprefixer@10.4.16 cssnano@6.0.2
npx tailwindcss init -p

while true
do
sleep 10
done
```
| 設定 | 補足説明 |
|:-|:-|
| /usr/bin/supervisord | supervisorを起動させる。(gunicornが起動する)<br>gunicornの設定ファイルがない場合は起動しないようにしている。 |
| npm init | npmフォルダを初期化する |
| tailwindcss | ほぼCSSを書かなくても良くなって、class名も共通化できる便利なやつ<br>これをPostCSSのプラグインとして使う |
| postcss-cli | CSS変換ツール(PostCSS)をnpmスクリプトとしてCLIで使用できるようにするもの、らしい<br>色々なプラグインを追加できる|
| autoprefixer | ベンダープレフィックスをつけてくれるPostCSSのプラグイン |
| cssnano | cssをminify化するやつ |
| while ~ done | コンテナ内でシェルスクリプトを起動させて終了するとコンテナが落ちてしまうため、無限ループ処理させて落ちないようにしている |

## Nginxコンテナ用のファイルを作成
2023/12時点で最新のdebian版とした。（alpine版は慣れない・・・）
```console
# touch containers/nginx/dockerfile
# chmod 666 containers/nginx/*
```
```dockerfile:containers/nginx/dockerfile
FROM nginx:1.25-bookworm
```
```conf:containers/nginx/default.conf
upstream django {
    server app:80;
}

server {
    server_name  myapp.com;
    listen 80;

    root        /code;
    access_log  /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location /static/ {
        autoindex on;
        alias /code/static/;
    }

    location /media/ {
        autoindex on;
        alias /code/media/;
    }

    location / {
        proxy_pass http://django/;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect off;
    }
}
```
| 設定 | 補足説明 |
|:-|:-|
| location /static/ | 静的ファイルのうちjsファイルやcssファイル、その他ページ構成に必要なファイル（画像ファイルなど）を配布するディレクトリの設定 |
| location /media/ | 画像や動画などのメディアファイルを保存するディレクトリ<br>例えば、アプリからアップロードする画像だとかの保存先になる |

## PostgreSQLコンテナ用のファイルを作成
```console
# touch containers/pgsql/dockerfile
# chmod 666 containers/pgsql/*
```
```dockerfile:containers/pgsql/dockerfile
FROM postgres:15-bookworm
```

# Dockerイメージ＆コンテナビルド
`docker-compose up`すると、dockerイメージがない場合はイメージビルドしつつコンテナもビルドしてくれます。
`-d`オプションをつけることで、デーモン起動します。
```console
# docker-compose up -d
```
dockerコンテナが起動していることを確認します。
STATUSがUpになっていれば正常に動作しています。
```console
# docker ps
CONTAINER ID   IMAGE                COMMAND                  CREATED         STATUS         PORTS                               NAMES
ed1baf809bfe   django-project_web   "/docker-entrypoint.…"   3 seconds ago   Up 2 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   web
5b0d00822fca   django-project_app   "python3"                3 seconds ago   Up 2 seconds   80/tcp                              app
6db1af530d87   django-project_db    "docker-entrypoint.s…"   3 seconds ago   Up 2 seconds   5432/tcp                            db
```


長くなったので一旦ここまでにします。

続きはこちら。

https://qiita.com/Nats72/items/95c5b2e903261cfbec3c