Skip to content

Installation

ddev-sync is a DDEV add-on: installing it gives a project a ddev sync command and a small companion container that runs the sync. It installs in one command from its published release, and the container image is pulled for you.

The newest release is a release candidate. A bare ddev add-on get installs it anyway, because DDEV asks GitHub for the release list and takes the newest entry without setting pre-releases aside. That is deliberate here rather than a surprise — it is the only way to install this yet — but it means you are installing a candidate, and that it will move when the next one is published. Pin a version if you would rather decide when that happens.

  • DDEV 1.25.0 or newer and a running Docker. That is the floor the add-on declares, and it is close to what has actually been tried: only DDEV 1.25.4 on macOS with Docker Desktop. Linux, Colima, OrbStack and rootless Docker have not been tried.
  • DDEV’s ssh agent switched on. It is on by default. The sync authenticates to the remote through the agent DDEV forwards, so with the agent switched off there is nothing to authenticate with, and the project will not start. See If the project will not start.
  • A key the agent knows, loaded with ddev auth ssh.
Terminal window
cd /your/project
ddev add-on get NoiXdev/ddev-sync
ddev auth ssh
ddev restart

That is all of it. The add-on carries the version it shipped with, so the sidecar’s image reference already names a published tag and the pre-start hook pulls it on the first start — nothing to build and nothing to write into .ddev/.env. This path is checked on every change: the add-on’s own test suite installs the newest release into a real DDEV project, restarts it with no image override anywhere, runs ddev sync --version and removes it again.

To stay on one version rather than following the newest release, name it:

Terminal window
ddev add-on get NoiXdev/ddev-sync --version v0.1.0-rc.2

The first restart also pulls DDEV’s small ddev-utilities image if it is not there already, which takes a moment.

For working on ddev-sync itself, or for a change that is not released yet, install from a checkout and build the image yourself.

Here the order is not a matter of taste. A hook runs before every start of the project and needs the container image to exist, so the image has to be built, and .ddev/.env has to name it, before the first ddev restart. Skip that and the whole project refuses to start — not just the sync.

Terminal window
git clone https://github.com/NoiXdev/ddev-sync /path/to/ddev-sync
docker build -t ddev-sync:dev /path/to/ddev-sync
cd /your/project
ddev add-on get /path/to/ddev-sync

Then add these two lines to the project’s .ddev/.env, and only then restart:

DDEV_SYNC_IMAGE=ddev-sync
DDEV_SYNC_VERSION=dev
Terminal window
ddev auth ssh
ddev restart

ddev sync doctor is the proof, and it needs a target to check. ddev sync init creates one and then runs doctor itself:

Terminal window
ddev sync init

It asks what it needs to set the project up — the detected project type, the production URL a sanitize rule rewrites to, where the remote keeps its environment file, which detected features to keep, and then the target’s name, connection and stage — and writes that one target to .ddev/ddev-sync.yaml, the commented template the install put there. If the credentials do not resolve, it asks for those too and writes them to .ddev/ddev-sync/local.yaml, which the add-on’s .gitignore covers, rather than to the committed file. Nothing is written until every question has an answer, so stopping part-way leaves both files as they were. .ddev/ddev-sync.yaml is committed with the project, so init ends by saying what to do about that: move the target to ~/.ddev/ddev-sync/config.yaml to keep the host and user out of the repository. That is where targets belong, and init cannot write them there itself, because the add-on mounts that directory into its container read-only. Configuration explains the keys and which file takes what; to write a target by hand instead, check it with:

Terminal window
ddev sync --target production doctor

The options go before the subcommand; see Commands and options. The output looks like this. The values are illustrative, and the ssh connection and deploy path rows have so far only been seen failing, because no successful connection to a real remote host has been made through the add-on:

Doctor for target 'production'
OK ssh connection web01.example
OK deploy path /srv/app/
OK disk space 184 GB free
OK credentials env: acme@db.internal:3306/acme_prod
OK web container ddev-acme-web answers
OK ssh agent /home/.ssh-agent/proxy-socket

The credentials row says whether the source you configured actually yields credentials — it names the database a sync would dump, and never the password. It appears for every project type, which is the point: a profile with no sanitize rules has no table to probe, so without it such a project could pass doctor and still fail at the dump. It is skipped only when the connection itself failed, because then there is no remote left to ask. A sanitize tables row joins the list when a sanitize rule requires a table that can be checked on the remote. doctor does not stop at the first failing row, so you always see all of them.

