05. Bind Domains and Environment Variables
This chapter is where your deployment starts looking like a real online product instead of a set of temporary project URLs.
0. Why bind domains?
Section titled “0. Why bind domains?”The system may run without binding custom domains. But:
- URLs will be ugly: default
*.workers.devor*.pages.devURLs are hard to remember and share. - Payment callbacks need public URLs: Stripe and Creem must reach your backend from the public internet.
- SEO metadata should not point at preview domains: sitemap, canonical tags, and robots should use your real production domain.
node3-pay-service must be bound first because it receives payment provider callbacks. Without a custom domain, Stripe and Creem webhooks are usually unreliable.
1. Bind custom domains
Section titled “1. Bind custom domains”Web (Astro 6)
Section titled “Web (Astro 6)”web currently deploys to a Worker, not Pages. Bind your custom domain in Cloudflare Dashboard -> Workers & Pages -> your web Worker -> Settings -> Domains & Routes.
Recommended workflow: Copy apps/web-astro to a new project and customize there. Do not modify apps/web-astro directly. Treat it as the official template.
If you copy apps/web-astro to create a new site
Section titled “If you copy apps/web-astro to create a new site”Use this order in the current system:
-
Create the tenant in Admin first
- Open Admin ->
Projects - Create a new project row with a unique
app_key - This registers the tenant in
t_project
- Open Admin ->
-
Copy
apps/web-astroto a new app directory- Example:
apps/my-brand
- Example:
-
Edit
apps/my-brand/zship.app.jsonappKey: must match the tenant you created in Admindomain: your production domainsiteUrl: your canonical production URLpagesProject: the deployment target name for this frontend app- Brand fields such as
siteName,brand.name, andbrand.logo
-
Keep runtime wiring aligned
zship.app.jsonownsappKey,domain, andsiteUrl- Env variables are for backend service URLs, secrets, and legacy/env-based overrides
-
Change
wrangler.toml- Update the
namefield - If you keep
name = "web"in the copied app, deployment may overwrite the originalwebWorker
- Update the
-
Bind the new custom domain
- Give the copied frontend its own domain
- Do not reuse the original
webdomain unless you intentionally want to replace it
-
Redeploy the copied frontend
- Treat it as its own frontend Worker and deploy it independently
Mental model:
- Admin creates the tenant
zship.app.jsonmaps a frontend app to that tenantwrangler.tomldecides which Worker receives the deployment
admin usually deploys to Pages. Bind the domain in Workers & Pages -> admin -> Custom domains.
Backend Workers
Section titled “Backend Workers”Priority: node3-pay-service. Bind it first so Stripe and Creem webhooks can reach your payment callback endpoint. Other Workers can follow.
CDN and R2 (CDN_PUBLIC_URL)
Section titled “CDN and R2 (CDN_PUBLIC_URL)”If you use node6-cdn-service for uploads, the Worker’s CDN_PUBLIC_URL must match the R2 bucket custom domain. Otherwise uploads may succeed while public URLs return 404. Full steps are in Troubleshooting.
2. How zship.app.json, site.ts, and env work together
Section titled “2. How zship.app.json, site.ts, and env work together”Frontend configuration is layered:
-
zship.app.json- Recommended source of truth for frontend app identity
- Manages
appKey,siteUrl,domain, brand metadata, and deployment target names
-
site.ts- Runtime adapter that reads
zship.app.jsonand turns it into typedsiteConfig - Regenerated by Dev Console only when scaffold/brand tooling needs to refresh the adapter
- Runtime adapter that reads
-
Environment variables
- Local:
.env - Production: Cloudflare Variables and Secrets
- Service URLs and secrets come from env; app identity and canonical URL come from the manifest unless a legacy app explicitly enables env-based tenant selection
- Local:
So the practical rule today is:
- Edit
zship.app.json - Let Dev Console keep robots and generated adapters aligned when you use its tooling
- Configure env for backend URLs and secrets only
3. Canonical URL vs latest deployed URL
Section titled “3. Canonical URL vs latest deployed URL”Do not treat these as the same thing:
- Canonical URL: your real production site URL, for example
https://my-brand.com - Latest deployed URL: the latest preview or temporary URL discovered during deploy, for example
https://xxxx.my-brand.pages.dev
Current rule:
siteUrlinzship.app.jsonis the canonical URL- the latest deployed
pages.devURL is only a deploy artifact and should not replace sitemap, robots, or canonical metadata
4. Recommended frontend variables
Section titled “4. Recommended frontend variables”For a copied web project, put identity in zship.app.json and configure service endpoints with env values like:
AUTH_SERVICE_URL=https://n1.example.comPAY_SERVICE_URL=https://n3.example.comBLOG_API_URL=https://n5.example.comSITE_SERVICE_URL=https://n7.example.comCHECKIN_SERVICE_URL=https://n9.example.comAI_SERVICE_URL=https://n10.example.comSUPPORT_SERVICE_URL=https://n2.example.comImportant:
appKeyinzship.app.jsonmust match the tenant created in AdminsiteUrlinzship.app.jsonshould be the real production URL, not a temporarypages.devpreview URL
5. Redeploy after env or domain changes
Section titled “5. Redeploy after env or domain changes”After changing env, custom domains, or frontend identity, redeploy the frontend. Otherwise old values may still be baked into the build output.
6. Final sanity check
Section titled “6. Final sanity check”Before initialization, make sure:
- The public site opens under the correct production domain
- Admin opens under the correct production domain
- Login and registration target the correct tenant
- Frontend requests hit the real production backend URLs
- Sitemap, robots, and canonical metadata point at the production domain rather than
pages.dev