Skip to content
Galley

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() with Authorization: Basic works 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:

PriorityMatchMiddleware
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.