Common Workflows

End-to-end walkthroughs of the most common Environment Sync workflows, from promoting changes to production to adopting sync on an existing project, rolling back, and recovering a drifted environment.

These workflows cover most day-to-day use of Environment Sync: promoting changes to production (all of them, or just the ones that are ready), starting to use sync on a project that already exists, undoing a bad push, re-aligning an instance after manual changes, and standing up a new instance from the sync files. The concepts behind each step live in How It Works; every flag is in Reference.

Promote changes from development to production

You model in the Data Studio on a development instance. Production receives the state captured in sync files that were reviewed in git.

  1. Make your changes on the development instance: collections, fields, flows, permissions.
  2. Pull them into the repository and commit:
    d6s sync pull --from dev
    git add directus/
    git commit -m "Add author bio fields"
    

    The sync files are deterministic, so the commit shows your change and nothing else. On a development instance other people also use, a scoped pull (--collections posts, or a resource flag like --flows) lets you overwrite only the part you are ready to review.
  3. Open a pull request. Reviewers read the change as plain JSON diffs. A CI check can add the target's view of the same change:
    d6s sync diff --to production --json
    
  4. On merge, apply:
    d6s sync push --to production --yes
    

    The default merge mode creates and updates but never deletes. A second run reports nothing to push.
Removals need mirror. A change that deletes a field or a record does not propagate under merge. Push with --mode mirror and pass its deletion gate, and run a full pull first so the mirror applies current state, not a stale tree.

Promote only the changes that are ready

A shared development or staging instance usually carries more than one piece of work at a time. Say the posts changes are ready to ship, and half-finished authors changes are not. Scope the pull to what ships:

d6s sync pull --from staging --collections posts

What that pull just did:

  • The posts Schema sync files were overwritten with the current source state.
  • The authors Schema sync files were outside the pull scope and stayed unchanged on disk. They still hold what the last full pull captured, which is the state production already runs. The half-finished authors work never entered the sync files.
  • The default Configuration sync files were also overwritten because a collection scope only narrows Schema. Their source state had not changed, so the overwritten files were byte-identical and git status shows only the posts files. If a flow had changed on staging, its file would appear too; exclude it with --no-flows.
  • Dependent resources cannot be excluded individually. Permissions are included with policies, and operations are included with flows. A teammate's new permission records therefore appear whenever the pull includes policies.

Commit, then diff and push as usual:

git add directus/
git commit -m "Add post fields"
d6s sync diff --to production
d6s sync push --to production

A push always applies the full sync project on disk. The posts change applies. The authors files already match production, so nothing happens there. The unfinished authors work is absent from the sync files, so it cannot ship, no matter what state staging is in.

For a configuration-only change, flip the scope: select the resource type and skip schema.

d6s sync pull --from staging --flows --no-schema

The What a pull touches table shows exactly which sync files each combination overwrites from the source and which it leaves unchanged.

Selection is by collection and resource type, not by record.--collections posts can separate finished posts work from unfinished authors work, but two changes inside the same collection travel together, and --flows takes every flow, not just one. If unrelated work shares a collection or a resource type, ship it together or wait. While the sync files hold a partial picture, avoid mirror: it would apply the stale remainder too.

Adopt Environment Sync on an existing project

Most projects don't start from an empty instance. You have a development instance, a production instance, and months of changes applied to each by hand. Here's how to get from that to a synced setup.

  1. Decide which instance is closest to the state you want everywhere. Usually that's production: it's the environment you've been careful with.
  2. Pull it as your baseline and commit:
    d6s profile add production --url https://cms.example.com   # offers to save a credential; or pass --token
    d6s sync pull --from production
    git add directus/ directus.config.json
    git commit -m "Baseline from production"
    
  3. See how far each other environment has drifted:
    d6s profile add staging --url https://staging.example.com
    d6s sync diff --to staging
    

    The first diff on a long-lived pair of instances can be long. Read it as an inventory of drift, not a to-do list: everything listed is a place where staging and production genuinely disagree, accumulated over however long the two were maintained by hand.
  4. Bring the environment in line gradually. A merge push adds and updates what staging is missing without deleting anything, so staging-only experiments survive:
    d6s sync push --to staging
    

    Once nothing on staging is worth keeping outside the sync files, a mirror push finishes the job and makes it match exactly.
  5. Expect a few identity questions on the first push. Staging and production often hold records that are plausibly the same one (two roles named "Editor", two flows built from the same template). The CLI asks instead of guessing; answer, then commit the updated id_map.json. Those decisions are reused on later pushes between the same source and target URLs.

From then on, the project runs the promote workflow: change on dev, pull, review, push.

