Skip to content

インフラストラクチャ構成ガイド

転職エージェント業務管理システム


目次

  1. アーキテクチャ概要
  2. 前提条件・事前準備
  3. Cloudflare Accessセットアップ
  4. Supabaseセットアップ
  5. GCPセットアップ
  6. Cloudflare Pagesセットアップ
  7. CI/CDパイプライン
  8. フロントエンド構築手順
  9. バックエンド構築手順
  10. 環境変数一覧
  11. デプロイ手順

1. アーキテクチャ概要

全体構成図

┌─────────────────────────────────────────────────────────────────────────┐
│                         アーキテクチャ全体図                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ┌─────────────┐     HTTPS      ┌──────────────────────┐              │
│   │   Client    │ ─────────────► │  Cloudflare Pages    │              │
│   │  (Browser)  │                │  (React SPA)         │              │
│   └─────────────┘                └──────────────────────┘              │
│          │                                 │                            │
│          │ Cloudflare Access               │ API Request               │
│          │ (Google IdP認証)                │ (CF-Access-JWT-Assertion) │
│          ▼                                 ▼                           │
│   ┌─────────────┐                ┌──────────────────────┐              │
│   │ Cloudflare  │                │  Cloud Run           │              │
│   │   Access    │                │  (Hono + Node 22)    │              │
│   └─────────────┘                │  ※JWT検証            │              │
│                                  └──────────────────────┘              │
│                                            │                            │
│                                            ▼ Direct Connection         │
│                                  ┌──────────────────────┐              │
│                                  │   Supabase           │              │
│                                  │   (PostgreSQL)       │              │
│                                  └──────────────────────┘              │
│                                                                         │
│   ┌─────────────────────────────────────────────────────────────────┐  │
│   │                      GitHub Actions (CI/CD)                      │  │
│   │  ・フロントエンド: v* タグ → Cloudflare Pages                     │  │
│   │  ・バックエンド: api-v* タグ → Cloud Run                          │  │
│   └─────────────────────────────────────────────────────────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

技術スタック

レイヤー技術バージョン
フロントエンドReact + TypeScript19.x
ビルドツールVite6.x
スタイリングTailwind CSS + shadcn/ui4.x
データ取得TanStack React Query5.x
バックエンドHono4.x
ランタイムNode.js22.x
データベースSupabase (PostgreSQL)-
認証Cloudflare Access (Google IdP)-
フロントホスティングCloudflare Pages-
APIホスティングCloud Run-

2. 前提条件・事前準備

必要なアカウント

サービス用途料金
GitHubリポジトリ管理・CI/CD無料
Cloudflareフロントエンドホスティング・認証無料枠あり
Google Cloud PlatformバックエンドAPI (Cloud Run)従量課金
Supabaseデータベース (PostgreSQL)無料枠あり
Google Workspace認証用ドメイン有料

ローカル開発環境

bash
# Node.js 22 (nvm推奨)
nvm install 22
nvm use 22

# Google Cloud CLI
curl https://sdk.cloud.google.com | bash
gcloud init

# Wrangler CLI (Cloudflare)
npm install -g wrangler

# Supabase CLI
npm install -g supabase

# Docker (Cloud Run開発用)
# https://docs.docker.com/get-docker/

3. Cloudflare Accessセットアップ