Two of the rows are about the installation itself, and they are the ones that prove it worked:

  • web container shows that the sidecar can reach your project’s own web container, which is where framework commands run. The probe goes through the Docker socket, so an OK here covers the socket too; there is no row of its own for it.
  • ssh agent shows that the sidecar finds DDEV’s forwarded agent socket. It does not show that a key is loaded.

The other rows are about the remote and your key, except disk space, which is the free space on your own machine.

If web container and ssh agent are OK, the add-on is installed and wired up, even when ssh connection fails. That is how a first run looks when the target is not reachable yet, and it is what you should expect before you have a real target. If ssh connection fails with the installation rows OK, the problem is the target, the key, or a remote host whose key has changed. A host that ssh has never seen is not a problem: ddev-sync accepts it on first contact and remembers it. The exception is a jump host, whose own key is not covered. The row says in ssh’s own words why it failed, and Your first sync covers the changed-key case, which is the one that needs you, and the jump host.

When it works, continue with Your first sync.

All of it lives under .ddev/:

  • the ddev sync command, the sidecar’s service definition and a pre-start hook, each marked as generated;
  • .ddev/ddev-sync.yaml, the configuration template, written only if the project does not have one already. It is yours and is meant to be committed;
  • .ddev/ddev-sync/home, where the sidecar keeps its known_hosts, and a generated passwd file beside it. Both belong to your machine and are ignored by git.

Outside the project, the first start creates ~/.ddev/ddev-sync in your own home if it is not there. That is where your targets go; the sync container gets it mounted read-only, so a sync can read your targets and never write to them.

The hook also writes two lines into .ddev/.env.local on every start: DDEV_SYNC_DOCKER_GID, which records which group owns the Docker socket on your machine, and DDEV_SYNC_HOST_HOME, which records where your home directory is so that the mount above can find it. Both differ from machine to machine, and both stay out of your repository without your doing anything: DDEV’s own generated .ddev/.gitignore covers /.env.local, and DDEV reads compose variables from that file in preference to .ddev/.env.

If your project was set up before this changed, the two lines are in .ddev/.env, which is not ignored. The next start moves them for you and says so once. The DDEV_SYNC_IMAGE and DDEV_SYNC_VERSION lines you added stay where you put them; they point at an image built on your machine, so a teammate needs the same two lines and their own build.

  • ddev-ssh-agent is in omit_containers. With DDEV’s ssh agent switched off, no project that has this add-on installed can start. The hook prints why and what to change. Remove ddev-ssh-agent from the list wherever it appears: the global config (~/.ddev/global_config.yaml), .ddev/config.yaml, or a .ddev/config.*.yaml file. Edit the list rather than clearing it with ddev config global --omit-containers=, which would also switch back on anything else you omitted globally, such as ddev-router.

    This matters in CI too: DDEV’s own GitHub setup action omits the ssh agent by default, so a workflow that installs this add-on has to switch it back on.

  • The hook cannot read the image. The message names the image and says what to do: build it with docker build -t ddev-sync:dev . in the clone and set DDEV_SYNC_IMAGE and DDEV_SYNC_VERSION in .ddev/.env. It is the order above, arrived at from the other end.

  • The hook cannot read the Docker socket’s group. It asks Docker, from inside a container, which group owns the socket, and the message includes what Docker answered. Docker not running is one way for that to fail.

Terminal window
ddev add-on remove ddev-sync

This removes the add-on’s own files and both the DDEV_SYNC_DOCKER_GID and DDEV_SYNC_HOST_HOME lines, from .ddev/.env.local and from .ddev/.env alike, so a project that predates the move is left clean too. It leaves .ddev/ddev-sync.yaml alone, because that file is the project’s configuration, not the add-on’s, and it does not touch ~/.ddev/ddev-sync/config.yaml either — your targets are yours and are shared with your other projects. It does delete .ddev/ddev-sync/home, and with it the sidecar’s known_hosts, so after a reinstall each remote host is accepted again on first contact. Some empty directories stay behind, which is DDEV’s behaviour and not a defect of this add-on. The DDEV_SYNC_IMAGE and DDEV_SYNC_VERSION lines you added are yours to delete.

The sync container keeps running until the next ddev restart. Removal deletes the service definition, but a container that is already up outlives the file that described it; DDEV only notices on the next start. Run ddev restart to see it go.

Installing, ddev sync doctor and removing have been run on macOS with Docker Desktop and DDEV 1.25.4. No real sync against a real remote host has been run through the add-on yet, and the automated test that exercises the add-on in CI is written but has not run. The engine and the profiles are tested separately from the add-on; the profiles reference says how far.