Quickstart

Run the full pull, diff, push loop against two throwaway Directus instances, and see what the CLI does at every step.

The fastest way to trust Environment Sync is to watch it work somewhere mistakes are free. In this guide you stand up two throwaway Directus instances with Docker, make a change on one, and move it to the other through git. Nothing here touches a real project, and at the end one command tears it all down.

You need Docker, Node.js with npm, and git.

Start two instances

Save this as docker-compose.yml in an empty directory:

services:
  source-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: "directus"
      POSTGRES_PASSWORD: "directus"
      POSTGRES_DB: "directus"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U directus"]
      interval: 5s
      retries: 12

  source:
    image: directus/directus:latest
    ports:
      - 8055:8055
    environment:
      SECRET: "quickstart-source-secret"
      DB_CLIENT: "pg"
      DB_HOST: "source-db"
      DB_PORT: "5432"
      DB_DATABASE: "directus"
      DB_USER: "directus"
      DB_PASSWORD: "directus"
      ADMIN_EMAIL: "admin@example.com"
      ADMIN_PASSWORD: "quickstart"
      ADMIN_TOKEN: "source-token"
    depends_on:
      source-db:
        condition: service_healthy

  target-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: "directus"
      POSTGRES_PASSWORD: "directus"
      POSTGRES_DB: "directus"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U directus"]
      interval: 5s
      retries: 12

  target:
    image: directus/directus:latest
    ports:
      - 8056:8055
    environment:
      SECRET: "quickstart-target-secret"
      DB_CLIENT: "pg"
      DB_HOST: "target-db"
      DB_PORT: "5432"
      DB_DATABASE: "directus"
      DB_USER: "directus"
      DB_PASSWORD: "directus"
      ADMIN_EMAIL: "admin@example.com"
      ADMIN_PASSWORD: "quickstart"
      ADMIN_TOKEN: "target-token"
    depends_on:
      target-db:
        condition: service_healthy
docker compose up -d

After a minute, http://localhost:8055 (the source) and http://localhost:8056 (the target) both serve a fresh Data Studio. Log in with admin@example.com / quickstart. The ADMIN_TOKEN values are static admin tokens the CLI will use.

Both services use the same image tag on purpose. Environment Sync requires the source and target to run the same Directus version, patch release included. In real projects, pin the same explicit version on every environment.

Install the CLI

npm install -g @directus/cli
d6s --version

The package installs two commands that do the same thing: directus-cli and the short alias d6s. These docs use d6s.

Add a profile for each instance

A profile pairs a name with an instance URL. Run this in the same directory as the compose file:

d6s profile add source --url http://localhost:8055 --token source-token
◇ Saved profile "source" → http://localhost:8055
◇ Saved a token for "source" to the credential store.
d6s profile add target --url http://localhost:8056 --token target-token

That first command created directus.config.json in the current directory:

{
  "profiles": {
    "source": { "url": "http://localhost:8055", "auth": { "type": "token" } },
    "target": { "url": "http://localhost:8056", "auth": { "type": "token" } }
  }
}

Notice what is not in it: the tokens. URLs are project configuration you commit; credentials go to ~/.directus/credentials.json, readable only by you. Confirm both connections work:

d6s profile test source
◇ Authenticated to http://localhost:8055 as Admin User (Administrator).

Make something to sync

In the source Studio at http://localhost:8055, create a collection called articles with two fields: title and status. This plays the part of a day's modeling work on a development instance.

Pull it into files

Make the directory a git repository, then pull:

git init
d6s sync pull --from source
◇ Pulled from source — http://localhost:8055
  Schema     1 collection → directus/default/schema
  Resources  14 records in 10 resources → directus/default/data

Your record counts may differ slightly; a fresh instance carries a handful of configuration records (the admin role, its policy, the settings row) even before you touch it.

Look at what appeared:

directus/default/
  schema/    # one JSON file per collection, plus metadata.json
  data/      # one JSON file per configuration resource, plus metadata.json

Open the articles file under schema/. It's your collection, readable as JSON: the fields you just created, their types, their interface settings. This is what reviewers will see in pull requests. Commit it:

git add directus/ directus.config.json
git commit -m "Baseline from source"

Change something and pull again

Back in the source Studio, add one more field to articles: summary. Then:

d6s sync pull --from source
git diff

The diff touches one file, and inside it, only the new summary field. The CLI writes files deterministically: no timestamps, no reshuffling, nothing but your change. This is the property that makes the files reviewable, and it means a pull that finds nothing new leaves your working tree clean. Commit the field.

git add directus/
git commit -m "Add articles.summary"

Preview against the target

The target instance is still empty. Ask what pushing the committed files would do to it:

d6s sync diff --to target
● Comparing committed files with target — http://localhost:8056 (merge — additive, no deletions)
● Schema — 1 change: 1 added, 0 modified, 0 deleted
+         collection articles (3 fields)
● Data — no changes to import.

Read the plan: one collection to add, and the mode line tells you up front that merge, the default, never deletes anything. If your data section lists a few configuration records instead of "no changes", that's fine; fresh instances can differ slightly in their defaults. diff applied nothing; it's always safe to run.

Push

d6s sync push --to target

The push prints the same plan, then asks before applying: Apply 1 schema change to target — http://localhost:8056?. Confirm it:

● Schema applied.
◇ Push complete. Applied 1 schema change to http://localhost:8056; schema hash verified. No data changes to import.

The "schema hash verified" line is the push double-checking that the target's schema ended up matching the plan it previewed, rather than assuming it did.

Open the target Studio at http://localhost:8056: the articles collection is there, fields and settings intact. That's the whole loop. Change on one instance, pull to files, review in git, push to another.

If a push imports configuration records, it also writes directus/default/id_map.json, which remembers which target record each committed record became. Commit it when it changes; How It Works explains why.

Push again

d6s sync push --to target
● Pushing to target — http://localhost:8056 (merge — additive, no deletions)
◇ target already matches the committed files — schema and data match; nothing to push.

Nothing to confirm, nothing applied. The CLI checked the target and proved it matches; it didn't assume. Re-running a completed push is safe, which is exactly what automation needs.

Clean up

docker compose down

Both instances and their databases are gone.

Where to go next

  • How It Works: the mental model behind what you just did, and the safety rules around deletions and record identity.
  • Common Workflows: the same loop applied to real situations, including adopting sync on an existing project.
  • Reference: every flag the commands take, including how to scope a pull to specific collections or resources.

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