3.1 Zero Trustダッシュボードにアクセス

  1. Cloudflareダッシュボード → Zero Trust を選択
  2. 初回は Team name を設定(例: yourcompany
  3. これが yourcompany.cloudflareaccess.com になる

3.2 Google IdPを追加

  1. SettingsAuthenticationLogin methods
  2. Add newGoogle
  3. Google Cloud Consoleで OAuth 2.0 クライアントを作成:
    • 承認済みリダイレクトURI: https://yourcompany.cloudflareaccess.com/cdn-cgi/access/callback
  4. Client IDとClient Secretを入力
  5. Save

3.3 Applicationを作成(フロントエンド用)

  1. AccessApplicationsAdd an application
  2. Self-hosted を選択
  3. 設定:
項目
Application nameAgent Management System
Session Duration24 hours
Application domainyour-app.yourdomain.com
  1. Policies タブで Add a policy:
項目
Policy nameAllow Company Domain
ActionAllow
IncludeEmails ending in @yourcompany.co.jp
  1. Save

3.4 取得すべき値

項目用途確認場所
Team Domainyourcompany.cloudflareaccess.comSettings → General
Application Audience (AUD)JWT検証用Applications → 対象App → Overview

4. Supabaseセットアップ

4.1 プロジェクト作成

  1. Supabase にサインアップ
  2. New Project を作成
  3. リージョン: Northeast Asia (Tokyo) を選択
  4. Database Password を設定(控えておく)

4.2 テーブル作成

SQLエディタで以下を実行(別途提供のSQLファイルを使用)

4.3 Direct Connection設定

  1. SettingsDatabase
  2. Connection stringURI をコピー
  3. これが SUPABASE_DB_URL になる

4.4 取得すべき値

項目用途確認場所
Project URLAPIアクセスSettings → API
anon key公開用APIキーSettings → API
service_role keyサーバー用APIキーSettings → API
Database URL直接接続用Settings → Database

5. GCPセットアップ

5.1 プロジェクト作成

bash
gcloud projects create your-project-id --name="Agent Management"
gcloud config set project your-project-id

5.2 必要なAPIを有効化

bash
gcloud services enable \
  run.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  iam.googleapis.com

5.3 Artifact Registryリポジトリ作成

bash
gcloud artifacts repositories create agent-management \
  --repository-format=docker \
  --location=asia-northeast1 \
  --description="Docker images for Agent Management"

5.4 GitHub Actions用Workload Identity Federation設定

Workload Identity Pool作成

bash
gcloud iam workload-identity-pools create "github-pool" \
  --location="global" \
  --display-name="GitHub Actions Pool"

Workload Identity Provider作成

bash
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
  --location="global" \
  --workload-identity-pool="github-pool" \
  --display-name="GitHub Provider" \
  --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
  --issuer-uri="https://token.actions.githubusercontent.com"

サービスアカウント作成

bash
gcloud iam service-accounts create github-actions-deployer \
  --display-name="GitHub Actions Deployer"

権限付与

bash
PROJECT_ID=$(gcloud config get-value project)
SA_EMAIL="github-actions-deployer@${PROJECT_ID}.iam.gserviceaccount.com"

# Cloud Run デプロイ権限
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/run.admin"

# Artifact Registry 権限
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/artifactregistry.writer"

# サービスアカウントユーザー権限
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/iam.serviceAccountUser"

# Workload Identity 連携
gcloud iam service-accounts add-iam-policy-binding $SA_EMAIL \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.repository/YOUR_ORG/YOUR_REPO"

5.5 取得すべき値

項目取得コマンド
Project IDgcloud config get-value project
Project Numbergcloud projects describe $(gcloud config get-value project) --format='value(projectNumber)'
Workload Identity Providerprojects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/providers/github-provider
Service Account Emailgithub-actions-deployer@PROJECT_ID.iam.gserviceaccount.com

6. Cloudflare Pagesセットアップ

6.1 Pagesプロジェクト作成

bash
wrangler login
wrangler pages project create agent-management-frontend

6.2 カスタムドメイン設定

  1. Cloudflareダッシュボード → Pages → プロジェクト選択
  2. Custom domainsSet up a custom domain
  3. ドメインを入力(例: app.yourdomain.com
  4. DNSレコードが自動設定される

6.3 Cloudflare AccessとPagesの連携

  1. Zero Trustダッシュボード → AccessApplications
  2. 作成したApplicationの Application domain がPagesのドメインと一致していることを確認

6.4 取得すべき値

項目取得場所
Account IDダッシュボード右サイドバー
API TokenMy Profile → API Tokens → Create Token
Project Name作成したプロジェクト名

7. CI/CDパイプライン

7.1 GitHub Secrets設定一覧

Secret名説明用途
GCP
GCP_PROJECT_IDGCPプロジェクトIDAPI
GCP_REGIONCloud Runリージョン(例: asia-northeast1)API
GCP_SERVICE_NAMECloud Runサービス名API
GCP_WORKLOAD_IDENTITY_PROVIDERWorkload Identity ProviderAPI
GCP_SERVICE_ACCOUNTサービスアカウントメールAPI
Supabase
SUPABASE_URLSupabaseプロジェクトURLAPI
SUPABASE_SERVICE_ROLE_KEYSupabaseサービスロールキーAPI
SUPABASE_DB_URLSupabase Direct Connection URLAPI
Cloudflare Access
CF_ACCESS_TEAM_DOMAINCloudflare AccessチームドメインAPI
CF_ACCESS_AUDApplication Audience TagAPI
Cloudflare Pages
CLOUDFLARE_API_TOKENCloudflare APIトークンFrontend
CLOUDFLARE_ACCOUNT_IDCloudflareアカウントIDFrontend
CLOUDFLARE_PROJECT_NAMEPagesプロジェクト名Frontend
API
VITE_API_ROOT_URLCloud RunサービスURLFrontend

7.2 デプロイフロー

┌─────────────────────────────────────────────────────────────────┐
│                      デプロイフロー                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [フロントエンド]                                                │
│  git tag v1.0.0 && git push origin v1.0.0                       │
│       │                                                         │
│       ▼                                                         │
│  GitHub Actions: deploy-cloudflare.yml                          │
│       │                                                         │
│       ▼                                                         │
│  npm ci → npm run build → wrangler pages deploy                 │
│       │                                                         │
│       ▼                                                         │
│  Cloudflare Pages (本番公開)                                     │
│                                                                 │
│  [バックエンド]                                                  │
│  git tag api-v1.0.0 && git push origin api-v1.0.0               │
│       │                                                         │
│       ▼                                                         │
│  GitHub Actions: deploy-api.yml                                 │
│       │                                                         │
│       ▼                                                         │
│  Docker build → Artifact Registry push → Cloud Run deploy       │
│       │                                                         │
│       ▼                                                         │
│  Cloud Run (本番公開)                                            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

8. フロントエンド構築手順

8.1 プロジェクト初期化

bash
npm create vite@latest frontend -- --template react-ts
cd frontend
npm install

# 主要パッケージ
npm install @tanstack/react-query react-router-dom axios zod zustand

# UI関連
npm install tailwindcss @tailwindcss/postcss autoprefixer
npm install class-variance-authority clsx tailwind-merge lucide-react

# shadcn/ui初期化
npx shadcn@latest init

8.2 ディレクトリ構成

frontend/
├── src/
│   ├── components/
│   │   ├── ui/              # shadcn/uiコンポーネント
│   │   ├── features/        # 機能別コンポーネント
│   │   │   ├── candidates/  # 求職者関連
│   │   │   ├── companies/   # 企業関連
│   │   │   ├── applications/# 選考関連
│   │   │   ├── dashboard/   # ダッシュボード
│   │   │   └── reports/     # 集計・レポート
│   │   └── layout/          # レイアウトコンポーネント
│   ├── hooks/               # カスタムフック
│   │   └── queries/         # React Query フック
│   ├── lib/                 # ユーティリティ・APIクライアント
│   │   ├── api-client.ts
│   │   └── utils.ts
│   ├── pages/               # ページコンポーネント
│   ├── stores/              # Zustand store
│   ├── types/               # 型定義
│   ├── App.tsx
│   ├── main.tsx
│   └── index.css
├── public/
├── package.json
├── vite.config.ts
├── tsconfig.json
└── .env.example

8.3 認証について

Cloudflare Accessがインフラレベルで認証を行うため、フロントエンドコードに認証ロジックは不要

APIリクエスト時に CF-Access-JWT-Assertion ヘッダーが自動的に付与される。

typescript
// src/lib/api-client.ts
import axios from 'axios'

const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_ROOT_URL,
  withCredentials: true, // Cookieを送信(CF-Access-JWT-Assertionが含まれる)
})

