GitHub Actions で PostgreSQL/Redis を services で立ち上げるテスト基盤レシピ
CI/CD パイプラインでデータベースやキャッシュサーバーを使ったテストを実行したいとき、環境構築に悩んだことはありませんか。GitHub Actions には services という強力な機能があり、PostgreSQL や Redis といったミドルウェアをコンテナとして簡単に起動できます。
この記事では、GitHub Actions の services 機能を使って PostgreSQL と Redis を立ち上げ、実際のアプリケーションテストを実行する方法を段階的に解説します。初めて CI 環境でデータベースを扱う方でも、すぐに実践できる内容になっていますよ。
背景
CI/CD におけるテスト環境の課題
現代の Web アプリケーション開発では、コードの品質を保つために自動テストが欠かせません。しかし、ローカル環境では動作するテストが、CI 環境では失敗してしまうケースがあります。
特にデータベースやキャッシュサーバーに依存するテストでは、CI 環境にも同じミドルウェアを用意する必要があるのです。
GitHub Actions の services 機能とは
GitHub Actions には services という機能があり、ワークフロー実行時に Docker コンテナとしてミドルウェアを起動できます。これにより、PostgreSQL や Redis などを簡単にテスト環境へ組み込めるようになりました。
services で起動したコンテナは、ジョブの実行中だけ存在し、終了後は自動的にクリーンアップされます。環境を汚さず、毎回クリーンな状態でテストできるのが大きな魅力ですね。
以下の図は、GitHub Actions のワークフロー実行時に services がどのように動作するかを示しています。
mermaidflowchart TB
trigger["ワークフローのトリガー<br/>(push/PR)"] -->|開始| setup["services コンテナ起動<br/>(PostgreSQL/Redis)"]
setup -->|準備完了| job["ジョブ実行<br/>(テストコード)"]
job -->|接続| postgres[("PostgreSQL<br/>コンテナ")]
job -->|接続| redis[("Redis<br/>コンテナ")]
job -->|完了| cleanup["コンテナ自動削除"]
cleanup --> finish["ワークフロー終了"]
図で理解できる要点:
- ワークフロー開始時に services コンテナが自動的に起動されます
- ジョブ内のテストコードは、起動済みのコンテナへ直接接続できます
- ジョブ完了後、コンテナは自動的にクリーンアップされます
Docker Hub との連携
GitHub Actions の services は、Docker Hub 上の公式イメージを指定するだけで利用できます。PostgreSQL なら postgres イメージ、Redis なら redis イメージを使用することで、複雑な設定なしにすぐ動かせるのです。
課題
テスト環境構築の複雑さ
従来の CI 環境では、データベースをセットアップするために以下のような手順が必要でした。
- パッケージマネージャーでミドルウェアをインストール
- 起動スクリプトを実行し、サービスを開始
- 接続待機のためのヘルスチェック実装
- テスト用データベースやユーザーの作成
- 環境変数や設定ファイルの準備
これらの手順は複雑で、メンテナンスコストも高くなりがちです。
バージョン管理とポータビリティの問題
CI 環境にインストールされているミドルウェアのバージョンが古かったり、ローカル環境と異なったりすることもあります。また、CI サービスを移行する際には、すべての環境構築手順を書き直す必要がありました。
以下の図は、従来の課題と services による解決策を対比しています。
mermaidflowchart LR
subgraph before["従来の方法"]
direction TB
install1["パッケージ<br/>インストール"]
install1 --> config1["設定ファイル<br/>作成"]
config1 --> start1["サービス起動"]
start1 --> wait1["ヘルスチェック<br/>待機"]
wait1 --> init1["初期化<br/>スクリプト"]
end
subgraph after["services による解決"]
direction TB
define["services 定義<br/>(YAML)"]
define --> auto["自動起動/<br/>ヘルスチェック"]
auto --> ready["すぐ利用可能"]
end
before -.->|簡素化| after
図で理解できる要点:
- 従来は複数のステップが必要だった環境構築が、services では YAML 定義だけで完結します
- ヘルスチェックや起動待機も自動化されます
- ポータブルな設定で、他の CI 環境への移行も容易になります
接続情報の管理
データベースやキャッシュサーバーへの接続には、ホスト名、ポート番号、認証情報などが必要です。これらを適切に管理し、テストコードから参照できるようにする必要がありました。
解決策
services 機能による統一的な定義
GitHub Actions の services を使えば、ワークフローファイル(YAML)にコンテナの定義を記述するだけで、必要なミドルウェアが自動的に起動されます。
services の主な利点は以下の通りです。
- Docker コンテナとして起動されるため、環境の再現性が高い
- ヘルスチェックが自動的に実行され、準備完了まで待機してくれる
- ジョブ終了後は自動的にクリーンアップされる
- 複数のサービスを同時に起動できる
ネットワーキングの自動設定
services で起動されたコンテナは、GitHub Actions ランナーから自動的にアクセス可能になります。サービス名がそのままホスト名として使えるため、接続設定も簡単です。
例えば、postgres という名前で定義した PostgreSQL サービスには、localhost:5432(Linux ランナーの場合)や postgres:5432(Docker コンテナジョブの場合)で接続できます。
環境変数による設定の注入
PostgreSQL や Redis の初期設定(パスワード、データベース名など)は、services 定義内の env セクションで環境変数として渡せます。これにより、コンテナ起動時に自動的に設定が適用されるのです。
以下の図は、services を使ったテスト実行の全体フローを示しています。
mermaidsequenceDiagram
participant GHA as GitHub Actions
participant PSrv as PostgreSQL<br/>Service
participant RSrv as Redis<br/>Service
participant Job as テストジョブ
GHA->>PSrv: コンテナ起動<br/>(env 設定適用)
GHA->>RSrv: コンテナ起動<br/>(env 設定適用)
PSrv->>GHA: ヘルスチェック<br/>OK
RSrv->>GHA: ヘルスチェック<br/>OK
GHA->>Job: ジョブ開始
Job->>PSrv: DB 接続<br/>(localhost:5432)
Job->>RSrv: キャッシュ接続<br/>(localhost:6379)
Job->>Job: テスト実行
Job->>GHA: ジョブ完了
GHA->>PSrv: コンテナ停止/削除
GHA->>RSrv: コンテナ停止/削除
図で理解できる要点:
- services コンテナは環境変数を受け取って自動的に初期化されます
- ヘルスチェックによって、サービスが準備完了してからジョブが開始されます
- テストジョブは localhost 経由で各サービスへアクセスできます
具体例
基本的な PostgreSQL サービスの定義
まずは、PostgreSQL だけを起動する最小限の設定から始めましょう。
ワークフローファイルの作成
.github/workflows ディレクトリ内に、YAML ファイルを作成します。
yamlname: PostgreSQL テスト
on:
push:
branches: [main]
pull_request:
branches: [main]
ワークフローのトリガーを定義しました。main ブランチへの push や PR 作成時に実行されます。
ジョブと services の定義
次に、ジョブと PostgreSQL サービスを定義します。
yamljobs:
test:
runs-on: ubuntu-latest
Ubuntu の最新版ランナーを使用します。Linux ランナーでは、services は localhost 経由でアクセスできますよ。
yamlservices:
postgres:
image: postgres:15
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
services セクションで PostgreSQL コンテナを定義しました。postgres:15 という Docker イメージを使い、環境変数で初期設定を指定しています。
- POSTGRES_USER: データベースユーザー名
- POSTGRES_PASSWORD: パスワード
- POSTGRES_DB: 作成するデータベース名
ポートマッピングとヘルスチェック
yamlports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports でポートマッピングを指定し、ホストマシンの 5432 ポートをコンテナの 5432 ポートにマッピングしています。
options では、ヘルスチェックの設定を記述しました。
- --health-cmd: ヘルスチェックコマンド(
pg_isreadyで PostgreSQL の準備状態を確認) - --health-interval: チェック間隔(10 秒ごと)
- --health-timeout: タイムアウト時間(5 秒)
- --health-retries: リトライ回数(5 回)
これにより、PostgreSQL が完全に起動してからテストが開始されます。
Redis サービスの追加
PostgreSQL に加えて、Redis も同時に起動してみましょう。
Redis サービスの定義
同じ services セクション内に Redis を追加します。
yamlservices:
postgres:
# ... (前述の PostgreSQL 設定)
redis:
image: redis:7
Redis の公式イメージ(バージョン 7)を使用します。Redis は認証なしでもデフォルトで動作するため、環境変数の設定は最小限で済みますね。
Redis のポートマッピングとヘルスチェック
yamlports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
Redis のデフォルトポート 6379 をマッピングし、redis-cli ping コマンドでヘルスチェックを行います。
テストステップの実装
ここからは、実際にサービスへ接続してテストを実行するステップを定義します。
チェックアウトと依存関係のインストール
yamlsteps:
- name: コードのチェックアウト
uses: actions/checkout@v4
- name: Node.js のセットアップ
uses: actions/setup-node@v4
with:
node-version: '20'
リポジトリのコードをチェックアウトし、Node.js 20 をセットアップします。
yaml- name: 依存関係のインストール
run: yarn install --frozen-lockfile
Yarn を使って依存パッケージをインストールしました。--frozen-lockfile オプションで、yarn.lock ファイルの変更を防ぎます。
環境変数の設定
テストコードから接続情報を参照できるよう、環境変数を設定します。
yaml- name: テスト実行
env:
DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
REDIS_URL: redis://localhost:6379
run: yarn test
DATABASE_URL と REDIS_URL を環境変数として定義し、テストコマンド yarn test を実行します。
テストコード内では、これらの環境変数を参照して接続するだけで、PostgreSQL と Redis を使ったテストが実行できますよ。
実際の接続テストコード例
Node.js/TypeScript でのテストコード例を示します。
PostgreSQL 接続のテスト
まず、PostgreSQL へ接続するテストコードです。
typescriptimport { Client } from 'pg';
describe('PostgreSQL 接続テスト', () => {
let client: Client;
beforeAll(async () => {
// 環境変数から接続情報を取得
client = new Client({
connectionString: process.env.DATABASE_URL,
});
pg パッケージの Client を使い、環境変数 DATABASE_URL から接続情報を取得しています。
typescript // データベースへ接続
await client.connect();
});
afterAll(async () => {
// テスト終了後に接続を閉じる
await client.end();
});
beforeAll で接続を確立し、afterAll でクリーンアップを行います。
typescript test('テーブルの作成と挿入', async () => {
// テーブル作成
await client.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
)
`);
簡単なテーブルを作成しました。SERIAL 型で自動採番される主キーを定義しています。
typescript// データ挿入
const insertResult = await client.query(
'INSERT INTO users (name) VALUES ($1) RETURNING *',
['テストユーザー']
);
// 挿入されたデータを検証
expect(insertResult.rows[0].name).toBe('テストユーザー');
プレースホルダー $1 を使ってデータを安全に挿入し、戻り値を検証しています。
typescript // データ取得
const selectResult = await client.query('SELECT * FROM users');
expect(selectResult.rows.length).toBeGreaterThan(0);
});
});
挿入したデータが正しく取得できることを確認しました。これで PostgreSQL への接続が正常に動作していることが検証できます。
Redis 接続のテスト
次に、Redis への接続をテストするコードです。
typescriptimport { createClient, RedisClientType } from 'redis';
describe('Redis 接続テスト', () => {
let redisClient: RedisClientType;
beforeAll(async () => {
// 環境変数から接続情報を取得
redisClient = createClient({
url: process.env.REDIS_URL,
});
redis パッケージの createClient を使い、環境変数から Redis の URL を取得しています。
typescript // Redis へ接続
await redisClient.connect();
});
afterAll(async () => {
// 接続を閉じる
await redisClient.quit();
});
connect で接続し、テスト終了時には quit で切断します。
typescript test('キャッシュの保存と取得', async () => {
const key = 'test:key';
const value = 'テスト値';
// 値を保存
await redisClient.set(key, value);
Redis に文字列データを保存しました。キーには test: というプレフィックスを付けて名前空間を分けています。
typescript// 値を取得
const retrieved = await redisClient.get(key);
expect(retrieved).toBe(value);
保存した値が正しく取得できることを確認しています。
typescript // 有効期限付きでデータを保存
await redisClient.setEx('test:expiring', 60, 'この値は60秒で期限切れ');
const expiringValue = await redisClient.get('test:expiring');
expect(expiringValue).toBe('この値は60秒で期限切れ');
});
});
setEx を使って、TTL(生存期間)付きでデータを保存しました。キャッシュの有効期限設定が正しく動作することを確認できます。
完全なワークフローファイル
これまでの内容をまとめた、完全なワークフローファイルをご紹介します。
yamlname: PostgreSQL と Redis を使ったテスト
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
test:
runs-on: ubuntu-latest
services:
# PostgreSQL サービス
postgres:
image: postgres:15
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
# Redis サービス
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: コードのチェックアウト
uses: actions/checkout@v4
- name: Node.js のセットアップ
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
- name: 依存関係のインストール
run: yarn install --frozen-lockfile
- name: テスト実行
env:
DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
REDIS_URL: redis://localhost:6379
NODE_ENV: test
run: yarn test
- name: カバレッジレポートのアップロード
uses: codecov/codecov-action@v3
if: always()
with:
files: ./coverage/coverage-final.json
このワークフローでは、以下の機能が実装されています。
| # | 機能 | 説明 |
|---|---|---|
| 1 | マルチブランチ対応 | main と develop ブランチへの push/PR で実行 |
| 2 | 複数サービス起動 | PostgreSQL と Redis を同時起動 |
| 3 | ヘルスチェック | 各サービスの準備完了を自動確認 |
| 4 | キャッシュ活用 | Node.js のキャッシュで依存関係インストールを高速化 |
| 5 | カバレッジレポート | テスト結果を Codecov へアップロード |
パフォーマンス最適化のポイント
実際の運用では、以下のような最適化も検討できます。
イメージのバージョン固定
yamlservices:
postgres:
image: postgres:15.3 # メジャー・マイナー・パッチまで固定
バージョンを詳細に指定することで、予期しない動作変更を防げます。ただし、セキュリティパッチが適用されなくなるため、定期的な更新が必要ですね。
不要なログの抑制
yamlservices:
postgres:
# ... (他の設定)
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
-c log_statement=none
-c log_statement=none オプションで、PostgreSQL の詳細なログ出力を抑制できます。ログ量が減ることで、ワークフローの実行速度がわずかに向上します。
Redis の永続化無効化
yamlservices:
redis:
# ... (他の設定)
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
--save ""
--appendonly no
テスト環境では永続化が不要なため、--save "" と --appendonly no で無効化します。これにより、ディスク I/O が減少し、パフォーマンスが向上するのです。
エラーハンドリングと診断
テストが失敗した場合の診断方法も押さえておきましょう。
サービスログの確認
yaml- name: PostgreSQL ログの出力(エラー時)
if: failure()
run: |
docker logs $(docker ps -q --filter "ancestor=postgres:15")
テスト失敗時(if: failure())に、PostgreSQL コンテナのログを出力します。接続エラーや初期化エラーの原因を特定しやすくなりますよ。
接続テストの追加
yaml- name: PostgreSQL 接続確認
run: |
sudo apt-get install -y postgresql-client
PGPASSWORD=testpass psql -h localhost -U testuser -d testdb -c "SELECT version();"
本格的なテストの前に、psql コマンドで接続できるか確認するステップを追加しました。これにより、サービス起動の問題とアプリケーションコードの問題を切り分けられます。
Redis 接続確認
yaml- name: Redis 接続確認
run: |
redis-cli -h localhost ping
redis-cli で Redis への接続を確認します。PONG が返ってくれば、Redis が正常に動作していることがわかりますね。
マトリックス戦略による複数バージョンテスト
異なるバージョンの PostgreSQL や Redis でテストしたい場合は、マトリックス戦略が便利です。
yamljobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
postgres-version: [13, 14, 15]
redis-version: [6, 7]
複数のバージョンの組み合わせでテストを実行する設定です。
yamlservices:
postgres:
image: postgres:${{ matrix.postgres-version }}
# ... (他の設定)
redis:
image: redis:${{ matrix.redis-version }}
# ... (他の設定)
${{ matrix.postgres-version }} のように、マトリックス変数を使ってイメージのバージョンを動的に指定できます。
この設定により、PostgreSQL の 3 バージョン × Redis の 2 バージョン = 合計 6 パターンの組み合わせでテストが自動実行されます。互換性の問題を早期に発見できるのは心強いですね。
Docker コンテナジョブでの使用
ジョブ自体を Docker コンテナ内で実行する場合は、接続方法が少し異なります。
yamljobs:
test:
runs-on: ubuntu-latest
container:
image: node:20
ジョブを Node.js 20 のコンテナ内で実行します。
yamlservices:
postgres:
image: postgres:15
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
# ports は不要(コンテナ間通信)
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
コンテナジョブでは、ports の指定は不要です。services コンテナとジョブコンテナは同じ Docker ネットワーク内にあるため、直接通信できます。
yamlsteps:
- name: テスト実行
env:
# localhost ではなくサービス名を使用
DATABASE_URL: postgresql://testuser:testpass@postgres:5432/testdb
REDIS_URL: redis://redis:6379
run: yarn test
接続先のホスト名は localhost ではなく、サービス名(postgres、redis)を使います。Docker ネットワークの DNS によって、サービス名が自動的に解決されるのです。
まとめ
GitHub Actions の services 機能を使えば、PostgreSQL や Redis といったミドルウェアを簡単にテスト環境へ組み込めます。
本記事でご紹介した内容をまとめると、以下のようになります。
押さえておきたいポイント
| # | ポイント | 説明 |
|---|---|---|
| 1 | services の定義 | ワークフロー YAML に Docker イメージとポート、環境変数を記述 |
| 2 | ヘルスチェック | options でヘルスチェックコマンドを指定し、準備完了を自動確認 |
| 3 | 接続方法 | Linux ランナーでは localhost、コンテナジョブではサービス名を使用 |
| 4 | 環境変数 | env セクションで初期設定を注入し、テストステップで接続情報を参照 |
| 5 | 複数サービス | PostgreSQL と Redis を同時に起動し、統合的なテストを実行可能 |
services を使うメリット
- シンプルな設定:YAML ファイルだけで完結し、複雑なセットアップスクリプトが不要です
- 再現性:Docker イメージを使うため、ローカル環境と CI 環境の差異が最小化されます
- 自動クリーンアップ:ジョブ終了後、コンテナは自動的に削除され、環境が汚れません
- 並列実行:マトリックス戦略と組み合わせて、複数バージョンでの互換性テストも簡単です
今後の活用に向けて
本記事で紹介した設定をベースに、以下のような拡張も検討できます。
- MySQL や MariaDB: PostgreSQL の代わりに MySQL を使う
- MongoDB: NoSQL データベースのテスト環境を構築
- Elasticsearch: 全文検索機能のテスト
- RabbitMQ や Kafka: メッセージキューを使った非同期処理のテスト
GitHub Actions の services は、これらすべてに対応できる柔軟な機能です。ぜひ、あなたのプロジェクトでも活用してみてください。
データベースやキャッシュサーバーを含む統合テストが CI で自動実行できるようになれば、コードの品質と開発速度の両方を大きく向上させられますよ。
関連リンク
articleGitHub Actions で PostgreSQL/Redis を services で立ち上げるテスト基盤レシピ
articleGitHub Actions 署名戦略を比べる:SHA ピン留め vs タグ参照 vs バージョン範囲
articleGitHub Actions のキャッシュがヒットしない原因 10 と対処レシピ
articleGitHub Actions コンテキスト辞典:github/env/runner/secrets の使い分け最速理解
articleGitHub Actions 実行コストを見える化:Usage API でジョブ別分析ダッシュボード
articleGitHub Actions でゼロダウンタイムリリース:canary/blue-green をパイプライン実装
articleGitHub Copilot Enterprise 初期構築:SSO/SCIM・ポリシー・配布ロールアウト設計
articleDevin が強い開発フェーズはどこ?要件定義~運用までの適合マップ
articleConvex 運用監視ダッシュボード構築:Datadog/Grafana 連携と SLO 設計
articleGitHub Actions で PostgreSQL/Redis を services で立ち上げるテスト基盤レシピ
articleGit の依存取り込み比較:subtree vs submodule — 運用コストと安全性の実態
articleComfyUI チートシート:よく使うノード 50 個の役割と接続例早見表
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来