はじめに

モダンな開発では、ローカル環境にデータベースを直接インストールするのではなく、Dockerコンテナとして起動するのが一般的です。環境の再現性、チーム間での設定共有、クリーンアップの容易さなど、多くのメリットがあります。

本記事では、PostgreSQLとDockerを組み合わせた実践的な環境構築から本番運用まで解説します。Docker Composeでの開発環境構築、ボリュームによるデータ永続化、初期化スクリプトの活用、本番環境での考慮点、さらにKubernetes上でのPostgreSQL運用の概要まで、段階的に学んでいきます。

この記事を読むことで、以下のことができるようになります。

  • Docker ComposeでPostgreSQL開発環境を構築できる
  • ボリュームを使ってデータを永続化し、コンテナの再起動でもデータを保持できる
  • 初期化スクリプトでテーブル作成やテストデータ投入を自動化できる
  • 本番環境でDockerized PostgreSQLを運用する際の考慮点を理解できる
  • Kubernetes上でのPostgreSQL運用の選択肢を把握できる

前提条件

  • Docker Desktop(Windows/Mac)またはDocker Engine(Linux)がインストールされていること
  • Docker Composeが利用可能であること(Docker Desktop には同梱)
  • PostgreSQLの基本的な知識(CREATE TABLE、INSERT、SELECTなど)があること

Docker Composeの基本操作に不安がある場合は、Docker Compose入門 - compose.yamlの書き方と基本操作を参照してください。

PostgreSQL公式Dockerイメージの基本

公式イメージの概要

PostgreSQLの公式Dockerイメージは、Docker Hubでpostgresとして提供されています。PostgreSQL Docker Communityによってメンテナンスされており、Debian系(bookworm、trixie)とAlpine Linux系の両方が用意されています。

flowchart TD
    subgraph OfficialImages["PostgreSQL 公式イメージ"]
        direction TB
        Latest["postgres:latest\n(= postgres:18)"]
        
        subgraph DebianBased["Debian系(本番推奨)"]
            V18Bookworm["postgres:18-bookworm"]
            V17Bookworm["postgres:17-bookworm"]
            V16Bookworm["postgres:16-bookworm"]
        end
        
        subgraph AlpineBased["Alpine系(軽量)"]
            V18Alpine["postgres:18-alpine"]
            V17Alpine["postgres:17-alpine"]
            V16Alpine["postgres:16-alpine"]
        end
    end
    
    Latest --> V18Bookworm

イメージの選択基準は以下の通りです。

イメージ種別 特徴 推奨用途
postgres:18(Debian系) フル機能、拡張機能のインストールが容易 本番環境、拡張機能を使用する場合
postgres:18-alpine 軽量(約80MB)、起動が速い 開発環境、CI/CD
postgres:18-bookworm 明示的にDebianバージョンを指定 再現性を重視する場合

必須の環境変数

PostgreSQLコンテナを起動するには、最低限POSTGRES_PASSWORD環境変数が必要です。これは公式イメージの仕様で、セキュリティ上の理由から必須とされています。

1
2
# 最小限の起動コマンド
docker run --name my-postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres:18

主要な環境変数の一覧です。

環境変数 必須 説明 デフォルト値
POSTGRES_PASSWORD 必須 スーパーユーザーのパスワード なし
POSTGRES_USER 任意 スーパーユーザー名 postgres
POSTGRES_DB 任意 初期作成されるデータベース名 POSTGRES_USERの値
PGDATA 任意 データディレクトリのパス /var/lib/postgresql/data
POSTGRES_INITDB_ARGS 任意 initdbに渡す追加引数 なし
TZ 任意 タイムゾーン UTC

PGDATA設定の重要な変更点

PostgreSQL 18以降の公式イメージでは、PGDATAのデフォルト値が変更されました。この変更により、メジャーバージョンアップグレード時にpg_upgrade--linkオプションが使いやすくなっています。

バージョン PGDATAのデフォルト ボリュームマウント先
17以前 /var/lib/postgresql/data /var/lib/postgresql/data
18以降 /var/lib/postgresql/18/docker /var/lib/postgresql

PostgreSQL 17以前を使用する場合は、必ず/var/lib/postgresql/dataにボリュームをマウントしてください。/var/lib/postgresqlにマウントするとデータが永続化されません。

Docker Composeでの開発環境構築