export default apiClient

9. バックエンド構築手順

9.1 プロジェクト初期化

bash
mkdir api && cd api
npm init -y

# 本番依存
npm install hono @hono/node-server jose pg
npm install @supabase/supabase-js

# 開発依存
npm install -D typescript @types/node @types/pg tsx vitest

9.2 ディレクトリ構成(クリーンアーキテクチャ)

api/
├── src/
│   ├── domain/                    # ドメイン層
│   │   ├── entities/             # エンティティ
│   │   │   ├── Candidate.ts
│   │   │   ├── Company.ts
│   │   │   ├── Application.ts
│   │   │   └── ...
│   │   └── repositories/         # リポジトリインターフェース
│   │       ├── ICandidateRepository.ts
│   │       ├── ICompanyRepository.ts
│   │       └── ...
│   ├── application/              # アプリケーション層
│   │   └── services/             # ユースケース・サービス
│   │       ├── CandidateService.ts
│   │       ├── CompanyService.ts
│   │       ├── ApplicationService.ts
│   │       ├── ReportService.ts
│   │       └── ...
│   ├── infrastructure/           # インフラ層
│   │   ├── database/            # DBクライアント
│   │   │   └── supabase.ts
│   │   └── repositories/        # リポジトリ実装
│   │       └── supabase/
│   │           ├── CandidateRepository.ts
│   │           ├── CompanyRepository.ts
│   │           └── ...
│   ├── middleware/              # ミドルウェア
│   │   ├── auth.ts             # Cloudflare Access JWT検証
│   │   ├── logger.ts
│   │   └── error-handler.ts
│   ├── routes/                  # ルーティング
│   │   ├── index.ts
│   │   ├── candidates.ts
│   │   ├── companies.ts
│   │   ├── applications.ts
│   │   └── reports.ts
│   ├── types/                   # 型定義
│   ├── app.ts                  # Honoアプリケーション
│   └── index.ts               # エントリーポイント
├── Dockerfile
├── package.json
├── tsconfig.json
└── .env.example

