GitHub Configuration Guide
This repo uses GitHub Actions for CI/CD with GitHub Environments for environment-specific configuration.
1. GitHub Environments Strategy
We use GitHub Environments (production and sandbox) instead of repository-level prefixed secrets/variables for cleaner, more secure configuration management.
Benefits
- Scoped Access: Secrets/variables are isolated per environment
- Protection Rules: Can require approvals for production deployments
- Cleaner Code: Workflows automatically inherit environment context
- No Prefixes: Simple variable names like
SUPABASE_URLinstead ofPROD_SUPABASE_URL
Architecture
Repository
├── Environments
│ ├── production (main branch)
│ │ ├── Variables: SUPABASE_URL, NEXT_PUBLIC_SITE_URL, etc.
│ │ └── Secrets: SUPABASE_ANON_KEY, KV_NAMESPACE_ID, etc.
│ └── sandbox (sandbox branch)
│ ├── Variables: SUPABASE_URL, NEXT_PUBLIC_SITE_URL, etc.
│ └── Secrets: SUPABASE_ANON_KEY, KV_NAMESPACE_ID, etc.
├── Repository Variables
│ └── CLOUDFLARE_ACCOUNT_ID (shared across all environments)
└── Repository Secrets
├── CLOUDFLARE_API_TOKEN (shared across all workflows)
├── SUPABASE_ACCESS_TOKEN (shared for Supabase CLI)
└── SSH_PRIVATE_KEY (shared for infrastructure deployments)
2. Required Environment Variables
Go to: Repository → Settings → Environments → [production|sandbox] → Variables
Production Environment
| Variable Name | Value Example |
|---|---|
CLOUDFLARE_ACCOUNT_ID | fabfd9b8a8027ca77f54ccc830eceb54 |
ALLOWED_ORIGINS | https://www.freeaihashtags.com |
NEXT_PUBLIC_API_BASE_PATH | https://api.freeaihashtags.com/v1 |
NEXT_PUBLIC_DOCS_URL | https://docs.freeaihashtags.com/api |
NEXT_PUBLIC_SITE_URL | https://www.freeaihashtags.com |
NEXT_PUBLIC_SUPABASE_URL | https://ssvgamtfxzyopspdzgzv.supabase.co |
PADDLE_ENV | production |
SUPABASE_PROJECT_REF | ssvgamtfxzyopspdzgzv |
SUPABASE_URL | https://ssvgamtfxzyopspdzgzv.supabase.co |
PADDLE_PRO_MONTHLY_PRICE_ID | pri_01xxxxx (Production Paddle ID) |
PADDLE_PRO_YEARLY_PRICE_ID | pri_01xxxxx (Production Paddle ID) |
PADDLE_AGENCY_MONTHLY_PRICE_ID | pri_01xxxxx (Production Paddle ID) |
PADDLE_AGENCY_YEARLY_PRICE_ID | pri_01xxxxx (Production Paddle ID) |
PADDLE_PRO_MONTHLY_DISCOUNT_ID | dsc_01xxxxx (optional) |
PADDLE_PRO_YEARLY_DISCOUNT_ID | dsc_01xxxxx (optional) |
PADDLE_AGENCY_MONTHLY_DISCOUNT_ID | dsc_01xxxxx (optional) |
PADDLE_AGENCY_YEARLY_DISCOUNT_ID | dsc_01xxxxx (optional) |
Sandbox Environment
| Variable Name | Value Example |
|---|---|
CLOUDFLARE_ACCOUNT_ID | fabfd9b8a8027ca77f54ccc830eceb54 |
ALLOWED_ORIGINS | http://localhost:3000,https://faiht-frontend-sandbox.adeel-zain-work.workers.dev |
NEXT_PUBLIC_API_BASE_PATH | https://faiht-api.adeel-zain-work.workers.dev/v1 |
NEXT_PUBLIC_DOCS_URL | https://sandbox.faiht2-public-docs.pages.dev/api |
NEXT_PUBLIC_SITE_URL | https://faiht-frontend-sandbox.adeel-zain-work.workers.dev |
NEXT_PUBLIC_SUPABASE_URL | https://wyjzdyfxczuixkukurdg.supabase.co |
PADDLE_ENV | sandbox |
SUPABASE_PROJECT_REF | wyjzdyfxczuixkukurdg |
SUPABASE_URL | https://wyjzdyfxczuixkukurdg.supabase.co |
PADDLE_PRO_MONTHLY_PRICE_ID | pri_01kbxdgfay3hbjvp3yqg6rndje |
PADDLE_PRO_YEARLY_PRICE_ID | pri_01kbxdghs02htdy7cswy47d9vc |
PADDLE_AGENCY_MONTHLY_PRICE_ID | pri_01kchmne1mtq81gbatcvh1216v |
PADDLE_AGENCY_YEARLY_PRICE_ID | pri_01kchmngft2arbpaqfc3sa9tgy |
PADDLE_PRO_MONTHLY_DISCOUNT_ID | dsc_01kbxe2cneh4qc78v3vs77k5bq |
PADDLE_PRO_YEARLY_DISCOUNT_ID | FIXTHIS (needs actual value) |
PADDLE_AGENCY_MONTHLY_DISCOUNT_ID | dsc_01kbxe2cneh4qc78v3vs77k5bq |
PADDLE_AGENCY_YEARLY_DISCOUNT_ID | FIXTHIS (needs actual value) |
Repository-Level Variables
| Variable Name | Value |
|---|---|
CLOUDFLARE_ACCOUNT_ID | fabfd9b8a8027ca77f54ccc830eceb54 |
3. Required Environment Secrets
Go to: Repository → Settings → Environments → [production|sandbox] → Secrets
Production Environment
| Secret Name | Description |
|---|---|
GOOGLE_CLIENT_ID | Google OAuth client ID for production |
KV_NAMESPACE_ID | Cloudflare KV namespace for production |
PADDLE_CLIENT_TOKEN | Paddle client token for production |
SUPABASE_ANON_KEY | Supabase anon key for production |
SUPABASE_SERVICE_ROLE_KEY | Supabase service role key for production |
TIKTOK_CLIENT_KEY | TikTok OAuth client key for production |
TIKTOK_CLIENT_SECRET | TikTok OAuth secret for production |
Sandbox Environment
| Secret Name | Description |
|---|---|
GOOGLE_CLIENT_ID | Google OAuth client ID for sandbox |
KV_NAMESPACE_ID | Cloudflare KV namespace for sandbox |
PADDLE_CLIENT_TOKEN | Paddle client token for sandbox |
SUPABASE_ANON_KEY | Supabase anon key for sandbox |
SUPABASE_SERVICE_ROLE_KEY | Supabase service role key for sandbox |
TIKTOK_CLIENT_KEY | TikTok OAuth client key for sandbox |
TIKTOK_CLIENT_SECRET | TikTok OAuth secret for sandbox |
Repository-Level Secrets (Shared)
| Secret Name | Description |
|---|---|
CLOUDFLARE_API_TOKEN | API Token for Cloudflare deployments |
SUPABASE_ACCESS_TOKEN | Supabase CLI access token |
SSH_PRIVATE_KEY | SSH key for infrastructure deploys |
4. Bulk Setup (CLI)
Prerequisites
You need the GitHub CLI with proper permissions:
gh auth login
# Select: GitHub.com → HTTPS → Authenticate with browser
# Token must have 'repo' and 'admin:org' (Environments) permissions
Upload Secrets from File
- Create environment-specific secret files:
# Production
cat > .github.production.secrets << EOF
GOOGLE_CLIENT_ID=your-prod-google-client-id
KV_NAMESPACE_ID=your-prod-kv-namespace-id
PADDLE_CLIENT_TOKEN=your-prod-paddle-token
SUPABASE_ANON_KEY=your-prod-supabase-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-prod-supabase-service-role-key
TIKTOK_CLIENT_KEY=your-prod-tiktok-client-key
TIKTOK_CLIENT_SECRET=your-prod-tiktok-client-secret
EOF
# Sandbox
cat > .github.sandbox.secrets << EOF
GOOGLE_CLIENT_ID=your-sandbox-google-client-id
KV_NAMESPACE_ID=your-sandbox-kv-namespace-id
PADDLE_CLIENT_TOKEN=your-sandbox-paddle-token
SUPABASE_ANON_KEY=your-sandbox-supabase-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-sandbox-supabase-service-role-key
TIKTOK_CLIENT_KEY=your-sandbox-tiktok-client-key
TIKTOK_CLIENT_SECRET=your-sandbox-tiktok-client-secret
EOF
- Upload via helper script:
# Upload to production
while IFS='=' read -r key value; do
[[ -z "$key" || "$key" =~ ^# ]] && continue
echo "$value" | gh secret set "$key" --env production
done < .github.production.secrets
# Upload to sandbox
while IFS='=' read -r key value; do
[[ -z "$key" || "$key" =~ ^# ]] && continue
echo "$value" | gh secret set "$key" --env sandbox
done < .github.sandbox.secrets
Important: Ensure .github.production.secrets and .github.sandbox.secrets are in .gitignore!
5. KV Namespace Setup
Cloudflare KV namespaces are required for the API Gateway (caching and rate limiting).
cd apps/api
# Create sandbox namespace
wrangler kv:namespace create "FAIHT_CACHE" --env sandbox
# Copy the ID → Add to sandbox environment's KV_NAMESPACE_ID secret
# Create production namespace
wrangler kv:namespace create "FAIHT_CACHE"
# Copy the ID → Add to production environment's KV_NAMESPACE_ID secret
Detailed Guide: Cloudflare KV Setup
6. Verification
Check Variables
# Repository-level
gh variable list
# Environment-level
gh api repos/:owner/:repo/environments/production/variables --jq '.variables[].name'
gh api repos/:owner/:repo/environments/sandbox/variables --jq '.variables[].name'
Check Secrets
# Repository-level
gh secret list
# Environment-level
gh api repos/:owner/:repo/environments/production/secrets --jq '.secrets[].name'
gh api repos/:owner/:repo/environments/sandbox/secrets --jq '.secrets[].name'
Check Environments
gh api repos/:owner/:repo/environments --jq '.environments[].name'
Expected output:
production
sandbox
7. Related Documentation
- Cloudflare Setup Guide - Complete Cloudflare configuration
- KV Namespace Setup - Detailed KV setup instructions
- Deployment Guide - How to deploy to sandbox and production