基本的なcompose.yamlの構成

開発環境用のPostgreSQL設定をDocker Composeで定義します。以下は、日本語対応とタイムゾーン設定を含めた実用的な構成例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# compose.yaml
services:
  db:
    image: postgres:18
    container_name: dev-postgres
    environment:
      POSTGRES_USER: devuser
      POSTGRES_PASSWORD: devpassword
      POSTGRES_DB: myapp_development
      TZ: Asia/Tokyo
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U devuser -d myapp_development"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

volumes:
  postgres-data:

この設定のポイントを解説します。

  1. 明示的なイメージタグ: postgres:18のように明示的にバージョンを指定することで、予期しないアップデートを防ぎます
  2. 日本語対応: POSTGRES_INITDB_ARGSでUTF-8エンコーディングを指定しています
  3. ヘルスチェック: pg_isreadyコマンドでPostgreSQLの起動完了を確認します
  4. ボリューム: 名前付きボリュームpostgres-dataでデータを永続化します

起動と接続確認

Docker Composeで環境を起動し、接続を確認します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# バックグラウンドで起動
docker compose up -d

# ログを確認
docker compose logs -f db

# 起動完了を待つ(healthcheck利用)
docker compose up -d --wait

# psqlで接続
docker compose exec db psql -U devuser -d myapp_development

# または外部からの接続
psql -h localhost -p 5432 -U devuser -d myapp_development

コンテナ内でpsqlを実行した場合、以下のようなプロンプトが表示されれば成功です。

1
2
3
4
5
myapp_development=# SELECT version();
                                                 version
---------------------------------------------------------------------------------------------------------
 PostgreSQL 18.1 on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-12) 14.2.0, 64-bit
(1 row)

複数データベースの作成

開発環境では、アプリケーション用とテスト用など複数のデータベースが必要になることがあります。初期化スクリプトを使って複数のデータベースを作成できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# compose.yaml
services:
  db:
    image: postgres:18
    container_name: dev-postgres
    environment:
      POSTGRES_USER: devuser
      POSTGRES_PASSWORD: devpassword
      POSTGRES_DB: myapp_development
      TZ: Asia/Tokyo
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql
      - ./docker/db/init:/docker-entrypoint-initdb.d:ro

volumes:
  postgres-data:

初期化スクリプト(docker/db/init/01-create-databases.sh)を作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/usr/bin/env bash
set -e

# テスト用データベースを作成
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
    CREATE DATABASE myapp_test;
    GRANT ALL PRIVILEGES ON DATABASE myapp_test TO $POSTGRES_USER;
EOSQL

echo "Additional database 'myapp_test' created successfully."

Windowsを使用している場合は、ファイルの改行コードをLFに設定してください。CRLFの場合、スクリプトが正しく実行されません。

データ永続化とボリューム設定

ボリュームマウントの仕組み

Dockerコンテナは一時的な存在であり、コンテナを削除すると内部のデータも失われます。PostgreSQLのデータを永続化するには、ボリュームをマウントする必要があります。

flowchart LR
    subgraph Container["PostgreSQLコンテナ"]
        PGDATA["/var/lib/postgresql\n(PGDATA)"]
    end
    
    subgraph DockerVolume["Docker ボリューム"]
        Volume["postgres-data\n(Dockerが管理)"]
    end
    
    subgraph Host["ホストマシン"]
        HostPath["/var/lib/docker/volumes/\npostgres-data/_data"]
    end
    
    PGDATA <--> Volume
    Volume <--> HostPath

ボリュームには2つのタイプがあります。

タイプ 定義方法 用途
名前付きボリューム postgres-data:/var/lib/postgresql 本番環境、長期間のデータ保持
バインドマウント ./data:/var/lib/postgresql 開発環境、ホストからの直接アクセス

一般的には、名前付きボリュームを推奨します。Dockerが管理するため、パフォーマンスが良く、ポータビリティにも優れています。

バックアップとリストア

ボリューム内のデータをバックアップ・リストアする方法を紹介します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# pg_dumpでバックアップ(SQL形式)
docker compose exec db pg_dump -U devuser -d myapp_development > backup.sql

# pg_dumpでバックアップ(カスタム形式 - 推奨)
docker compose exec db pg_dump -U devuser -Fc myapp_development > backup.dump

# リストア(SQL形式)
docker compose exec -T db psql -U devuser -d myapp_development < backup.sql

