Two deployables, two targets: tasks go to Trigger.dev, the zone goes to Vercel. Neither knows about the other; the platform is what joins them.
1. Environments
Trigger.dev has three: Development, Staging, Production. Which one anything talks to is decided entirely by which secret key it holds — there is no environment name in your code, because tasks are triggered by their string id.
Development tr_dev_… trigger dev, running on your machine
Staging tr_prod_… deployed, for verification
Production tr_prod_… deployed, live
That has one consequence worth internalising: a platform runtime environment configured with a staging key can only reach tasks deployed to staging. If a run fails with "task not found" and the task id is definitely right, check which key the runtime environment holds.
It also means environment switching is a one-line change — swap the key.
Deployed runs cannot read your local files. trigger dev loads .env.local from the repository root, which is why the dev loop works with no extra setup. A deployed task has no filesystem of yours, so every variable it needs must be set in the Trigger.dev dashboard for that environment. Forgetting this produces a task that works perfectly in dev and throws on an undefined variable in staging.
Set these per Trigger.dev environment:
DATABASE_URL
CXPA_API_URL
CXPA_REVENUE_CHAT_AGENT_API_KEY
CXPA_REVENUE_CHAT_INGEST_API_KEY
CXPA_NIGHTLY_ETL_AGENT_API_KEY
Third-party credentials are not in this list, and should not be: they are resolved from the platform on every run.
2. Database migrations
Your own database, your own migrations. Apply them before deploying code that depends on them:
npx supabase link --project-ref <ref>
npx supabase db push
3. Deploy the tasks
npx trigger deploy --env staging
Verify in staging, then:
npx trigger deploy --env prod
Check that additionalFiles in trigger.config.ts lists every non-code file a task reads at runtime. This is the failure that only ever appears in a deployed environment.
4. Deploy the zone
The zone is its own Vercel project, with root directory web.
Set the six environment variables from Register the zone, with production values and no trailing slashes. DISABLE_ZONE_AUTH must not be present at all.
Two Vercel settings must be changed, and both are non-obvious:
Skew Protection → off, on both the zone project and the platform project. With it on, the two projects' deployment identifiers collide during the asset proxy and every _next/* request ends in a redirect loop.
Deployment Protection → Vercel Authentication: Only Preview Deployments, or off. With "All Deployments", proxied asset requests get redirected to Vercel's SSO page and loop. Password protection likewise must be off or preview-only on production.
Neither weakens security: the zone rejects any request without a valid JWT at the middleware. Vercel-level protection on production adds nothing and breaks the proxy.
Deploy the zone in the same region as the platform project. Cross-region rewrites add 50–200 ms to every request.
5. Go-live checklist
Each item is marked with who can do it: [admin] platform administrator, [member] anyone in the org, [maintainer] whoever maintains the platform deployment, [you] your own infrastructure. If you hold none of the first three, this is your delegation list — see Roles and permissions.
Platform configuration:
- [admin] Runtime environment holds a production secret key, and its connection test passes.
- [admin] Nango environment configured, if any credential is OAuth.
- [admin] Each agent's Task ID matches its deployed task's
idexactly. - [admin] Max run duration is set to something realistic and agrees with the task's
maxDuration. - [member] Every required credential requirement shows as connected.
- [member] Schedules exist, with the right cron and the right timezone.
- [member] An
emailaction onfailedexists for unattended agents. - [admin] External trigger and webhook switches are on only if something external needs them.
Zone:
- [maintainer] Registry entry present,
ZONE_*_URLset, platform redeployed without build cache. - [you] All six zone variables set;
DISABLE_ZONE_AUTHabsent. - [you]
curlagainst the zone's own origin returns 401. - [you] Assets load from the platform's origin.
- [you] Skew Protection off on both projects; Deployment Protection preview-only or off.
[admin] Now set each agent's status to Active at /acme/{agentSlug}/settings.
This has to happen before the end-to-end checks, not after. A discovery agent refuses to execute — Run Now returns Agent is not active (status: discovery) — so there is no way to prove the agent works while it is still in discovery. Discovery protects you while you are configuring; it is not a mode you can test in.
End-to-end, once active:
- [member] Trigger each agent once via Run Now or the input surface. Both reach
completed. - [member] Run detail shows the expected
output_dataand a non-zerooutputs. - [member] Logs load for a completed run.
- [member] Every credential resolved cleanly — no
resolveCredentialerrors in the run logs. - [member] The zone loads, triggers a run, and shows progress through to a result.
- Schedules are disabled until the manual checks pass, so a cron fire cannot land mid-verification.
These runs are real. The platform has no test mode: once an agent is active, every run counts toward its run history, and any run reporting
outputsfeeds the customer-facing value metrics. Verify with input that is safe to process, and expect the rehearsal to appear in the numbers.
6. After go-live
Rotating a key is a two-step change with a gap. Rotate on the platform, then update the consumer and redeploy. Between the two, calls with the old key fail — so do it deliberately, not during a busy period:
| Key | Consumer to update |
|---|---|
agent_api_key | The Trigger.dev environment variable |
ingest_api_key | Whatever external system triggers the agent |
zone_jwt_secret | The zone's Vercel environment, then redeploy |
| Runtime secret key | The platform runtime environment only — agents pick it up automatically |
That last row is the payoff for using a shared runtime environment: rotating a Trigger.dev key is one edit, no matter how many agents use it.
Adding an agent to an existing org is much shorter than the first one: create the record, point it at the existing runtime environment, declare its credential requirements, deploy the task. No platform redeploy unless it needs a zone.
7. Onboarding another customer
Adding a customer is a different shape from adding an agent, and more expensive than most people expect. Worth knowing before you price the second one.
Everything scoped to an organization has to be created again, and — if that customer gets a zone — a zone deployment is per customer, not per product. The org and agent slugs are compiled into the zone's basePath at build time, so one build serves exactly one (org, agent) pair. There is no multi-tenant zone build.
Per customer:
| Step | Who | Repeat per customer? |
|---|---|---|
| Create the organization | Platform admin | Yes |
| Runtime environment (Trigger.dev) | Platform admin | Only if they use their own Trigger.dev project |
| Nango environment | Platform admin | Yes — recommended, so connections stay separated |
| Agent records | Platform admin | Yes |
| Credential requirements | Platform admin | Yes |
| Connect the credentials | The customer | Yes |
| A separate zone deployment, built with their slugs | You | Yes, if they get a zone |
Registry entry + ZONE_*_URL | Platform maintainer | Yes |
| Platform redeploy without build cache | Platform maintainer | Yes |
| Deploy the tasks | You | No — one deployment serves every customer |
The task code is the one thing that does not multiply. Everything else does, and the last three rows are the ones that hurt: each new zoned customer needs a change to the platform's source and a redeploy, performed by someone who may not be on your team. Build that lead time into onboarding rather than discovering it on the day.
Two consequences worth designing for early:
- Decide the database shape before customer two, not after. One database per zone deployment or one shared with an org predicate on every query — Zone data and tenancy sets out the fork, and retrofitting the org column later means auditing every query you have written.
- A customer without a zone is far cheaper. If the built-in JSON editor and the run detail page are enough for a given customer, they need no deployment, no registry entry, and no platform redeploy.
8. There is no staging
Worth stating plainly, because the absence is easy to mistake for something you have not found yet.
The platform has no environment concept. Agents and organizations carry no environment flag; a runtime environment is a connection to a Trigger.dev project, not a platform-side environment; and discovery status means "cannot run at all", not "runs against staging".
So there is no supported way to rehearse an agent against anything other than production. What you can do instead:
- Point a second agent record at a non-production runtime environment. Give it a runtime environment holding your Trigger.dev staging key and its own credential requirements connected to test accounts. It is a real agent in the customer's org — or in an org of your own — and it is the closest thing to a staging agent that exists.
- Keep schedules disabled until the manual checks pass, so nothing fires while you are still verifying.
- Expect your verification runs to count. Once an agent is active, every run appears in its history, and any run reporting
outputsfeeds the hours-saved and ROI figures the customer sees. There is no flag that excludes a test run. Rehearse in an org of your own where you can, and where you cannot, use input that is safe to process and accept the handful of runs in the numbers.
Next
Troubleshooting — worth reading before you need it.