Nervous about the first push against a real instance? Rehearse the whole loop against throwaway instances first: the Quickstart sandbox is exactly that, and adding a profile for a scratch instance to a real repository is harmless.

Roll back a bad push

A change made it to production and turned out to be wrong. Git history records every reviewed state of the sync files, so rollback is a git operation followed by a push. One asymmetry matters:

  • If the bad change modified something, reverting the commit and pushing with merge restores the old values.
  • If the bad change added something, merge cannot undo it: after the revert, the field or record is absent from your sync files, and merge never deletes what the sync files don't mention. Removal is a deletion, and deletions need mirror.

Say the bad commit added a summary field and a notification flow, and tweaked the note on articles.title. Revert it, then preview the undo in mirror mode (the default merge preview plans no deletions, so it cannot show you a removal):

git revert <bad-commit>
d6s sync diff --to production --mode mirror
● Comparing ./directus/default with production — https://cms.example.com (mirror — INCLUDES DELETIONS)
● Schema — 2 changes: 0 added, 1 modified, 1 deleted
✖ DELETE  field articles.summary
~         field articles.title (meta.note)
● Configuration — 1 change: 0 created, 0 updated, 1 deleted
~ directus_flows  +0 new  ~0 updated  ✖1 deleted (f7)

The ~ line restores the old note. The ✖ DELETE and ✖1 deleted lines are the undo, and they should name exactly what the bad change added, and nothing else. If anything else shows up for deletion, your sync files are stale: stop, run a full pull from production on a scratch branch, read that diff to see what production actually holds, and fold anything worth keeping into the sync files before mirroring.

d6s sync push --to production --mode mirror

The push repeats the plan, then the deletion gate demands typed consent:

This push permanently deletes 1 configuration record and 1 schema deletion from production. Type "production" to confirm:

In automation, the same push requires --dangerously-allow-delete instead.

Undoing an added field deletes its content. Mirroring away summary drops the column and everything editors typed into it since the bad push. The sync files cover configuration; only a database backup covers content. Back up the target first if that content matters.

When the bad change is tangled up with good ones, fixing forward is often simpler than reverting: correct it on the development instance, pull, and promote the fix like any other change.

Rebase an environment from production after drift

Sometimes production changes outside the deployment path, usually an urgent manual fix. Staging and the sync files no longer reflect reality. Bring the fix into git, then re-align the lower instance. This is what mirror is for: making an instance match the sync files exactly.

  1. Pull the full state from production. The manual change appears as an ordinary git diff, which is your record of what the hotfix actually was:
    d6s sync pull --from production
    git diff
    git add directus/
    git commit -m "Adopt production hotfix"
    
  2. Preview what re-aligning staging would mean:
    d6s sync diff --to staging --mode mirror
    

    Read the deletions closely. Anything that exists only on staging and falls inside the sync's scope is on the list. (The mode matters: a default merge preview plans no deletions, so only a mirror diff shows what re-aligning would remove.)
  3. Make staging match the sync files:
    d6s sync push --to staging --mode mirror
    

    Interactively, the push names the losses and asks you to type the profile name; in automation it requires --dangerously-allow-delete. See deletion gates.
Mirror removes staging-only work in scope. If staging holds experiments you want to keep, pull that work into a branch first, or use merge and clean up by hand.

Stand up a new environment

Going from an empty instance to a working copy of your project's shape:

  1. Provision a fresh Directus instance and create its admin account as usual.
  2. Add a profile for it:
    d6s profile add staging --url https://staging.example.com --token <token>
    
  3. Preview, then push the sync files:
    d6s sync diff --to staging
    d6s sync push --to staging
    

    Schema applies first, then the Configuration records import.
  4. Commit the updated id_map.json. The push records which target record corresponds to each source record, and the ID map is how every later push updates records instead of duplicating them.

Things to expect on a first push:

  • Identity questions. If the target already holds records the CLI cannot tell apart from the source records (two policies named "Administrator" is the classic case), an interactive push asks you to choose. Commit the ID map so later pushes between the same source and target URLs reuse the answer. See record identity.
  • Recognized secrets stay behind. API keys in settings and fields marked concealed, hashed, or encrypted are stripped. Set them on the new instance directly. Free-form flow request headers are not stripped, so review the warning and the operation files before committing them.
Extensions do not sync, and nothing warns about them. A field built on a custom interface, or a flow using a custom operation, pushes silently to a target that may not have that extension installed, and arrives broken in the Studio until it is. Deploy your extensions to the target before pushing schema or flows that depend on them.
Push the full sync project to a fresh target, not a scoped slice. A partial sync project whose relations point at collections outside the scope can fail to apply on an instance that has nothing else yet. The pull warns about these references when it writes the sync files.

Get once-a-month release notes & real‑world code tips...no fluff. 🐰