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.
What you need
Section titled “What you need”- 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.
Install
Section titled “Install”cd /your/projectddev add-on get NoiXdev/ddev-syncddev auth sshddev restartThat 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:
ddev add-on get NoiXdev/ddev-sync --version v0.1.0-rc.2The first restart also pulls DDEV’s small ddev-utilities image if it is not there already, which takes a moment.
Installing from a clone
Section titled “Installing from a clone”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.
git clone https://github.com/NoiXdev/ddev-sync /path/to/ddev-syncdocker build -t ddev-sync:dev /path/to/ddev-synccd /your/projectddev add-on get /path/to/ddev-syncThen add these two lines to the project’s .ddev/.env, and only then restart:
DDEV_SYNC_IMAGE=ddev-syncDDEV_SYNC_VERSION=devddev auth sshddev restartCheck that it worked
Section titled “Check that it worked”ddev sync doctor is the proof, and it needs a target to check. ddev sync init creates one and then runs doctor itself:
ddev sync initIt 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:
ddev sync --target production doctorThe 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-socketThe 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 containershows 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 anOKhere covers the socket too; there is no row of its own for it.ssh agentshows 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.
What the install adds to your project
Section titled “What the install adds to your project”All of it lives under .ddev/:
- the
ddev synccommand, 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 itsknown_hosts, and a generatedpasswdfile 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.
If the project will not start
Section titled “If the project will not start”-
ddev-ssh-agentis inomit_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. Removeddev-ssh-agentfrom the list wherever it appears: the global config (~/.ddev/global_config.yaml),.ddev/config.yaml, or a.ddev/config.*.yamlfile. Edit the list rather than clearing it withddev config global --omit-containers=, which would also switch back on anything else you omitted globally, such asddev-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 setDDEV_SYNC_IMAGEandDDEV_SYNC_VERSIONin.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.
Removing it
Section titled “Removing it”ddev add-on remove ddev-syncThis 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.
What has and has not been tried
Section titled “What has and has not been tried”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.