# リストア(カスタム形式)
docker compose exec db pg_restore -U devuser -d myapp_development --clean backup.dump

ボリューム自体をバックアップする場合は、以下のようにします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# ボリュームのバックアップ(tarアーカイブ)
docker run --rm \
  -v postgres-data:/source:ro \
  -v $(pwd):/backup \
  alpine tar cvf /backup/postgres-backup.tar -C /source .

# ボリュームのリストア
docker run --rm \
  -v postgres-data:/target \
  -v $(pwd):/backup \
  alpine sh -c "cd /target && tar xvf /backup/postgres-backup.tar"

ボリュームの管理コマンド

よく使うボリューム管理コマンドをまとめます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# ボリューム一覧
docker volume ls

# ボリュームの詳細確認
docker volume inspect postgres-data

# 使用していないボリュームの削除
docker volume prune

# 特定のボリュームを削除(データが消えるので注意)
docker volume rm postgres-data

初期化スクリプトの活用

docker-entrypoint-initdb.dの仕組み

PostgreSQL公式イメージには、初期化スクリプトを実行する仕組みが組み込まれています。/docker-entrypoint-initdb.dディレクトリに配置されたファイルは、初回起動時(データディレクトリが空の場合)に自動実行されます。

sequenceDiagram
    participant User as ユーザー
    participant Docker as Docker
    participant Entrypoint as entrypoint.sh
    participant InitDB as initdb
    participant Scripts as 初期化スクリプト
    participant PG as PostgreSQL

    User->>Docker: docker compose up
    Docker->>Entrypoint: コンテナ起動
    Entrypoint->>Entrypoint: PGDATAが空か確認
    alt PGDATAが空
        Entrypoint->>InitDB: initdb実行
        InitDB-->>Entrypoint: 完了
        Entrypoint->>PG: 一時的に起動(Unixソケットのみ)
        Entrypoint->>Scripts: /docker-entrypoint-initdb.d/ 内のファイルを順次実行
        Scripts->>PG: .sql, .sh を実行
        Scripts-->>Entrypoint: 完了
        Entrypoint->>PG: 再起動(ネットワーク接続可能)
    else PGDATAに既存データあり
        Entrypoint->>PG: 通常起動
    end

対応するファイル形式は以下の通りです。

拡張子 実行方法 備考
.sql psqlで実行 POSTGRES_USERで実行される
.sql.gz gunzip後、psqlで実行 圧縮されたSQLファイル
.sh(実行可能) 直接実行 chmod +x が必要
.sh(実行不可) sourceで読み込み シェル変数の設定など

ファイルはファイル名のアルファベット順(ロケールに依存)で実行されます。明示的に順序を制御したい場合は、プレフィックスに番号を付けます。

テーブル作成スクリプト

プロジェクトのスキーマを初期化するSQLスクリプトの例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-- docker/db/init/01-schema.sql

-- ユーザーテーブル
CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 記事テーブル
CREATE TABLE IF NOT EXISTS articles (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    status VARCHAR(20) DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),
    published_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- インデックス
CREATE INDEX IF NOT EXISTS idx_articles_user_id ON articles(user_id);
CREATE INDEX IF NOT EXISTS idx_articles_status ON articles(status);
CREATE INDEX IF NOT EXISTS idx_articles_published_at ON articles(published_at);

-- 更新日時を自動更新するトリガー関数
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = CURRENT_TIMESTAMP;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- トリガーの設定
CREATE TRIGGER users_updated_at
    BEFORE UPDATE ON users
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER articles_updated_at
    BEFORE UPDATE ON articles
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

テストデータ投入スクリプト

開発用のテストデータを投入するスクリプト例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
-- docker/db/init/02-seed.sql

-- 開発用ユーザー
INSERT INTO users (email, name, password_hash) VALUES
    ('admin@example.com', '管理者', '$2b$10$dummyhashvalue1'),
    ('user1@example.com', 'テストユーザー1', '$2b$10$dummyhashvalue2'),
    ('user2@example.com', 'テストユーザー2', '$2b$10$dummyhashvalue3')
ON CONFLICT (email) DO NOTHING;

