Secrets & Environment¶
How App-name manages configuration across the two environments (test and production) using a single source of truth: GitHub holds the canonical secrets and injects them at deploy time into the server .env and containers. The Docker images stay environment-agnostic. For the complete variable tables see the Secrets Matrix and the Env Var Matrix.
The single-source-of-truth strategy¶
The principle is that no environment-specific value is baked into a build:
- Single definition. Each variable is defined once, in GitHub, scoped to the right environment.
- Runtime injection. At deploy time the workflow writes the server
.env, and the frontend container generates itsconfig.jsat container startup from the environment it is given. - Build agnosticism. The Docker image is built once and runs anywhere; it carries no
<your-domain>- ortest.<your-domain>-specific configuration.
flowchart LR
GH[GitHub Secrets<br/>canonical] -->|deploy workflow| ENV[Server .env]
ENV --> BE[Backend container]
ENV --> FE[Frontend container<br/>generates config.js at startup]
GitHub secret hierarchy¶
| Type | Level | Holds |
|---|---|---|
| Environment secrets | production / test |
URLs, database strings, and API keys specific to that environment |
| Repository secrets | Global | SSH keys, deploy hosts, and cross-environment tokens (e.g. GOOGLE_SERVICE_ACCOUNT_JSON) |
Required variables reference¶
The essentials are below. The full per-environment breakdown lives in the Secrets Matrix and Env Var Matrix.
Service keys¶
| Variable | Description | Security rule |
|---|---|---|
VITE_SUPABASE_URL |
Supabase project URL | Public in the frontend |
VITE_SUPABASE_ANON_KEY |
Anonymous key | Public; obeys RLS |
SUPABASE_SERVICE_KEY |
service_role key | Critical. Never prefix with VITE_; bypasses RLS |
Database connections¶
| Variable | Connection | Port | Use |
|---|---|---|---|
DATABASE_URL |
Transaction pooler | 6543 | Standard app operations |
DATABASE_URL_DIRECT |
Direct | 5432 | Migrations and heavy batch tasks |
Application & external APIs¶
| Variable | Description | Example / recommendation |
|---|---|---|
VITE_API_URL |
Backend server URL | https://<your-domain> |
VITE_APP_URL |
Self-address (for email links) | https://<your-domain> |
SESSION_SECRET |
Auth encryption key | openssl rand -base64 32 |
GOOGLE_SERVICE_ACCOUNT_JSON |
Google Gemini key | Full text of the service-account .json |
The service key is server-only
Anything prefixed VITE_ is bundled into the browser. If you ever prefix SUPABASE_SERVICE_KEY with VITE_, it becomes globally visible and anyone can bypass your Row Level Security. Never do this.
Local development .env¶
Locally you use a single .env at the repo root. To develop against the local Supabase stack, point these at the local ports instead — see Supabase (Local).
# .env (local)
NODE_ENV=development
# Supabase
VITE_SUPABASE_URL=https://xxxxx.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJI...
SUPABASE_SERVICE_KEY=eyJhbGciOiJI...
# Database
DATABASE_URL=postgresql://postgres.xxx:6543/postgres
# AI (local uses a file path)
GOOGLE_APPLICATION_CREDENTIALS=./config/key.json
# App
VITE_API_URL=http://localhost:5001
VITE_APP_URL=http://localhost:5173
SESSION_SECRET=local-dev-secret
GitHub secret configuration¶
Configure under Settings → Secrets and Variables → Actions.
Environment secrets¶
Create the production and test environments and add the per-environment values (Supabase URL/keys, DATABASE_URL, VITE_API_URL, VITE_APP_URL). These are the values the deploy workflow injects into the matching server's .env.
Repository secrets¶
Add these once for the whole project:
| Secret | Purpose |
|---|---|
DEPLOY_HOST |
IP of the VPS |
SSH_KEY |
Private deploy key |
GOOGLE_SERVICE_ACCOUNT_JSON |
Optional: sync the Gemini service-account file |
CF_ACCESS_CLIENT_ID |
Cloudflare Tunnel client ID |
CF_ACCESS_CLIENT_SECRET |
Cloudflare Tunnel client secret |
Security verification¶
Verify that fallbacks resolve before relying on a deploy:
cd backend
npm run test:config
Think of the two keys as hotel clearances:
| Key | Clearance | Access |
|---|---|---|
| Anon key | Guest card | Only the user's own rows (RLS) |
| Service key | Master key | Every row and table (bypasses RLS) |
Troubleshooting¶
| Issue | Fix |
|---|---|
| "Network Error" in the browser | Confirm VITE_API_URL is the HTTPS domain of the server |
| Broken links in emails | Confirm VITE_APP_URL matches the public website domain |
| AI models not responding | Ensure GOOGLE_SERVICE_ACCOUNT_JSON holds the full, valid JSON |