Secrets & Variables Matrix¶
Every GitHub Actions secret, GitHub variable, and server-side .env value that the app-name pipeline touches — split by where it must live. The guiding rule: deployment plumbing lives in GitHub; runtime application secrets live only on the server.
Single source of truth
Each value is defined in exactly one place. Frontend (VITE_*) and runtime secrets are defined once in the server .env and injected into the container's config.js at startup — the image itself is environment-agnostic. GitHub only holds what the pipeline needs to build, scan, and reach the server. See Secrets & Environment.
GitHub Secrets¶
Encrypted values in Settings → Secrets and variables → Actions. Referenced as ${{ secrets.NAME }}.
| Name | Scope | Purpose | Notes |
|---|---|---|---|
GITHUB_TOKEN |
repo (auto) | Auto-provided token. Authenticates docker login ghcr.io to push images (build) and pull them on the server (deploy). Also used by the SonarCloud scan. |
Injected, never set by hand. Workflow grants packages: write, contents: read, id-token: write. |
SONAR_TOKEN |
repo | Authenticates the SonarCloud quality-gate scan and the gate-status API check. | Only consumed when vars.SONAR_ENABLED == 'true'. |
DEPLOY_HOST |
repo | Hostname/IP of the test server, reached over the Cloudflare Tunnel. | Workflow falls back to vars.DEPLOY_HOST if the secret is unset (secrets.DEPLOY_HOST || vars.DEPLOY_HOST). |
PROD_DEPLOY_HOST |
repo | Hostname/IP of the production server. | Falls back to vars.PROD_DEPLOY_HOST. |
SSH_KEY |
repo | Private SSH deploy key used to reach the servers. Written to ~/.ssh/id_rsa in the runner. |
Falls back to vars.SSH_KEY. Must include full BEGIN/END lines. |
PROD_SSH_KEY |
repo | Optional production-specific SSH key. | Resolution order: secrets.PROD_SSH_KEY || secrets.SSH_KEY || vars.SSH_KEY. Omit to reuse SSH_KEY. |
CF_ACCESS_CLIENT_ID |
repo | Cloudflare Access service-token ID. Feeds cloudflared access ssh as the SSH ProxyCommand. |
Shared across test and production. |
CF_ACCESS_CLIENT_SECRET |
repo | Cloudflare Access service-token secret. | Shared across test and production. See Cloudflare Tunnel. |
Secret-or-variable fallback pattern
Hosts and keys use secrets.X || vars.X so non-sensitive deployments can store the host as a plain variable while sensitive setups keep it as a secret. Pick one per value and stay consistent.
Optional / situational secrets¶
| Name | Scope | Purpose | Notes |
|---|---|---|---|
SKILLS_READ_TOKEN |
repo | Read token for pulling shared internal skills/packages during CI. | Only required if the build consumes a private skills source. |
CLOUDFLARE_API_TOKEN |
repo | Cloudflare API token for managing Pages deploys / DNS from CI. | Only needed if the frontend is published via Cloudflare Pages. |
CLOUDFLARE_ACCOUNT_ID |
repo | Cloudflare account ID paired with the API token. | Often kept as a var rather than a secret. |
GitHub Variables (vars)¶
Plain, non-secret values in the Variables tab. Referenced as ${{ vars.NAME }}.
| Name | Scope | Purpose | Notes |
|---|---|---|---|
SONAR_ENABLED |
repo | Feature flag — gates the entire SonarCloud quality-gate job (if: vars.SONAR_ENABLED == 'true'). |
Set to true to enforce the gate; leave unset/false to skip it (the gate then resolves as skipped, which still allows deploy). |
CF_PAGES_PROJECT |
repo | Cloudflare Pages project name, if the frontend is deployed to Pages. | Pairs with CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID. |
DEPLOY_HOST |
repo / environment | Fallback test host when not stored as a secret. | Used via secrets.DEPLOY_HOST || vars.DEPLOY_HOST. |
PROD_DEPLOY_HOST |
repo / environment | Fallback production host. | Used via secrets.PROD_DEPLOY_HOST || vars.PROD_DEPLOY_HOST. |
SSH_KEY |
repo | Fallback SSH key location when not stored as a secret. | Storing a private key as a plain variable is discouraged — prefer the secret. |
Server-side .env (never in GitHub)¶
These runtime application secrets live only in the server .env (/opt/app-name/.env for test, /opt/app-name-prod/.env for production). The deploy script aborts if .env is absent. They are deliberately not stored as GitHub secrets.
| Name | Scope | Purpose | Notes |
|---|---|---|---|
DATABASE_URL |
server .env |
App database connection (transaction pooler, port 6543). | Per-environment value; same key name everywhere. |
DATABASE_URL_DIRECT |
server .env |
Direct connection (port 5432) for migrations. | |
DB_SSL_MODE |
server .env |
Postgres SSL mode, e.g. require. |
|
VITE_SUPABASE_URL |
server .env |
Supabase URL injected into config.js at container start. |
Public, but still managed on the server for single-source consistency. |
VITE_SUPABASE_ANON_KEY |
server .env |
Supabase anon key. | Public; obeys RLS. |
SUPABASE_SERVICE_KEY |
server .env |
service_role key — bypasses RLS. Critical. |
Never prefix with VITE_. |
SENDGRID_API_KEY |
server .env |
SendGrid transactional email key. | |
SENDGRID_FROM_EMAIL |
server .env |
Verified sender address. | |
SESSION_SECRET |
server .env |
Session signing/encryption secret. | openssl rand -base64 32. |
VITE_API_URL / VITE_APP_URL |
server .env |
Public API and app URLs. | Must match the environment's domain. |
Do not promote runtime secrets into GitHub
Putting DATABASE_URL, SUPABASE_SERVICE_KEY, SESSION_SECRET, or any API key into GitHub Secrets breaks the single-source-of-truth model and widens the blast radius of a leak. The pipeline is designed to need none of them — it only injects IMAGE_TAG, ENV, REGISTRY_OWNER, GITHUB_TOKEN, and GITHUB_ACTOR via the transient .env.deploy.
GitHub Environments¶
Defined under Settings → Environments. Names must match the workflow environment: keys exactly.
| Environment | Used by | Trigger | Protection rules |
|---|---|---|---|
test |
deploy.yml → deploy-test job |
Automatic on push to main (also workflow_dispatch). |
None required. Deploy runs only after the quality-gate, E2E-results, and security-audit jobs succeed (or the Sonar gate is skipped). |
production |
deploy-production.yml → deploy-production job |
Push of a v* tag or manual workflow_dispatch. |
Recommended: required reviewers on the production environment so deploys pause for manual approval. Same gate jobs must pass first. |
Gates precede the environment
Both workflows make the deploy job needs: the gate jobs with an if: guard. The environment's own protection rules (e.g. required reviewers) apply on top of those gates, giving production a manual checkpoint.
Related references¶
- Secrets & Environment — full secrets strategy and the "first principles" model.
- Environment Variable Matrix — every variable and where it lives.
- Cloudflare Tunnel — how
CF_ACCESS_CLIENT_ID/CF_ACCESS_CLIENT_SECRETproxy the deploy SSH. - Pipeline Overview — where each secret is consumed in the build/deploy flow.