How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Move jobs from staging to production safely

Deploy the templates, export and import the jobs, then set what did not travel (site, schedule, site data), check what did (record ids, URLs, slugs), and publish one job at a time.

Context

Context. Jobs are built and tested on a staging or development site and moved to production with the site's data export. The export carries each job's definition and actions, but not everything a job needs to work, and some of what it does carry is wrong on the other side. This is the order that avoids the usual failures: a job that runs against the partner's sandbox, a trigger aimed at the wrong form, a nightly job with no schedule.

Steps

  1. Deploy the templates first. Everything under templates/hooks/ goes to production through the repository, like any template. A job whose Send mail or Render template names a file that is not there yet fails at run time, not at import.
  2. Export the jobs from staging (Utilities › Export), choosing only the jobs being moved.
  3. Import on production in skip mode the first time. Use overwrite only for a later update of jobs that came from an earlier export; it updates each job it recognises from the export.
  4. Set what did not travel: the site each job is limited to, and the schedule of every scheduled job. Both are empty after an import.
  5. Set the site data each job reads on production: API keys and URLs for the production systems, recipient addresses, and nothing that exists only for testing, such as a mail redirect key.
  6. Check what did travel and may be wrong, job by job: record ids in triggers and actions (a trigger narrowed to form 12 now means whatever has id 12 here), URLs typed into actions, and web event slugs, which arrive unchanged.
  7. Publish one job at a time, with batch persistence always, and trigger each once with a real but harmless record. Read its batch.
  8. Record what moved: the jobs, the export date and the site data keys set, in the repository next to the templates, so the next move can be checked against it.

Code

There is no code in the move itself; the protection is in how the jobs were written. Read everything that differs between environments from site data, so the job definition is identical on both sides:

Template code
url:     {$siteData->get('crm-api-url')}/contacts
headers: Authorization: Bearer {$siteData->get('crm-api-key')}
to:      {$siteData->get('enquiries-to')}
url:     {{ siteData.get('crm-api-url') }}/contacts
headers: Authorization: Bearer {{ siteData.get('crm-api-key') }}
to:      {{ siteData.get('enquiries-to') }}

And make a job that is missing its site data fail visibly rather than send somewhere by default. A condition first:

Template code
{if $siteData->get('crm-api-url') && $siteData->get('crm-api-key')}{$result->setMatching(true)}{/if}
{% if siteData.get('crm-api-url') and siteData.get('crm-api-key') %}{% do result.setMatching(true) %}{% endif %}

with, in its else branch, a Stop with status error, so the batch log says the job is not configured instead of showing a request to an empty URL.

Why this and not that

Not rebuilding the jobs by hand on production: two hand-built copies drift, and the next fix goes into one of them. Not overwrite mode by default: it updates every production job the export recognises, including one somebody has since fixed on production, without asking. Not publishing everything at once: an import can carry a dozen jobs, and when one misbehaves the job runs of the others make it harder to see which. And not moving site data with the jobs, which the export does not do: each environment's keys are its own, and a staging key on production is the mistake this whole arrangement exists to prevent. The full list of what an export carries is on Moving jobs between environments.

Go deeper

Was this page helpful?

Your answer goes to the docs team.