Skip to content

CI/CD Deployment Pipeline

gondolier deploys two artifacts via separate pipelines:

  1. Workers: Go WASM binary via wrangler deploy
  2. Pages: Static site via wrangler pages deploy
  1. Validatego vet ./... && go test ./... && go build ./cmd/gondolier
  2. Push secretswrangler secret put <NAME> for each required secret
  3. Deploywrangler deploy

Worker secrets (tokens, keys, environment variables that are not plain env vars) must be set via wrangler secret put, not as environment variables in wrangler.toml. Plain env vars go in wrangler.toml under [vars] or as [env.<name>.vars]. Secrets go via CLI:

Terminal window
wrangler secret put TENANT_FORGE_TOKEN

The Wrangler CLI must be authenticated to the Cloudflare account with Workers access.

  1. Build the site (if applicable)
  2. Deploy — wrangler pages deploy ./site --project-name gondolier-site --branch production
  • Pages is upload-based, not branch-based. You must pass --branch production to deploy to the production site. There is no auto-deploy from a git push.
  • Always deploy from a worktree checked out at origin/main to avoid deploying stale local changes.
  • Workers Paid plan required for Containers / CloudChamber features. Free plans do not include these capabilities.
  • Token permissions: The Cloudflare API token used for Workers deployment must have:
    • Workers Scripts (Edit)
    • KV (Edit)
    • Containers / CloudChamber (Edit)
    • Account Settings (Read)
    • Workers Routes (Edit)
  • Binding-name shadowing: Never set environment variable names that match DO or KV binding names in wrangler.toml. When a binding exists (e.g., SESSION_STORE as a KV namespace), setting an env var with the same name causes the Worker to crash with error 1101 (binding resolution failure). Use distinct names.
  • Worker secrets vs env vars: Secrets that must be encrypted at rest in Cloudflare must be set via wrangler secret put, not as [vars] entries.
  • json() response helper CORS: The json() response helper must include CORS headers (Access-Control-Allow-Origin, etc.) in the actual response body, not just in preflight OPTIONS responses. Browsers block the response on non-preflight requests if CORS headers are missing from the actual response.
  • Module-scope functions must not reference request params without defining them locally. Module-level closures that capture request will not compile or will reference the wrong scope at runtime.
  • OIDC tokens require enable-openid-connect: true at the workflow level (not just repo-level setting).
  • The audience claim must be audience: openbao (no template braces).
  • OpenBao forgejo-actions role configuration:
    • Must include user_claim
    • bound_audiences is required
    • bound_claims must be valid JSON (not a string representation)
    • No bound_subject — omit it entirely or the role will not match
  • REST filters must be query params, not request bodies. Supabase PostgREST does not accept filter bodies on GET requests. Use ?column=eq.value syntax.
  • Actions env vars: In GitHub/Forgejo Actions, environment variable references must use ${{ env.X }} syntax, not $VAR. The shell-style $VAR is not interpolated in Actions YAML expressions.

The session-expiry redirect loop was caused by a redirect that fired before the session store had been initialized. The fix was to gate the redirect on store readiness and ensure the session validation middleware did not redirect during the auth flow’s own redirect.