DockerでPostgresを使う時、/docker-entrypoint-initdb.d
ディレクトリ配下に.sql
,.sql.gz
,.sh
のいずれかの名前でファイルを作成することで、初期起動時に実行してくれます。
たとえば、公式にあるような/docker-entrypoint-initdb.d/init-user-db.sh
というファイルに
#!/bin/bash set -e psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL CREATE USER docker; CREATE DATABASE docker; GRANT ALL PRIVILEGES ON DATABASE docker TO docker; EOSQL
と記載して起動すれば、
# \l
データベース一覧
名前 | 所有者 | エンコーディング | 照合順序 | Ctype(変換演算子) | アクセス権限
-----------+----------------------+------------------+------------+-------------------+-----------------------------------------------
docker | ***** | UTF8 | ja_JP.utf8 | ja_JP.utf8 | =Tc/***** +
| | | | | *****=CTc/*****+
| | | | | docker=CTc/*****
postgres | ***** | UTF8 | ja_JP.utf8 | ja_JP.utf8 |
template0 | ***** | UTF8 | ja_JP.utf8 | ja_JP.utf8 | =c/***** +
| | | | | *****=CTc/*****
template1 | ***** | UTF8 | ja_JP.utf8 | ja_JP.utf8 | =c/***** +
| | | | | *****=CTc/*****
test | ***** | UTF8 | ja_JP.utf8 | ja_JP.utf8 |
という感じでdocker
データベースが自動で作成されます。
/docker-entrypoint-initdb.d/init-user-db.sh
は初回コンテナ作成時に実行されるのですが、.sh
の通り、bashなので単独で実行しても問題ないです。
そうやって2回目以降実行するときに、すでに作成済みのテーブルがあればIF NOT EXISTS
をつけてやれば良いのですが、CREATE DATABASE
みたいなIF NOT EXISTS
が無いときはどうすれば良い?が気になったので試しました。
たとえば、先程の/docker-entrypoint-initdb.d/init-user-db.sh
を再度実行してみると(db
というコンテナ名で起動しています)
$ docker-compose exec db bash /docker-entrypoint-initdb.d/init-user-db.sh
ERROR: ロール"docker"はすでに存在します
という感じで、docker
ロール(ユーザー)がすでに存在しています、と怒ってきます。
stackoverflowのSimulate CREATE DATABASE IF NOT EXISTS for PostgreSQL? を参考にしました。
/docker-entrypoint-initdb.d/init-user-db.sh
を以下のように書き換えます。
#!/bin/bash set -e psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL SELECT 'CREATE USER docker' WHERE NOT EXISTS (SELECT usename FROM pg_user WHERE usename = 'docker')\gexec SELECT 'CREATE DATABASE docker' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'docker')\gexec GRANT ALL PRIVILEGES ON DATABASE docker TO docker; EOSQL
それぞれ、サブクエリとしてユーザーであればpg_user
、データベースであればpg_database
と基本情報として存在するテーブルに問い合わせをして、存在していなければSELECT文にあるSQLを実行する、というような流れになっています。
文末の\gexec
を記載することで、SQLとして実行してくれます。
このように設定することで、何度でもbashを実行してもエラーが発生されなくなります。
$ docker-compose exec db bash /docker-entrypoint-initdb.d/init-user-db.sh
GRANT
$ docker-compose exec db bash /docker-entrypoint-initdb.d/init-user-db.sh
GRANT
そのほかのIF NOT EXISTS
がないコマンドについても、同じような思想で問い合わせすることで回避できるようになるかと思います。