Skip to content
Galley

Docs / Install

DNS and TLS

Wildcard DNS records, ACME DNS-01 wildcard certificates, BYO certs.

Every preview gets a subdomain. The wildcard cert path lets a fresh PR land on HTTPS within seconds of the build finishing — no per-PR cert issuance, no rate-limit risk.

DNS records you need

Two A/AAAA records pointing at the host that runs the ingress proxy:

RecordPurpose
galley.yourco.devThe dashboard / API. Matches GALLEY_PUBLIC_HOST.
*.preview.yourco.devEvery preview lands under this wildcard. Matches GALLEY_PREVIEW_DOMAIN.

Single host: both records point at the same IP. Split topology: both records point at whichever host runs the ingress proxy (usually the control-plane host).

If your zone provider doesn’t allow wildcards directly, use a DNS provider that does (Route 53, Cloudflare, deSEC, etc.) or set the wildcard via API.

Galley uses the DNS-01 challenge, so wildcard issuance works without exposing any service publicly during the handshake. You give it credentials for your DNS provider, it writes a TXT record, gets a wildcard cert, and removes the TXT.

Set GALLEY_LE_DNS_PROVIDER plus the provider’s expected env vars:

ProviderGALLEY_LE_DNS_PROVIDERRequired env
CloudflarecloudflareCLOUDFLARE_DNS_API_TOKEN (scoped to Zone:Read + DNS:Edit on the zone)
Route 53route53AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
Google Cloud DNSgcloudGCE_PROJECT, GCE_SERVICE_ACCOUNT_FILE (mounted into the proxy container)
Azure DNSazurednsAZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID, AZURE_RESOURCE_GROUP
DigitalOceandigitaloceanDO_AUTH_TOKEN
HetznerhetznerHETZNER_API_KEY
deSECdesecDESEC_TOKEN
LinodelinodeLINODE_TOKEN
GandigandiGANDI_API_KEY

Other providers are supported — the full list is documented at the ACME library used by the proxy. Set the matching env vars in .env; the proxy reads them at boot.

Cloudflare token scopes

For the API token, the minimum scopes are:

  • Zone: Read (so the lookup of your zone works)
  • DNS: Edit (to write/delete the _acme-challenge TXT)

Restrict the token to the specific zone — don’t issue an account-wide token unless you have a reason.

Bring-your-own cert (BYO)

If you can’t use ACME (air-gapped, internal-only certs from your PKI, etc.), drop the cert + key into the proxy’s TLS volume and tell it to use them:

# In .env
GALLEY_TLS_MODE=byo

# Mount the cert + key
- ./tls/cert.pem:/etc/ssl/cert.pem:ro
- ./tls/key.pem:/etc/ssl/key.pem:ro

The cert needs to be a wildcard for *.preview.yourco.dev (and ideally also cover the dashboard host). You’re responsible for renewal — Galley won’t rotate a BYO cert on its own.

Internal-only previews

If previews shouldn’t be publicly reachable at all (corporate network, VPN-only review), keep DNS internal:

  • Wildcard pointing at an internal IP, resolvable only from your VPN / private DNS.
  • The proxy exposes :80/:443 on the internal interface.
  • Pair with preview-access set to ip_allowlist for a second layer.

This is the standard config for teams where reviewers connect via Tailscale, ZeroTier, or a corporate VPN. ACME DNS-01 still works because the challenge happens through your DNS provider’s API, not by exposing a port.

Troubleshooting

  • Cert still pending after several minutes: check docker logs <proxy container> for ACME errors. Most failures are credential scope (token can’t write the TXT) or a registrar that’s slow to propagate.
  • dns_problem: the challenge TXT was written but Let’s Encrypt couldn’t read it back yet. Some registrars take 60–120s to propagate; the resolver retries automatically.
  • Wrong cert showing: make sure both the dashboard host and the wildcard resolve to the same proxy. A mismatched DNS view (the proxy gets one cert request but a different one is in cache) usually means a stale acme.json. Stop the proxy, delete the volume, restart.