Docs / Use
Preview access
Lock previews down with basic auth, an IP allowlist, or a code-friendly bypass token.
Previews are public by default. For most teams, a wildcard subdomain is “obscure enough” — but for repos with sensitive data or customer demos, you’ll want a gate. Galley has three modes plus a side door for code callers.
Configure all of this at Project settings → Preview access. Changes apply to the next deploy; existing previews keep their old gate.
Modes
Public
No gate. Anyone with the URL can reach the preview. Fine for OSS projects, demos behind a corporate VPN, or anything you’d put on a status page.
Basic auth
A single username + password, prompted in the browser. Stored as a bcrypt hash; nothing on the server can read the plaintext after you set it.
Username: preview
Password: <8+ chars>
The browser remembers credentials per host, so once a reviewer authenticates, every subsequent preview on the same wildcard reuses the cached creds. Rotation: set a new password — the old one stops working immediately on next deploy.
Limitations:
- One credential pair per project. Not per-reviewer. Treat it as a shared secret for the team.
- HTTP only. It’s an ingress-proxy middleware; it doesn’t apply to TCP-only services.
- Awkward for code.
fetch()withAuthorization: Basicworks but ergonomically painful. Use the bypass token (below) for CI / scripts / e2e runners.
IP allowlist
Requests from outside the listed CIDRs get a 403 before the preview sees them.
10.0.0.0/8
203.0.113.42
2001:db8::/32
Bare IPs are accepted — 203.0.113.42 becomes 203.0.113.42/32. CIDRs validated server-side; an invalid entry rejects the save with a clear error.
Use cases:
- Office network only. Allowlist your office’s egress IP.
- VPN-gated. Allowlist the VPN’s egress.
- Combine with public DNS. Public name resolution + IP allowlist gives you a “VPN-required” experience without internal DNS plumbing.
Limitations:
- Source IP must be the real client. If you’re behind a CDN that doesn’t forward
X-Forwarded-For, the proxy will see the CDN’s egress, not the user. - IPv6 works, but make sure your DNS publishes AAAA only when the host’s IPv6 stack is reachable.
Bypass token
Available alongside any of the modes above. A long random token that, when sent as a header (X-Galley-Bypass: <token>) or query param (?_galley_bypass=<token>), grants access without the regular gate.
Why it exists: basic auth is fine for humans but lousy for code. A fetch() with a bearer token is normal; a fetch() with a basic auth dialog is not. The bypass token is the code-friendly side door.
Generate / rotate
Project settings → Preview access → Bypass token → Generate (or Rotate if one’s already configured). The token shows once in a modal — copy it now, you won’t see it again. Lose it, rotate.
The plaintext is encrypted under the master key in the database, decrypted only at deploy time so the agent can render it into the proxy’s match rule.
Use it
From any HTTP client:
curl -H "X-Galley-Bypass: $GALLEY_BYPASS_TOKEN" \
https://api.pr-42-myrepo.preview.yourco.dev/healthz
In CI:
# .github/workflows/e2e.yml
- name: Wait for preview, then test
env:
BYPASS: ${{ secrets.GALLEY_BYPASS_TOKEN }}
URL: ${{ steps.preview.outputs.url }}
run: |
curl --fail -H "X-Galley-Bypass: $BYPASS" "$URL/healthz"
pnpm playwright test --base-url="$URL" \
--header "X-Galley-Bypass=$BYPASS"
In the preview’s own frontend calling its own backend, you don’t need the bypass — they’re on the same wildcard, so the basic-auth cookie (if any) covers both. The bypass token is for off-preview callers.
How it composes
The proxy installs two routers per service when a bypass token is set:
| Priority | Match | Middleware |
|---|---|---|
| 100 (high) | Host(...) && X-Galley-Bypass: <token> | None |
| 1 (default) | Host(...) | basic auth or IP allowlist as configured |
The bypass router wins when the header matches; everyone else falls through to the gated router. So humans see the basic auth dialog, code sends the header. Same URL, different paths in.
When to revoke
- Token suspected leaked: Rotate (one click — old token stops working immediately).
- Project moving to public access: Revoke (also one click — clears without generating a new value).
Rotation is fine to do regularly. Treat it like any long-lived bearer token: rotate on suspicion, rotate on personnel change, rotate on a quarterly cadence if you’re being thorough.
What’s not covered
- OAuth / SSO in front of previews — listed in the dashboard as “coming soon”; not in v1. Work around with IP allowlist + your existing SSO at the network edge.
- Per-user revocation — basic-auth credentials are shared, the bypass token is shared. Per-user is on the roadmap.
- TCP-level gates — gates apply to HTTP routes (web, api). Database/cache/queue services aren’t publicly exposed at all, so there’s nothing to gate at the moment.