ddev pull WordPress-Generator

This interactive tutorial enables you to pull an existing WordPress site into a local DDEV project. Local development enables stress-free local testing and development - without breaking your live site.

Resources:

1. Select your configuration

How do you want to pull your site?
You local project name, the local site will be available via the local URL https://PROJECT-NAME.ddev.site.
Copy this value from Site Health » Info » Directories & sizes » WordPress directory location (?).
Example: "/sites/my-website/wordpress" (no trailing slash)
If you don't use a child theme currently, just keep "twentytwentyone-child". It can be changed later as well.
DDEV local project settings
name: my-wp-site
type: wordpress
docroot: ""
nfs_mount_enabled: false
mutagen_enabled: false

# WebServer settings
php_version: "7.4"
mariadb_version: "10.3"
webserver_type: nginx-fpm
✏️ Edit web server settings
PHP version
Database version
Web Server
FAQ: What settings should I choose?

If you are unsure about the server settings, just leave the defaults. They are good to go. Optional: For more serious testing you could choose the values accordingly to your live website. You can use the WordPress feature "Site Health Screen" to figure out your live sites environment, I'm also testing a small WordPress plugin ddev-pull-wp-helper which helps getting the information with one click.

2. Setup project folder and copy these files

Create a new empty directory for your project:

mkdir my-wp-site
cd my-wp-site/

The followings files were generated based on your selected configuration. Copy and paste these files into your newly created project folder:

2.1 DDEV project configuration
.ddev/config.yaml

name: my-wp-site
type: wordpress
docroot: ""
nfs_mount_enabled: false
mutagen_enabled: false

# WebServer settings
php_version: "7.4"
mariadb_version: "10.3"
webserver_type: nginx-fpm
Why is this file needed?

This is the DDEV configuration of your project folder. The web_environment-values are our custom variables. These will then be used in the provider pull script, see below. The awesome thing is that this config can be shared via git: This means it can be used in team projects as well and everyone uses the same webserver configuration. Check out the DDEV documentation for more information: config.yaml-docs.

2.2 DDEV provider script
.ddev/providers/ssh.yaml

# Pull a live site WordPress site into DDEV

# Commands:
#   'ddev pull ssh' - pulls a live wordpress site via SSH/mysqldump and rsync into DDEV.
#   'ddev push ssh' - pushes the child theme folder to the remote server
# Project repository: https://github.com/mandrasch/ddev-pull-wp-scripts

# Requires DDEV version >= 1.18.2 !
# https://github.com/drud/ddev/releases/tag/v1.18.2
# https://ddev.readthedocs.io/en/stable/users/providers/provider-introduction/

# ------------------------------   configuration  ---------------------------------------------
environment_variables:
  sshUser: user123
  sshHost: ssh.example.org
  sshWpPath: /sites/my-website/wordpress
  # path to wordpress on ssh server without(!) trailing slash, 
  # e.g.: /var/www/html/my-website.eu
  
  childThemeFolderName: twentytwentyone-child
  # just the folder name in wp-content/themes/, no slashes
  # if you don't use a child theme currently, just leave 'twentytwentyone-child'

# -----------------------------  eo configuration  -------------------------------------------

# ---------  provider script  ---------
# (Update the following code if 
#  ddev-pull-wp-scripts gets an update)
# -------------------------------------

# 1. Add ssh keys to the user agent
auth_command:
  command: |
    ssh-add -l >/dev/null || ( echo "Please 'ddev auth ssh' before running this command." && exit 1 )

