Docker で作る postgres 環境


postgres 環境を docker で構築します。もし同時に PHP 環境が必要な場合は下記も参照してください。

Docker で作る Nginx + PHP7 + Xdebug 環境

docker コンテナとして postgres を扱うメリット

docker を使用すれとローカル環境に複数のデータベースを作成、使用するのが容易になります。同じ設定で postgres のバージョンだけ変更することも出来、不具合の検証や pg_dump、pg_restore といった postgres のバージョンに依存した問題を解決しやすいです。データベース練習用としても最適です。

Docker の設定ファイル

postgreSQL のようなメジャーなシステムは大抵、公式が docker image を用意してくれています。dockerfile では image の読み込みと日本語対応を行います。

dockerfile

FROM postgres:11-alpine
ENV LANG ja_JP.utf8

dockerhub の postgres

公式には debian と alpine 環境が用意されています。alpine-linux は docker image サイズが小さいのでお勧めです。

次に docker-compose.yaml で起動時の初期設定を書いていきます。dockerfile を使い、docker run することでも動かすことは可能ですが、起動時の設定を覚えるのが大変なので docker-compose.yaml でその設定を書いてしまいます。

docker-compose.yaml を dockerfile と同じ階層に作成します。

version: '3'
services:
  db:
    build: .
    ports:
      - 5433:5432
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin

各項目について説明します

version

1.x〜3.x で指定可能、使用している機能によってバージョンを選択します。docker の特定バージョンにしかない、あるいは新しい機能を使う時にはマイナーバージョンまで(3.4 など)指定しますが、基本は 3 で問題ありません。

services

立ち上げる docker コンテナの情報を書きます。今回は 1 つですが複数のコンテナを管理できます。

services:
  db1:
    image: postgres:11-alpine
    ports: '5433:5432'
  db2:
    image: postgres:10-alpine
    ports: '5434:5432'

dbという名前は任意です、他のコンテナと連携する時に使用するためわかりやすい名前をつけます。build の.は同じ階層の dockerfile を指しています。postgres の image をそのまま使う時は上記例のように image で指定します。

port pots: 3433:3432 という表記は左側が外部からのアクセス時のポート、右側が docker コンテナからアクセスする時のポート番号になります。例えば、pgAdmin や DBeaver といったデータベースクライアントで postgres のコンテナにアクセスしたい時、

services:
  db:
    image: postgres:11-alpine
    ports: '5433:5432'

このように設定していた場合は、

host: localhost
port: 5433

という設定でアクセスが可能です。一方、同じ docker-compose 内に PHP も設定していた場合

services:
  php:
  # 省略
  db:
    image: postgres:11-alpine
    ports: '5433:5432'

PHP ファイルで connect する設定例は下記のようになります。postgres 側には admin データベース、admin ユーザー、password が admin を想定しています。

$db = new PDO(
  'pgsql:host=db;dbname=admin;',
  'admin',
  'admin'
);

ポート 5432 で繋がり、host は service で設定したデーターベス名となります。外部用ポートを 5432 から変更しているのは複数の postgres を立ち上げる際にぶつかるからです。そのポートが使えるかは、Mac なら

lsof -i:5432

上記でポート 5432 が何かに使われているかチェックできます。複数環境を立ち上げるようになるとポートがぶつかりやすくなるため、良く使うポートは避けておく方が無難です。

environment

postgres の公式 docker image は envrionement の値を読みとり初期設定を行なう機能があります。設定詳細は公式参照。設定例は下記です。

environment:
  POSTGRES_USER: admin
  POSTGRES_PASSWORD: admin

この設定で、database、user、password が全て admin という名前で作成されます。また、POSTGRES_DB は省略すると USER 名と同じものが作成されます。変更したい場合は指定します

    environment:
      POSTGRES_USER: admin
      POSTGRES_DB: test
      POSTGRES_PASSWORD: admin

これで test データベースに admin ユーザー、password が admin でアクセスできます。

docker コンテナを起動する

docker-compose.yaml がある階層で下記コマンドを実行します。

docker-compose up -d

docker-compose を使用するとポート番号など設定が全てファイル内に入るため、立ち上げるコマンドがとてもシンプルになります。コンテナを停止するときは

docker-compose down

実際にコンテナが立ち上がり、postgres が正常に動いているかを確認するには上述のデータベースクライアントから接続するか、コンテナに中に入ります。

docker-compose exec db bash

exec コマンドでは service でつけたコンテナ名を指定します。terminal の表示が bash 4.4# のように変化していればコンテナの中に入れています。コンテナ内は linux マシンなのでその感覚で扱います

psql -U admin

bash 内に入ったら psql コマンドを使ってみます。admin で入ることができれば設定はうまくいっています。admin ユーザーがおらず、postgres ユーザーで入れるときは設定がうまくいっていません。

admin がいるのを確認したら exit します。

データベースクライアントと接続する場合

pots に 5433:5432 と設定している想定です。ローカルの DBeaver や Pgadmin などデータベースクライアントでの接続は下記の設定です。

Host: localhost
Port: 5433
Database: admin
User: admin
Password: admin

自身で設定を変えている場合はそれに合わせてください。テーブルは何もありませんがデータベースには入れるはずです。

コンテナ起動時に SQL を発行する

