Djangoの開発環境をコンテナで起動&Postgresに初期データを自動でSQL実行する方法

## やってみたこと
* 以前の記事でDjangoの開発環境を`docker-compose`を利用してコンテナで起動&DockerにPostgres環境を構築する方法を紹介しました。今回は、初期データ登録用のSQLを作成し、コンテナ起動時に自動で実行する手順を紹介します。
 
## 前提
* 前回までの記事はこちら
    * DjangoチュートリアルをDockerから立ち上げてみた記事はこちら：[Djangoの開発環境をdocker-composeを利用してコンテナで起動し、runserverコマンドでホスト側からアクセスする方法](https://qiita.com/Ryo-0131/items/522c4e9c0cb4c539eb37)
    * Djangoチュートリアルとpostgres環境を構築した記事はこちら：[Djangoの開発環境をdocker-composeを利用してコンテナで起動&DockerにPostgres環境を構築する方法](https://qiita.com/Ryo-0131/items/71ca923880f4d89b26c1)
* djangoチュートリアルでpollsアプリケーションを制作したmysiteプロジェクトを、以下ディレクトリ構造で配置(前回と同様)。

```
.
├── docker
│   ├── docker-compose.yml
│   └── Dockerfile
└── web
    ├── mysite
    │   ├── mysite
    │   ├── polls
    │   ├── db.sqlite3
    │   └── manage.py
    └── requirements.txt
```

* djangoチュートリアルで作成したものをそのまま使っていますので、`settings.py`の`DAEABASES`もそのままになっています。

```settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
```

# 手順
①Dockerfileの準備
②docker-compose.ymlの準備
③Dockerコンテナの起動により自動実行

## Dockerfileの準備
* Dockerfileは、前回記事と同様です。

```Dockerfile
# python:3.8の公式 image をベースの image として設定
FROM python:3.8

# 作業ディレクトリの作成
RUN mkdir /mysite

# 作業ディレクトリの設定（以後の RUN は WORKDIR で実行）
WORKDIR /mysite

# カレントディレクトリにある資産をコンテナ上の指定のディレクトリにコピーする
ADD web /mysite

# pipでrequirements.txtに指定されているパッケージを追加する
RUN pip install -r requirements.txt

# 起動(今回はなし)
# CMD python3 manage.py runserver 0.0.0.0:8000
```

* `requirements.txt`も前回記事同様、必要最低限のままにしています。

```txt
Django==4.1
requests
```

## docker-compose.ymlの準備
* `docker-compose.yml`には以下のように記載します。

```yaml
version: '3'
services:
  mysite:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile
    volumes:
      - '../web:/mysite'
    ports:
      - "8000:8000"
    container_name: docker_mysite
    tty: true
    working_dir: '/mysite'
  db-sample1:
    build:
      context: ../
      dockerfile: ./docker/sample1/Dockerfile
    ports:
      - "5438:5432"
    container_name: mysite_sample1_db
    environment:
      POSTGRES_USER: user
      POSTGRES_DB: sample
      POSTGRES_PASSWORD: pass
    volumes:
        - postgres-data-volume:/var/lib/postgresql/data
        - ./sql:/docker-entrypoint-initdb.d　#　ここを追加
volumes:
    postgres-data-volume:
```

* これにより、Dockerコンテナ起動時に初回適用のSQLを自動的に実行することができます。
* `- ./sql:/docker-entrypoint-initdb.d`の行は、ホストマシンの`./sql`ディレクトリ内にあるSQLファイルを、PostgreSQLコンテナの`/docker-entrypoint-initdb.d`ディレクトリにマウントするように設定しています。
* 今回、`./sql`ディレクトリ内にあるSQLファイルは`01_example.sql`で、テーブルを作成するコマンドを記載しました。

```sql
CREATE TABLE example01
(
    id            INTEGER PRIMARY KEY,
    example_code  varchar(255),
    created_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at    TIMESTAMPTZ
);
```

* ちなみに`docker-entrypoint-initdb.d`内に複数のスクリプトが存在する場合、名前の昇順で実行されます。依存関係がある場合は順番が大切になるので、ファイル名は`01`とか頭に振っておくと良いです。
* サポートされている拡張子と動作は以下です。
    * `.sh`: そのまま実行する
    * `.sql`: `mysql`コマンドに流し込む
    * `.sql.gz`: 解凍してから`mysql`コマンドに流し込む

## Dockerコンテナの起動により自動実行
* Dockerコンテナが起動すると、`/docker-entrypoint-initdb.d`ディレクトリ内のSQLファイルが自動的にPostgreSQLによって実行されます。

```terminal
$ docker compose up -d
```

* Dockerコンテナに入って、SQLが実行されているかDBを見てみましょう。

```terminal
$ docker exec -it  mysite_sample1_db bash

40befb0207ed:/# psql -U user -d sample
psql (11.18)
Type "help" for help.

sample=# \d
          List of relations
 Schema |    Name     | Type  | Owner 
--------+-------------+-------+-------
 public | example01   | table | user
```

* `example01`テーブルが作成されていることがわかります。

## ボリュームが残っている場合に、SQL実行は再度適用される？
* 2回目以降のコンテナ起動時にはSQLが再度実行されます。
* ただし、volumesに記載したボリュームが残っている場合、Dockerコンテナの起動時には既存のボリュームがマウントされます。ホスト上のファイルを更新した場合は古いボリュームを削除し、ボリュームのマウントからやり直す必要があります。
* したがって、初回実行する際には、上記の場合`postgres-data-volume`を削除する必要があります。ボリュームを削除するには、以下のコマンドを実行します。

```terminal
$ docker volume rm postgres-data-volume
```

* ローカル環境といえど、削除して良いのかどうかはよく確認しましょう。場合によってはバックアップを取ることをお勧めします。
* 以下のコマンドで、Dockerホスト上で利用可能なすべてのボリュームのリストを表示できます。

```terminal
$ docker volume ls
```

## ボリュームを残したまま、再度SQLを実行したい場合は？
* 初回実行したいSQLが変わったら、一回一回ボリュームを削除する、という運用では大変ですよね。そのときは、以下の方法でSQL再適用が可能です。
* まず、DBのコンテナに入ります。

```terminal
$ docker exec -it  mysite_sample1_db bash
```

* その後、以下の`psql`コマンドで、マウントした`01_example.sql`を実行します。
    * `psql`: PostgreSQLのコマンドラインクライアントで、SQLクエリを実行したり、PostgreSQLサーバーに接続したりするためのツールです。

```terminal
$ psql -h localhost -U user -d sample -f /docker-entrypoint-initdb.d/01_example.sql
```

* 上記コマンドは以下を示しています。
    * `-h localhost`: 接続先のホスト名を指定するオプション
    * `-U user`: 接続するユーザー名を指定するオプション
    * `-d sample`: 接続するデータベース名を指定するオプション
    * `-f /docker-entrypoint-initdb.d/01_example.sql`: SQLスクリプトを実行するオプション
* つまり、`user`というユーザーで`sample`という名前のデータベースに対して、`/docker-entrypoint-initdb.d/01_example.sql`に含まれるSQLクエリを実行することを意味します。
* これでボリュームを削除せずにSQLを再実行することができました。


# 参考記事
* [【Docker】【MySQL】コンテナ初回起動時にスクリプトを実行させる](https://zenn.dev/re24_1986/articles/978801ae092498)
* [docker-compose起動時に、init.dbのSQLが実行されない場合の対処](https://qiita.com/kondo0602/items/ab0a85fb1e731234eb1a)