Appearance
インフラストラクチャ構成ガイド
転職エージェント業務管理システム
目次
- アーキテクチャ概要
- 前提条件・事前準備
- Cloudflare Accessセットアップ
- Supabaseセットアップ
- GCPセットアップ
- Cloudflare Pagesセットアップ
- CI/CDパイプライン
- フロントエンド構築手順
- バックエンド構築手順
- 環境変数一覧
- デプロイ手順
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 + TypeScript | 19.x |
| ビルドツール | Vite | 6.x |
| スタイリング | Tailwind CSS + shadcn/ui | 4.x |
| データ取得 | TanStack React Query | 5.x |
| バックエンド | Hono | 4.x |
| ランタイム | Node.js | 22.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ダッシュボードにアクセス
- Cloudflareダッシュボード → Zero Trust を選択
- 初回は Team name を設定(例:
yourcompany) - これが
yourcompany.cloudflareaccess.comになる
3.2 Google IdPを追加
- Settings → Authentication → Login methods
- Add new → Google
- Google Cloud Consoleで OAuth 2.0 クライアントを作成:
- 承認済みリダイレクトURI:
https://yourcompany.cloudflareaccess.com/cdn-cgi/access/callback
- 承認済みリダイレクトURI:
- Client IDとClient Secretを入力
- Save
3.3 Applicationを作成(フロントエンド用)
- Access → Applications → Add an application
- Self-hosted を選択
- 設定:
| 項目 | 値 |
|---|---|
| Application name | Agent Management System |
| Session Duration | 24 hours |
| Application domain | your-app.yourdomain.com |
- Policies タブで Add a policy:
| 項目 | 値 |
|---|---|
| Policy name | Allow Company Domain |
| Action | Allow |
| Include | Emails ending in @yourcompany.co.jp |
- Save
3.4 取得すべき値
| 項目 | 用途 | 確認場所 |
|---|---|---|
| Team Domain | yourcompany.cloudflareaccess.com | Settings → General |
| Application Audience (AUD) | JWT検証用 | Applications → 対象App → Overview |
4. Supabaseセットアップ
4.1 プロジェクト作成
- Supabase にサインアップ
- New Project を作成
- リージョン: Northeast Asia (Tokyo) を選択
- Database Password を設定(控えておく)
4.2 テーブル作成
SQLエディタで以下を実行(別途提供のSQLファイルを使用)
4.3 Direct Connection設定
- Settings → Database
- Connection string → URI をコピー
- これが
SUPABASE_DB_URLになる
4.4 取得すべき値
| 項目 | 用途 | 確認場所 |
|---|---|---|
| Project URL | APIアクセス | 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-id5.2 必要なAPIを有効化
bash
gcloud services enable \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com \
iam.googleapis.com5.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 ID | gcloud config get-value project |
| Project Number | gcloud projects describe $(gcloud config get-value project) --format='value(projectNumber)' |
| Workload Identity Provider | projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/providers/github-provider |
| Service Account Email | github-actions-deployer@PROJECT_ID.iam.gserviceaccount.com |
6. Cloudflare Pagesセットアップ
6.1 Pagesプロジェクト作成
bash
wrangler login
wrangler pages project create agent-management-frontend6.2 カスタムドメイン設定
- Cloudflareダッシュボード → Pages → プロジェクト選択
- Custom domains → Set up a custom domain
- ドメインを入力(例:
app.yourdomain.com) - DNSレコードが自動設定される
6.3 Cloudflare AccessとPagesの連携
- Zero Trustダッシュボード → Access → Applications
- 作成したApplicationの Application domain がPagesのドメインと一致していることを確認
6.4 取得すべき値
| 項目 | 取得場所 |
|---|---|
| Account ID | ダッシュボード右サイドバー |
| API Token | My Profile → API Tokens → Create Token |
| Project Name | 作成したプロジェクト名 |
7. CI/CDパイプライン
7.1 GitHub Secrets設定一覧
| Secret名 | 説明 | 用途 |
|---|---|---|
| GCP | ||
GCP_PROJECT_ID | GCPプロジェクトID | API |
GCP_REGION | Cloud Runリージョン(例: asia-northeast1) | API |
GCP_SERVICE_NAME | Cloud Runサービス名 | API |
GCP_WORKLOAD_IDENTITY_PROVIDER | Workload Identity Provider | API |
GCP_SERVICE_ACCOUNT | サービスアカウントメール | API |
| Supabase | ||
SUPABASE_URL | SupabaseプロジェクトURL | API |
SUPABASE_SERVICE_ROLE_KEY | Supabaseサービスロールキー | API |
SUPABASE_DB_URL | Supabase Direct Connection URL | API |
| Cloudflare Access | ||
CF_ACCESS_TEAM_DOMAIN | Cloudflare Accessチームドメイン | API |
CF_ACCESS_AUD | Application Audience Tag | API |
| Cloudflare Pages | ||
CLOUDFLARE_API_TOKEN | Cloudflare APIトークン | Frontend |
CLOUDFLARE_ACCOUNT_ID | CloudflareアカウントID | Frontend |
CLOUDFLARE_PROJECT_NAME | Pagesプロジェクト名 | Frontend |
| API | ||
VITE_API_ROOT_URL | Cloud RunサービスURL | Frontend |
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 init8.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.example8.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 apiClient9. バックエンド構築手順
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 vitest9.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.example9.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.app10.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=development11. デプロイ手順
11.1 初回デプロイ
GitHub Secretsを設定(上記一覧を参照)
バックエンドを先にデプロイ(Cloud Run URLを取得)
bashcd api gcloud run deploy agent-management-api \ --source . \ --region asia-northeast1 \ --allow-unauthenticated \ --min-instances 1 # 出力されるURLをメモ → VITE_API_ROOT_URLに設定フロントエンドをデプロイ
bashgit 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.011.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で過去リビジョンに切り替え