-- サンプル記事
INSERT INTO articles (user_id, title, content, status, published_at) VALUES
    (1, 'はじめての投稿', 'これは最初の記事です。', 'published', CURRENT_TIMESTAMP),
    (1, '下書き記事', '公開前の記事内容です。', 'draft', NULL),
    (2, 'ユーザー1の記事', '別のユーザーが投稿した記事です。', 'published', CURRENT_TIMESTAMP - INTERVAL '1 day')
ON CONFLICT DO NOTHING;

環境別の初期化制御

本番環境ではシードデータを投入したくない場合があります。シェルスクリプトで環境変数を参照して制御できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env bash
# docker/db/init/02-seed.sh
set -e

# 環境変数で制御(デフォルトはdevelopment)
APP_ENV=${APP_ENV:-development}

if [ "$APP_ENV" = "development" ] || [ "$APP_ENV" = "test" ]; then
    echo "Seeding database for $APP_ENV environment..."
    psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
        INSERT INTO users (email, name, password_hash) VALUES
            ('admin@example.com', '管理者', '\$2b\$10\$dummyhashvalue1')
        ON CONFLICT (email) DO NOTHING;
EOSQL
    echo "Seeding completed."
else
    echo "Skipping seed data for $APP_ENV environment."
fi

compose.yamlで環境変数を設定します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
services:
  db:
    image: postgres:18
    environment:
      POSTGRES_USER: devuser
      POSTGRES_PASSWORD: devpassword
      POSTGRES_DB: myapp_development
      APP_ENV: development  # development, test, production
    volumes:
      - postgres-data:/var/lib/postgresql
      - ./docker/db/init:/docker-entrypoint-initdb.d:ro

本番環境での考慮点

セキュリティ設定

本番環境でDockerized PostgreSQLを運用する際は、以下のセキュリティ対策が必要です。

パスワード管理

環境変数にパスワードを直接書くのは危険です。Docker Secretsまたは環境変数ファイルを使用しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# compose.yaml(Docker Secrets使用)
services:
  db:
    image: postgres:18
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
      POSTGRES_DB: myapp_production
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt  # このファイルはGit管理外に

もしくは.envファイルを使用します。

1
2
3
4
5
6
# compose.yaml
services:
  db:
    image: postgres:18
    env_file:
      - .env.production  # Git管理外
1
2
3
4
# .env.production
POSTGRES_USER=appuser
POSTGRES_PASSWORD=very_secure_random_password_here
POSTGRES_DB=myapp_production

ネットワークの分離

本番環境では、データベースを外部に公開しないようにします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
services:
  app:
    build: .
    networks:
      - frontend
      - backend

  db:
    image: postgres:18
    # portsを指定しない = 外部からアクセス不可
    networks:
      - backend  # バックエンドネットワークのみ

networks:
  frontend:
  backend:
    internal: true  # 外部ルーティング不可

リソース制限

コンテナに適切なリソース制限を設定し、他のサービスへの影響を防ぎます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
services:
  db:
    image: postgres:18
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4G
        reservations:
          cpus: '0.5'
          memory: 1G
    # PostgreSQLの共有メモリ設定
    shm_size: 256mb

shm_sizeは重要な設定です。PostgreSQLは共有メモリを多く使用するため、デフォルトの64MBでは不足する場合があります。shared_buffersの設定値に応じて調整してください。

PostgreSQL設定のチューニング

本番環境では、デフォルト設定では性能が不足します。カスタム設定ファイルを使用するか、起動時オプションで設定を上書きします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
services:
  db:
    image: postgres:18
    command:
      - "postgres"
      - "-c" 
      - "shared_buffers=1GB"
      - "-c"
      - "effective_cache_size=3GB"
      - "-c"
      - "maintenance_work_mem=256MB"
      - "-c"
      - "work_mem=16MB"
      - "-c"
      - "max_connections=100"
      - "-c"
      - "random_page_cost=1.1"  # SSD使用時
      - "-c"
      - "log_statement=all"      # 本番では'ddl'または'none'推奨
      - "-c"
      - "log_min_duration_statement=1000"  # 1秒以上のクエリをログ

または、カスタム設定ファイルをマウントします。

1
2
3
4
5
6
7
services:
  db:
    image: postgres:18
    volumes:
      - postgres-data:/var/lib/postgresql
      - ./config/postgresql.conf:/etc/postgresql/postgresql.conf:ro
    command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"]

ログ管理

