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 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
Section titled “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 env aligned
- The frontend still reads
APP_KEYat runtime today - Dev Console syncs env from
zship.app.json, but env is still part of the runtime path
- The frontend still reads
-
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- Still used by the frontend runtime as typed site configuration
- Dev Console syncs manifest values into this file
-
Environment variables
- Local:
.env - Production: Cloudflare Variables and Secrets
- The frontend still reads
APP_KEY,SITE_URL, and service URLs from env at build/SSR/runtime boundaries
- Local:
So the practical rule today is:
- Edit
zship.app.json - Let Dev Console sync to
site.tsand.env - Remember that runtime still reads env
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 URLSITE_URLshould follow that 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, you will usually configure values like:
APP_KEY=my-brandSITE_URL=https://my-brand.comAUTH_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:
APP_KEYmust match the tenant created in AdminSITE_URLshould 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