【Docker】【MySQL】コンテナ起動時にSQLを実行する方法とその仕組み

# 初めに
コンテナ起動後、毎回SQLを実行するのめんどくさいですよね。
ということで、Docker起動時に自動でSQLを実行してくれる方法を調べました。

# やり方
ディレクトリ構成は以下です。
```init.sql```がコンテナ起動時に実行したいSQLファイルになります。
```
workspace
├── docker-compose.yml
└── init
    └── init.sql
```

```sql: init.sql
create database initDB;                               
```

```docker-compose.yml```にて、ホストPCの```./init```と、コンテナの```/docker-entrypoint-initdb.d```の二つを共有させるようにバインドマウントさせます。
これにより、コンテナ起動時に```/docker-entrypoint-initdb.d```の中に```init.sql```作成されます。

``` yml:docker-compose.yml
version: '3'
services:
  mysql:
    image: mysql:latest
    container_name: my-mysql-container
    environment:
      MYSQL_ROOT_PASSWORD: your-root-password
      MYSQL_DATABASE: your-database-name
      MYSQL_USER: your-username
      MYSQL_PASSWORD: your-password
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
      - ./init:/docker-entrypoint-initdb.d　<- 追加
volumes:
  mysql-data:
```
これで準備完了です。
```docker compose up -d```でコンテナを作成します。

```shell-session
$ docker compose up -d
[+] Running 3/3
 ✔ Network mysqltest_default      Created                                                                          0.0s
 ✔ Volume "mysqltest_mysql-data"  Created                                                                          0.0s
 ✔ Container my-mysql-container   Started                                                                          0.0s
$
```
```docker exec```でコンテナの中に入って```MySQL```にログインします。
DBの一覧を見ると、```init.sql```で定義したSQLが実行されていることがわかります。

```shell-session
$ docker exec -it my-mysql-container bash
bash-4.4#
bash-4.4#
bash-4.4# mysql -u root -p
Enter password:

mysql>
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| initDB             |　<- init.sqlで作成されたデータベース
| mysql              |
| performance_schema |
| sys                |
| your-database-name |
+--------------------+
6 rows in set (0.01 sec)

```

# 初回実行される仕組み
dockerイメージには、docker初回起動時に呼び出される```docker-entrypoint.sh```というシェルスクリプトが定義されています。
MySQLの```docker-entrypoint.sh```には```/docker-entrypoint-initdb.d```の内のsqlファイルを実行するという処理が書いてあるようです。

MySQL 8.0の```docker-entrypoint.sh```は以下。
https://github.com/docker-library/mysql/blob/master/8.0/docker-entrypoint.sh


# 疑問点
## sqlファイル以外は実行できないの？
**結論：SQLファイルとshファイルが実行可能**
docker-entrypoint.shには以下のようなソースがあります。

```shell: docker-entrypoint.sh
docker_process_init_files() {
	# mysql here for backwards compatibility "${mysql[@]}"
	mysql=( docker_process_sql )

	echo
	local f
	for f; do
		case "$f" in
			*.sh)
				# https://github.com/docker-library/postgres/issues/450#issuecomment-393167936
				# https://github.com/docker-library/postgres/pull/452
				if [ -x "$f" ]; then
					mysql_note "$0: running $f"
					"$f"
				else
					mysql_note "$0: sourcing $f"
					. "$f"
				fi
				;;
			*.sql)     mysql_note "$0: running $f"; docker_process_sql < "$f"; echo ;;
			*.sql.bz2) mysql_note "$0: running $f"; bunzip2 -c "$f" | docker_process_sql; echo ;;
			*.sql.gz)  mysql_note "$0: running $f"; gunzip -c "$f" | docker_process_sql; echo ;;
			*.sql.xz)  mysql_note "$0: running $f"; xzcat "$f" | docker_process_sql; echo ;;
			*.sql.zst) mysql_note "$0: running $f"; zstd -dc "$f" | docker_process_sql; echo ;;
			*)         mysql_warn "$0: ignoring $f" ;;
		esac
		echo
	done
}

```
このソースによると、shファイルとsqlファイルの場合は実行、sqlファイルの圧縮ファイルであれば解凍してから実行しているようです。このため、shファイルとsqlファイルであれば実行可能になります。

## 複数ファイルがある場合はどれから実行されるの？
**結論：ファイル名の昇順で実行される**
```docker-entrypoint-initdb.d```にあるファイルは名前の昇順で実行されます。
依存関係のあるファイルを実行させる場合、ファイル名の頭に数字をつけるなどして工夫することが必要がありそうです。

```shell-session
$ ls
1_init.sql  2_init.sql  3_init.sql  
```


# 最後に
今回はコンテナ起動時にファイルを実行する方法を紹介しました。
便利なのでぜひ試してみてください。



# 参考
https://qiita.com/moaikids/items/f7c0db2c98425094ef10
https://zenn.dev/re24_1986/articles/978801ae092498
https://kakakakakku.hatenablog.com/entry/2017/11/08/193031
https://qiita.com/michikun06/items/24e99618dc59807b98c8