# 2. Pull a fresh database dump via SSH
# 
# Get db connection credentials via bash from wp-config.php on live server,
# dump database via mysqldump to local .sql.gz file in .ddev/downloads
# 
# (Possible alternative: 'wp db export' command, not implemented yet because 
#  detecting availability on remote server was not that easy)
# 
db_pull_command:
  command: |
    # set -x   # You can enable bash debugging output by uncommenting
    set -eu -o pipefail
    pushd "/var/www/html/${DDEV_DOCROOT}" >/dev/null
    
    # TODO: add support for mixed quotes / double quotes in wp-config.php?

    (ssh ${sshUser}@${sshHost} "cd ${sshWpPath} && bash -s" <<'ENDSSH'
      #set -x # uncomment for debug

      # Only works with single quotes currently (no mixed quotes / double quotes)
      DB_USER=$(cat wp-config.php | grep "'DB_USER'" | cut -d \' -f 4)
      DB_PASSWORD=$(cat wp-config.php | grep "'DB_PASSWORD'" | cut -d \' -f 4)
      DB_NAME=$(cat wp-config.php | grep "'DB_NAME'" | cut -d \' -f 4)
      DB_HOST=$(cat wp-config.php | grep "'DB_HOST'" | cut -d \' -f 4)
      DB_CHARSET=$(cat wp-config.php | grep "'DB_CHARSET'" | cut -d \' -f 4)

      # special case, some hosters such as WPEngine have <host>:<port>,
      DB_HOST=$(echo $DB_HOST| cut -d':' -f 1) # remove port

      mysqldump \
          --user $DB_USER \
          --host $DB_HOST \
          --default-character-set $DB_CHARSET \
          --no-tablespaces \
          --password="$DB_PASSWORD" $DB_NAME
    ENDSSH
    ) > .ddev/.downloads/db.sql
    
    # DDEV will import the db.sql.gz file automatically, compress it:
    gzip -9 .ddev/.downloads/db.sql

    # Thx to https://stackoverflow.com/a/45927977/809939
    # Thx to https://www.cloudsavvyit.com/14216/how-to-run-a-local-shell-script-on-a-remote-ssh-server/

  service: web

# 3. Rsync all the files (except excludes)
files_pull_command:
  command: |
    # set -x   # You can enable bash debugging output by uncommenting
    set -eu -o pipefail
    ls /var/www/html/.ddev >/dev/null 
    pushd /var/www/html/${DDEV_DOCROOT} >/dev/null

    # Add trailing slash for sshWpPath here in a safe way
    # (maybe user mistakenly provided trailing slash, thx 
    # https://gist.github.com/luciomartinez/c322327605d40f86ee0c)
    [[ "${sshWpPath}" != */ ]] && sshWpPathTrailingSlash="${sshWpPath}/"

    echo "Downloading files from remote site to /var/www/html/${DDEV_DOCROOT}"
    # exclude child theme + some default locations of wordpress backup plugins
    # (exclude pattern is glob based, not path based)
    # add -v for output of files transferred, -vv for full debug
    rsync -azh --stats \
      --exclude=".git/" \
      --exclude=".gitignore" \
      --exclude=".ddev/" \
      --exclude="README.md" \
      --exclude="LICENSE" \
      --exclude="wp-content/themes/${childThemeFolderName}" \
      --exclude="wp-content/updraft" \
      ${sshUser}@${sshHost}:${sshWpPathTrailingSlash} .

    # TODO: Should we use -a (archive mode) or is there a better combination of flags?
  service: web

# 4. Set database connection + migrate URLs in DB
# 
# We use this step to run some important WP-CLI commands locally
# a) Replace db connection settings in wp-config.php
# b) Database migration: Replace live site url (from pulled wp-config)
#    with DDEV_PRIMARY_URL (<your-project>.ddev.site) in local database
# c) Overwrite WP_HOME and WP_SITEURL
files_import_command:
  command: |
    # set -x  # You can enable bash debugging output by uncommenting
    set -eu -o pipefail
    pushd "/var/www/html/${DDEV_DOCROOT}" >/dev/null

    echo "Adjusting wp-config db connection settings for DDEV ..."
    wp config set DB_NAME "db" && wp config set DB_USER "db" && wp config set DB_PASSWORD "db" && wp config set DB_HOST "db"

    # Important: Use wp search-replace for URL replacement
    echo "Replacing the old URL ($(wp option get siteurl)) in database with DDEV local url (${DDEV_PRIMARY_URL})..."
    wp search-replace $(wp option get siteurl) "${DDEV_PRIMARY_URL}"

    # Additional update the wp config values, because some devs/hoster, e.g. raidboxes
    # put it here (only remove if existent)
    # TODO: if [wp config has] would be better, but how to implement it here?
    # if [ "$(wp config has WP_HOME)" = 0 ]; then wp config delete WP_HOME; fi
    # if [ "$(wp config has WP_SITEURL)" = 1 ]; then wp config delete WP_SITEURL; fi
    # TODO: This throws a (false) error currently if var is not set, how do we get rid of it?
    #       event with --no-add: 
    #       Error: The constant or variable 'WP_HOME' is not defined in the 'wp-config.php' file.)
    wp config set WP_HOME "${DDEV_PRIMARY_URL}"
    wp config set WP_SITEURL "${DDEV_PRIMARY_URL}/"

    # Flush object cache
    wp cache flush

    # Optional: more steps would be possible here after import, e.g. for WP Super Cache
    # wp config delete WPCACHEHOME

    echo "All set, have fun! Run 'ddev launch' to open your site."

  service: web

# ! EXPERIMENTAL - USE WITH CAUTION !
# Push child theme folder to remote server
# (Other reliable ways are WPPusher, Github Action pipeline, etc.)

db_push_command:
  command: |
    # set -x   # You can enable bash debugging output by uncommenting
    set -eu -o pipefail
    echo "Skipping db_push_command, we don't use it."
  service: web

files_push_command:
  command: |
    # set -x   # You can enable bash debugging output by uncommenting
    set -eu -o pipefail
    pushd "/var/www/html/${DDEV_DOCROOT}" >/dev/null

    # Add trailing slash for sshWpPath (maybe user mistakenly provided trailing slash) 
    # (thx https://gist.github.com/luciomartinez/c322327605d40f86ee0c)
    [[ "${sshWpPath}" != */ ]] && sshWpPathTrailingSlash="${sshWpPath}/"

    # Send child theme from source (DDEV) to target (SSH server / webspace)
    source="/var/www/html/${DDEV_DOCROOT}/wp-content/themes/${childThemeFolderName}/"
    target="${sshUser}@${sshHost}:${sshWpPathTrailingSlash}wp-content/themes/${childThemeFolderName}"
  
    echo "Configuration for push:"
    echo "Source: ${source}"
    echo "Target: ${target}"
    
    echo "Start pushing the child theme folder..."
    rsync -rvzh "${source}" "${target}"

  service: web

Why is this file needed?

This is our pull script which takes care of pulling the live web site to your local DDEV project. See DDEV docs for more information: Hosting Provider Integration

Source: mandrasch/ddev-pull-wp-scripts/.ddev/providers/ssh.yaml
2.3 Ignore everything except child theme folder (optional)

This is for git usage. You can skip this file if you don't use a git repository.

.gitignore


# Ignore all ...
/*

# ... but track specific files / folders: 

# General files
!.gitignore
!/README.md
!/LICENSE

# DDEV config and provider script
!/.ddev
/.ddev/*
!/.ddev/config.yaml
!/.ddev/providers
/.ddev/providers/*
!/.ddev/providers/ssh.yaml

# Child theme:
!/wp-content
/wp-content/*
!/wp-content/themes
/wp-content/themes/*
!/wp-content/themes/twentytwentyone-child
Why is this needed?

This is needed to track and manage the child theme in a git repository, but ignore the rest of the WordPress files which will be pulled to the local project in the next step.

3. Start DDEV

Your configuration is all setup, run this command in your project folder:

ddev start

Add your SSH keys to DDEV to connect with the live site:

ddev auth ssh

4. Pull your live site 🙌

Alright, let's pull the live site content (files and database, except the child theme folder):

ddev pull ssh

Use with caution Please always check in the local wp-config.php-file that the database connection replacement was successfull and that you are connected to the local DDEV project database (and not the remote database of your live site).

Optional: Already have a child theme on your live site, but not in your local folder? Pull it in via:

ddev exec 'rsync -avhz user123@ssh.example.org:/sites/my-website/wordpress/wp-content/themes/twentytwentyone-child/ /var/www/html/${DDEV_DOCROOT}/wp-content/themes/twentytwentyone-child/'

5. Develop locally & have fun!

Now its possible to work with your site locally in a safe way.

Open the locally cloned site in your browser:

ddev launch

Open admin dashboard in your browser:

ddev launch wp-admin/

You can always pull again, for example when your live sites content changed and new images were added:

ddev pull ssh

Info Your local database and files will be overwritten with the latest database and files from your live site (expect for the child theme folder defined in .ddev/providers/ssh.yaml).

6. Experimental Push your child theme via SSH

You can push your local child theme folder via SSH (rsync) to your live site. While this is a quick and easy way of deploying for single developers, this can cause trouble in team projects (See alternatives below).

ddev push ssh --skip-db

Use with caution Please create a backup beforehand and make sure your configuration is correctly set in .ddev/providers/ssh.yaml.

Nice, that's all!

Alternatives for deployment in team projects (via git):

Troubleshooting: Verify SSH connection in terminal

Verify that SSH connection works in general:

ssh user123@ssh.example.org
Use "exit" to end SSH connection for next test.

Verify that the "Path to WordPress" setting is correct:

ssh -t user123@ssh.example.org "cd /sites/my-website/wordpress; exec $SHELL --login"
Use "exit" to end SSH connection.