Server Sync — Pull Remote → Local
Pulls the remote content state (database and assets) down to a local DDEV environment using sync.sh.
Note on "production":
sync.shpulls from the authoritative content source — typically production, but may be a pre-prod server during pre-launch phases. The.envREMOTE_*vars always point to whichever environment holds the master content.
Higher-level workflow:
ddev-syncwraps this in a full "refresh local dev" pipeline — start DDEV, runsync.sh, then run the framework build step. Preferddev-syncfor day-to-day refreshes; useserver-syncwhen you need the lower-level reference or safety checklist.
[!WARNING]
sync.shis highly destructive. It will drop and overwrite the local DDEV database and assets. Always confirm the source environment before running.
Prerequisites / Safety
[!IMPORTANT] DDEV containers don't inherit host SSH keys. Run
ddev auth sshonce per DDEV instance before either of these:
sync.sh— needs authenticated SSH to reach the remote server (see below).composer update/installon any SSH-sourced VCS package (git@github.com:..., e.g. adynamic/*module pulled as a VCS repository rather than from Packagist) —ddev composerfails to clone/fetch it untilddev auth sshhas run.Symptom without it:
Permission denied (publickey), or a stalled SSH prompt with no interactive terminal to answer it.
[!IMPORTANT]
sync.shruns INSIDE the DDEV container (ddev exec ./sync.sh). It connectsmysql/mysqldumpto the ddevdbservice host and imports into the container DB, so it must run where that hostname resolves. The script validatesssh rsync mysqldump mysql gzip gunzip— note noddevin that list.
# Authorize SSH agent first
ddev auth ssh
# Pull remote → local: runs INSIDE the container
ddev exec ./sync.sh
Environment Configuration (.env)
sync.sh reads REMOTE_* vars from the project .env:
REMOTE_USER="username"
REMOTE_HOST="target-host.com"
REMOTE_ASSETS_PATH="/var/www/html/public/assets"
REMOTE_DB_NAME="db_name"
REMOTE_DB_USER="db_user"
REMOTE_DB_PASSWORD="db_password"
REMOTE_DB_HOST="localhost"
[!CAUTION]
REMOTE_*andPREPROD_*must point at DIFFERENT hosts.sync.shreadsREMOTE_*(pull FROM).deploy.shreadsPREPROD_*(push TO). If both vars resolve to the same host, you pull from pre-prod and deploy to pre-prod — production is never touched. Always verify before migration work:grep -E "REMOTE_HOST|PREPROD_HOST" .env # must be two DIFFERENT hosts grep "^REMOTE_" sync.sh # sync.sh must read REMOTE_*
Command Flags
| Flag | Description |
|---|---|
--help |
Shows usage information |
--dry-run |
Tests the rsync without writing to disk or importing the DB. Note: the remote mysqldump is executed and the dump is created even in dry-run mode — only the local import/restore is skipped. See the dry-run caution below. Always recommend running --dry-run first. |
--assets |
Bypasses the database phase. Only syncs public/assets/. |
--db |
Bypasses the asset rsync phase. Only drops and imports the database. |
--exclude=PATTERN |
Exclude specific files or globs from the rsync cycle (repeat for multiple patterns). Example: --exclude=*.log --exclude=_resampled/ |
Script Internal Logic
- DB segment: Runs
mysqldumpvia SSH on the remote server, gzips to/tmp, pulls viarsync, then pipes throughgunzip→ drops local DB → imports. - Remote cleanup: Removes the dump from
/tmpon the remote server. - Assets segment: Executes
rsync --deleteto map the remote/assets/directory down, purging orphaned local files.
Dry-Run Caution
[!WARNING]
--dry-runcreates — and, if cleanup is gated incorrectly, leaves — a full DB dump in/tmpon the remote server. The remotemysqldumpruns before the dry-run gate; only the local import is skipped. If the script's cleanup step is gated on!dry-run, every--dry-runsilently accumulates a complete DB dump on the server — a data-at-rest exposure.Fix: Make the remote cleanup unconditional — the dump is always created, so cleanup must always run:
# Cleanup — always remove the remote dump (created even in --dry-run) ssh ${REMOTE_USER}@${REMOTE_HOST} "rm -f ${REMOTE_DUMP_PATH}"The dry-run gate should guard only the import/restore step. Audit your local
sync.shto confirm cleanup is not gated on dry-run mode.
DDEV Lifecycle Gotchas
[!WARNING]
ddev poweroffclears the ssh-agent. Afterddev poweroff(or a full restart) the ddev-ssh-agent container is removed, so the nextsync.shfails with an SSH auth error. Re-runddev auth sshbefore syncing. (deploy.shuses host SSH, so it's unaffected.)
[!WARNING] OrbStack port forwarding goes stale after
ddev mutagen reset+ddev restart. The.ddev.sitehostname can returnERR_CONNECTION_RESETeven though the containers are healthy. Fully re-initialize withddev poweroff && ddev start. A direct127.0.0.1:<port>(fromddev describe) often still works as a stopgap.