Environment & Secrets Management
Secrets Overview
Types of Secrets
┌─────────────────────────────────────────────────────────────┐
│ SECRETS IN LIGHT STACK │
├─────────────────────────────────────────────────────────────┤
│ │
│ Supabase: │
│ ├── SUPABASE_URL (public - can expose) │
│ ├── SUPABASE_ANON_KEY (public - can expose) │
│ └── SUPABASE_SERVICE_KEY (SECRET - never expose!) │
│ │
│ Cloudflare: │
│ ├── CLOUDFLARE_ACCOUNT_ID (semi-secret) │
│ └── CLOUDFLARE_API_TOKEN (SECRET) │
│ │
│ Third-party: │
│ ├── STRIPE_SECRET_KEY (SECRET) │
│ ├── SENDGRID_API_KEY (SECRET) │
│ └── Other API keys (SECRET) │
│ │
└─────────────────────────────────────────────────────────────┘
GitHub Secrets
Repository Secrets
Repository → Settings → Secrets and variables → Actions
Types:
├── Repository secrets (all workflows)
├── Environment secrets (specific environment)
└── Organization secrets (shared across repos)
Add Secret
1. Go to Settings → Secrets → Actions
2. Click "New repository secret"
3. Name: SUPABASE_SERVICE_KEY
4. Value: (paste secret value)
5. Add secret
Note: Once added, value cannot be viewed again
GitHub Variables
Repository Variables
Repository → Settings → Secrets and variables → Actions → Variables
Use for non-sensitive configuration:
├── SUPABASE_PROJECT_REF
├── CLOUDFLARE_ACCOUNT_ID
├── NODE_VERSION
└── Other config values
Secrets vs Variables
| Type |
Use For |
Access |
| Secrets |
API keys, passwords |
${{ secrets.NAME }} |
| Variables |
Config, IDs |
${{ vars.NAME }} |
Environment-specific Secrets
Setup
# Workflow with environment secrets
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging # Uses staging secrets
steps:
- name: Deploy
env:
SUPABASE_URL: ${{ vars.SUPABASE_URL }}
SUPABASE_KEY: ${{ secrets.SUPABASE_SERVICE_KEY }}
deploy-production:
runs-on: ubuntu-latest
environment: production # Uses production secrets
steps:
- name: Deploy
env:
SUPABASE_URL: ${{ vars.SUPABASE_URL }}
SUPABASE_KEY: ${{ secrets.SUPABASE_SERVICE_KEY }}
Environment Configuration
staging environment:
├── Secrets:
│ ├── SUPABASE_SERVICE_KEY=staging-key
│ └── CLOUDFLARE_API_TOKEN=token
├── Variables:
│ ├── SUPABASE_URL=https://staging.supabase.co
│ └── SUPABASE_PROJECT_REF=staging-ref
└── Protection: None (auto-deploy)
production environment:
├── Secrets:
│ ├── SUPABASE_SERVICE_KEY=production-key
│ └── CLOUDFLARE_API_TOKEN=token
├── Variables:
│ ├── SUPABASE_URL=https://production.supabase.co
│ └── SUPABASE_PROJECT_REF=production-ref
└── Protection:
├── Required reviewers: 1
└── Deployment branches: main only
Cloudflare Secrets
Worker Secrets
# Set secret via CLI
wrangler secret put MY_SECRET
# Enter value when prompted
# Set for specific environment
wrangler secret put MY_SECRET --env staging
# List secrets (names only)
wrangler secret list
# Delete secret
wrangler secret delete MY_SECRET
In CI/CD
- name: Set Cloudflare Secrets
run: |
echo "${{ secrets.STRIPE_KEY }}" | wrangler secret put STRIPE_KEY
echo "${{ secrets.SENDGRID_KEY }}" | wrangler secret put SENDGRID_KEY
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Environment Variables in Next.js
Build-time vs Runtime
// NEXT_PUBLIC_ = Available in browser (build-time embedded)
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=public-key
// Without NEXT_PUBLIC_ = Server-side only (never exposed to browser)
SUPABASE_SERVICE_KEY=secret-key
STRIPE_SECRET_KEY=sk_live_xxx
Accessing Variables
// Client-side (browser)
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
// Server-side only
const serviceKey = process.env.SUPABASE_SERVICE_KEY;
// This is undefined in browser
Build-time Injection
GitHub Actions
- name: Build Next.js
run: npm run build
env:
# Public variables (embedded in build)
NEXT_PUBLIC_SUPABASE_URL: ${{ vars.SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
NEXT_PUBLIC_APP_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
Cloudflare Runtime
# wrangler.toml - Runtime variables
[vars]
SUPABASE_URL = "https://xxx.supabase.co"
APP_ENV = "production"
# Secrets set via CLI (not in toml)
# SUPABASE_SERVICE_KEY
# STRIPE_SECRET_KEY
Security Best Practices
Do's and Don'ts
✅ DO:
- Use environment-specific secrets
- Rotate secrets regularly
- Use minimal permissions for tokens
- Audit secret access
- Use NEXT_PUBLIC_ prefix correctly
❌ DON'T:
- Commit secrets to git
- Log secret values
- Use production secrets in staging
- Share secrets in chat/email
- Use same secret across all environments
Secret Rotation
# Rotate Supabase service key
# Dashboard → Project Settings → API → Regenerate service key
# Update in GitHub
# Settings → Secrets → Update SUPABASE_SERVICE_KEY
# Update in Cloudflare
wrangler secret put SUPABASE_SERVICE_KEY
Local Development
.env.local
# .env.local (gitignored)
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=local-anon-key
SUPABASE_SERVICE_KEY=local-service-key
.gitignore
# Environment files
.env
.env.local
.env.*.local
# Cloudflare
.dev.vars
# Never commit
*.pem
*.key
credentials.json
Tổng kết
Secrets Checklist
- [ ] Separate secrets per environment
- [ ] Never commit secrets to git
- [ ] Use NEXT_PUBLIC_ correctly
- [ ] Set Cloudflare secrets via CLI
- [ ] Configure environment protection
- [ ] Document secret rotation process
Quick Reference
| Platform |
Secret Storage |
| GitHub |
Settings → Secrets |
| Cloudflare |
wrangler secret put |
| Local |
.env.local, .dev.vars |
Q&A
- Secret rotation policy?
- Có shared secrets giữa projects?
- Access audit cần không?