コンテナを立ち上げた時、初期設定で何か SQL を走らせたい場合はシェルを登録します。docker-entrypoint-initdb.d というフォルダを docker-compose.yaml と同じ階層に作成します。次に volumes 設定を追加し、ホストと docker コンテナ内のファイルを同期します。

version: '3'
services:
  db:
    build: .
    ports:
      - 5433:5432
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin
    volumes:
      - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d

postgres の docker image は、docker-entrypoint-initdb.d 内に.sh ファイルを置いておくと起動時にその内容を実行してくれます。例として docker-entrypoint-initdb.d フォルダ内に init.sh ファイルを作成し、次を設定します。

/docker-entrypoint-initdb.d/init.sh

set -e
psql -U admin admin << EOSQL
CREATE TABLE Users(
  account_id        SERIAL PRIMARY KEY,
  account_name      VARCHAR(20),
  email             VARCHAR(100),
  password    CHAR(64)
);

CREATE TABLE UserStatus(
  status            VARCHAR(20) PRIMARY KEY
);
EOSQL

これは docker-compose の environment で admin データベースと admin ユーザーを作成している場合です。すでに admin データベースはあるので create database はしていません。admin ユーザーで admin データベースに入って Users テーブルを作成しています。

docker-compose up -d

設定がうまくいっているかを確認するためにコンテナを立ち上げます。デーベースクライアントからデーターベースを確認すると新しくテーブルができているはずです。うまくできていない時は後述の改行コード問題か記述ミス、キャッシュの問題などが考えられます

docker-compose down -v
docker-compose build --no-cache
docker-compose up -

というキャッシュクリアを試してください。

windows ユーザーは init.sh の改行に注意

init.sh は docker コンテナ内で実行されます。この時、windows だとファイルの改行コードがCRLFになっているケースがあり、これが原因でエラーになるケースがあります。

VSCode を使用しているならエディタ右下に改行コードを変更する部分がありますので、LFに変更して保存しましょう。

既存のデータベースを使用する場合

稼働しているデーターベースをローカルで検証したい時や、開発の途中から環境を作成するときなど。すでにあるデータベースを使いたい場合はそれをリストアするのが効率的です。

今回は docker コンテナ上のデーターベースを pg_dump してバックアップし、それを pg_restore します。通常のサーバー上の postgres も同じ手順でバックアップ可能で、バックアップがあれば同じ手順でリストアできます。

データベースクライアントでコンテナ内の postgres にアクセスした方がバックアップもリストアも簡単だと思いますので下記は参考程度で。postgres のバージョン違いなどでバックアップやリストアが難しいときなどは役立つと思います。

pg_dump してバックアップ

上述の init.sh が稼働していれば現在の db コンテナには User と UserStatus の 2 つのテーブルがあるはずです。まずは pg_dump でこのデータベースをバックアップします。バックアップファイルをホストで受け取るために docker-compose.yaml に volume を追加します。docker-compose.yaml と同じ階層に database フォルダを作成。

  db:
    image: postgres:11-alpine
    ports:
      - 5439:5432
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin
    volumes:
      - ./database:/usr/src

コンテナ内の/usr/src に postgres のバックアップを出力して、それをホスト側の database フォルダで同期します。docker-compose でコンテナを再起動した後、次にコンテナ内に入り postgres をバックアップします。

docker-compose exec db bash
which pg_dump

pg_dump の場所は which で見つかります。/usr/local/bin/pg_dump にあると思いますので、続いてバックアップします。

/usr/local/bin/pg_dump -Fc -d admin -h localhost -p 5432 -O -U admin -f /usr/src/db.backup

これで database フォルダ内に db.backup ファイルが出力されたはずです。今回はローカルの docker でしたがサーバー上の postgres にもアクセス許可がある状態(例えばローカルの pgAdmin でバックアップとれる状態)であればコマンドを変更してバックアップできます。-h のホスト指定部分を対象ドメインに変更し、ユーザーとパスワードなども揃えます。

例:postgres が test.example.com にある場合

/usr/local/bin/pg_dump -Fc -d admin -h test.example.com -p 5432 -O -U admin -f /usr/src/db.backup

この方式の利点は postgres のバージョン変更が容易なところです。バージョン違いで pg_dump できない場合、postgres の docker image バージョンだけ変更すれば対応できます

pg_restore する

上記のバックアップ手順が成功したら docker-compose down します。再度立ち上げるときに init.sh は走らないように設定を変更します。

volumes:
#  - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d

データーベースは作成されるが中身は何もない状態にして、リストアされたか分かりやすくするためです。そして pg_dump と同じような手順でリストアを行います。docker-compose で立ち上げたあと、データベースが空になっているかを確認してください。

docker-compose exec db bash
which pg_restore
/usr/local/bin/pg_restore -d admin -U admin /usr/src/db.backup

先ほど取得して backup を使いリストアします。これでデーターベースがリストアされ、Users と UserStatus テーブルがあるはずです。

データベース内容を保持する

毎回綺麗な環境というのは魅力的ではあるものの、データーベース内容は維持してほしいケースが多いです。アプリケーションと連動しているなら migration の問題もあります。内容を維持するには次のように docker-compose.yaml を設定します。

version: '3'
services:
  db:
    build: .
    ports:
      - 5433:5432
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
volumes:
  db_data: {}

これで docker-compose down してもデータベース内容は初期化されません。もし、初期化したくなった時は volume を削除します。

docker-compose down -v