本番環境ではログの適切な管理が重要です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
services:
  db:
    image: postgres:18
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"
    command:
      - "postgres"
      - "-c"
      - "logging_collector=on"
      - "-c"
      - "log_directory=/var/log/postgresql"
      - "-c"
      - "log_filename=postgresql-%Y-%m-%d.log"
      - "-c"
      - "log_rotation_age=1d"
      - "-c"
      - "log_rotation_size=100MB"
    volumes:
      - postgres-logs:/var/log/postgresql

volumes:
  postgres-logs:

コンテナでPostgreSQLを運用する際の注意点

Dockerized PostgreSQLを本番環境で使用する際は、以下の点を考慮してください。

観点 考慮事項 対策
可用性 単一コンテナは単一障害点になる レプリケーション構成、マネージドサービスの検討
バックアップ 定期バックアップの仕組みが必要 cronジョブ、バックアップコンテナの追加
監視 コンテナとPostgreSQL両方の監視が必要 Prometheus + postgres_exporter
アップグレード メジャーバージョンアップは慎重に pg_upgradeの手順確認、十分なテスト
パフォーマンス オーバーヘッドは最小限だが存在する ベンチマークで確認

多くのケースでは、本番環境ではAWS RDS、Google Cloud SQL、Azure Database for PostgreSQLなどのマネージドサービスの利用を検討することを推奨します。

Kubernetes上でのPostgreSQL運用

Kubernetesでのデータベース運用の選択肢

Kubernetes上でPostgreSQLを運用する方法は複数あります。

flowchart TD
    Start["Kubernetes上でPostgreSQLが必要"] --> Decision1{"マネージドDBは使える?"}
    
    Decision1 -->|"はい"| Managed["マネージドサービス\n(RDS, Cloud SQL等)\n⭐推奨"]
    Decision1 -->|"いいえ(オンプレ等)"| Decision2{"運用負荷を許容できる?"}
    
    Decision2 -->|"できるだけ低く"| Operator["PostgreSQL Operator\n(CloudNativePG等)"]
    Decision2 -->|"自前で管理する"| StatefulSet["StatefulSet + Helm"]
    
    Managed --> Note1["接続はExternalName Serviceで抽象化"]
    Operator --> Note2["CRDでPostgreSQLクラスタを宣言的に管理"]
    StatefulSet --> Note3["PV/PVC、ConfigMap、Secretを自前で管理"]

各選択肢の比較です。

選択肢 運用負荷 柔軟性 推奨シーン
マネージドサービス クラウド環境での本番運用
PostgreSQL Operator オンプレ/マルチクラウド
StatefulSet(自前) 最高 特殊要件がある場合

CloudNativePGの概要

CloudNativePGは、CNCFサンドボックスプロジェクトとして2025年1月に採択された、Kubernetes上でPostgreSQLを運用するためのOperatorです。宣言的なリソース定義で、PostgreSQLクラスタのデプロイ、スケーリング、バックアップ、フェイルオーバーを自動化できます。

CloudNativePGの主な機能は以下の通りです。

  • 宣言的なPostgreSQLクラスタ定義(Custom Resource Definition)
  • 自動フェイルオーバーとレプリカ昇格
  • 継続的バックアップ(S3、GCS、Azure Blob対応)
  • ポイントインタイムリカバリ(PITR)
  • ローリングアップデート
  • TLS暗号化通信

基本的なクラスタ定義の例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# cluster.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: myapp-db
spec:
  instances: 3  # プライマリ1 + レプリカ2
  
  postgresql:
    parameters:
      shared_buffers: "256MB"
      effective_cache_size: "768MB"
  
  storage:
    size: 10Gi
    storageClass: standard
  
  bootstrap:
    initdb:
      database: myapp
      owner: myapp
  
  backup:
    barmanObjectStore:
      destinationPath: "s3://my-bucket/backups"
      s3Credentials:
        accessKeyId:
          name: s3-creds
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: s3-creds
          key: SECRET_ACCESS_KEY

この定義をapplyすることで、3ノードのPostgreSQLクラスタが自動的に構築されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# CloudNativePGのインストール
kubectl apply --server-side -f \
  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.25/releases/cnpg-1.25.0.yaml

# クラスタの作成
kubectl apply -f cluster.yaml

# クラスタの状態確認
kubectl get clusters
kubectl get pods -l cnpg.io/cluster=myapp-db

他のPostgreSQL Operatorとの比較