9.3 Cloudflare Access JWT検証ミドルウェア

typescript
// src/middleware/auth.ts
import { Context, Next } from 'hono'
import * as jose from 'jose'

const CF_ACCESS_TEAM_DOMAIN = process.env.CF_ACCESS_TEAM_DOMAIN!
const CF_ACCESS_AUD = process.env.CF_ACCESS_AUD!

const CERTS_URL = `https://${CF_ACCESS_TEAM_DOMAIN}/cdn-cgi/access/certs`

export async function authMiddleware(c: Context, next: Next) {
  const cfToken = c.req.header('CF-Access-JWT-Assertion')

  if (!cfToken) {
    return c.json({ error: 'Unauthorized: No access token' }, 401)
  }

  try {
    const jwks = jose.createRemoteJWKSet(new URL(CERTS_URL))
    const { payload } = await jose.jwtVerify(cfToken, jwks, {
      audience: CF_ACCESS_AUD,
    })

    // ユーザー情報をコンテキストにセット
    c.set('user', {
      email: payload.email,
      sub: payload.sub,
    })

    await next()
  } catch (error) {
    console.error('JWT verification failed:', error)
    return c.json({ error: 'Unauthorized: Invalid token' }, 401)
  }
}

9.4 Dockerfile

dockerfile
FROM node:22-slim AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-slim AS runner

WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

CMD ["node", "dist/index.js"]

10. 環境変数一覧

10.1 フロントエンド (.env)

bash
# API エンドポイント
VITE_API_ROOT_URL=https://your-api-xxxxx-an.a.run.app

10.2 バックエンド (.env)

bash
# Supabase
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJI...
SUPABASE_DB_URL=postgresql://postgres:password@db.xxxxx.supabase.co:5432/postgres

# Cloudflare Access
CF_ACCESS_TEAM_DOMAIN=yourcompany.cloudflareaccess.com
CF_ACCESS_AUD=xxxxx...

# Server
PORT=3000
NODE_ENV=development

11. デプロイ手順

11.1 初回デプロイ

  1. GitHub Secretsを設定(上記一覧を参照)

  2. バックエンドを先にデプロイ(Cloud Run URLを取得)

    bash
    cd api
    gcloud run deploy agent-management-api \
      --source . \
      --region asia-northeast1 \
      --allow-unauthenticated \
      --min-instances 1
    # 出力されるURLをメモ → VITE_API_ROOT_URLに設定
  3. フロントエンドをデプロイ

    bash
    git tag v0.1.0
    git push origin v0.1.0

11.2 通常デプロイ

bash
# フロントエンド
git tag v1.0.0
git push origin v1.0.0

# バックエンド
git tag api-v1.0.0
git push origin api-v1.0.0

11.3 タグ命名規則

対象タグ形式
フロントエンドv*v1.0.0, v1.0.1
バックエンドapi-v*api-v1.0.0, api-v1.0.1

11.4 ロールバック

  • Cloudflare Pages: ダッシュボードから過去のデプロイを選択して再公開
  • Cloud Run: gcloud run services update-traffic で過去リビジョンに切り替え

参考リンク