05. Bind Domains and Environment Variables
Bind production domains, switch frontend variables to real online addresses, and understand how copied web apps should be wired.
Last updated Mar 26, 2026
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?
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
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 to a new project and customize there. Do not modify apps/web directly. Treat it as the official template.
If you copy apps/web 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/webto 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
admin usually deploys to Pages. Bind the domain in Workers & Pages -> admin -> Custom domains.
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)
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
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
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
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.com
PAY_SERVICE_URL=https://n3.example.com
BLOG_API_URL=https://n5.example.com
SITE_SERVICE_URL=https://n7.example.com
CHECKIN_SERVICE_URL=https://n9.example.com
AI_SERVICE_URL=https://n10.example.com
SUPPORT_SERVICE_URL=https://n2.example.com
Important:
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
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
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