CloudNativePG以外にも、いくつかのPostgreSQL Operatorが存在します。

Operator 開発元 特徴 ライセンス
CloudNativePG EDB CNCFプロジェクト、活発な開発 Apache 2.0
Crunchy PGO Crunchy Data 豊富な機能、商用サポートあり Apache 2.0
Zalando Postgres Operator Zalando 大規模運用実績 MIT
Percona PG Operator Percona Percona製品との統合 Apache 2.0

選択の際は、コミュニティの活発さ、ドキュメントの充実度、商用サポートの有無を考慮してください。

Kubernetesでの運用時の注意点

Kubernetes上でステートフルなワークロード(PostgreSQL)を運用する際は、以下に注意してください。

  1. PersistentVolumeの選択: 適切なStorageClassを選択し、ディスクI/O性能を確保する
  2. Node Affinity: データベースPodが適切なノードにスケジュールされるよう設定する
  3. Pod Disruption Budget: メンテナンス時のダウンタイムを制御する
  4. ネットワークポリシー: データベースへのアクセスを必要なPodに限定する
  5. シークレット管理: External Secrets OperatorやVaultとの連携を検討する

開発ワークフローのベストプラクティス

プロジェクト構成の例

PostgreSQLを使用するプロジェクトの推奨ディレクトリ構成です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
project/
├── compose.yaml              # 開発環境用
├── compose.production.yaml   # 本番環境用(必要に応じて)
├── .env.example              # 環境変数のテンプレート
├── .env                      # 実際の環境変数(Git管理外)
├── docker/
│   └── db/
│       ├── init/
│       │   ├── 01-schema.sql     # スキーマ定義
│       │   ├── 02-seed.sql       # シードデータ
│       │   └── 03-extensions.sql # 拡張機能
│       └── config/
│           └── postgresql.conf   # カスタム設定(必要に応じて)
├── .gitignore
└── src/
    └── ...

.gitignoreの設定例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 環境変数ファイル
.env
.env.production
.env.*.local

# シークレット
secrets/

# バックアップファイル
*.sql
*.dump
*.tar
backup/

便利なMakefileの作成

よく使うコマンドをMakefileにまとめておくと便利です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
.PHONY: up down logs db-shell db-reset db-backup db-restore

# 開発環境の起動
up:
	docker compose up -d --wait

# 開発環境の停止
down:
	docker compose down

# ログの表示
logs:
	docker compose logs -f db

# PostgreSQLに接続
db-shell:
	docker compose exec db psql -U devuser -d myapp_development

# データベースのリセット(注意: 全データ削除)
db-reset:
	docker compose down -v
	docker compose up -d --wait

# バックアップの作成
db-backup:
	@mkdir -p backup
	docker compose exec db pg_dump -U devuser -Fc myapp_development > backup/backup-$$(date +%Y%m%d-%H%M%S).dump
	@echo "Backup created: backup/backup-$$(date +%Y%m%d-%H%M%S).dump"

# 最新のバックアップからリストア
db-restore:
	@LATEST=$$(ls -t backup/*.dump 2>/dev/null | head -1); \
	if [ -z "$$LATEST" ]; then \
		echo "No backup found in backup/"; \
		exit 1; \
	fi; \
	echo "Restoring from $$LATEST..."; \
	docker compose exec -T db pg_restore -U devuser -d myapp_development --clean < $$LATEST

まとめ

本記事では、PostgreSQLとDockerを組み合わせた実践的な環境構築から本番運用までを解説しました。

開発環境では、Docker Composeを使うことで、チームメンバー全員が同じデータベース環境を簡単に構築できます。初期化スクリプトを活用すれば、スキーマ定義やテストデータの投入も自動化できます。

本番環境では、セキュリティ設定(パスワード管理、ネットワーク分離)、リソース制限、適切なPostgreSQL設定のチューニングが重要です。多くのケースでは、マネージドサービスの利用を検討することを推奨します。

Kubernetes環境では、CloudNativePGなどのOperatorを使うことで、PostgreSQLクラスタの運用を自動化できます。ただし、ステートフルワークロードの運用には固有の課題があるため、十分な検証が必要です。

Dockerを活用することで、PostgreSQLの環境構築と運用を大幅に効率化できます。本記事で紹介した設定やベストプラクティスを参考に、プロジェクトに最適な構成を構築してください。

参考リンク