DDEV & WordPress (CLI) – clone a live site to your local laptop

Scared a wordpress update will break your live site? You just want to try out a few plugins without polluting your live database? With the help of DDEV and WP-CLI it became very easy to create a fresh local wordpress as well as clone an existing wordpress live site to your local computer (in a reliable and robust way).

DDEV is a local PHP development environment system using docker containers. One of the goals of the project is that developers don’t have to fiddle around with their local webserver configuration anymore. The configuration in DDEV is stored in a simple YAML-file and can be easily shared with colleagues via git. DDEV is available as Open Source, follow recommendation: twitter.com/randyfay

WP-CLI on the other hand is the command line interface for wordpress: „You can update plugins, configure multisite installations and much more, without using a web browser (wp-cli.org). WP-CLI is available as Open Source as well and it offers great automatization potential.

👨‍🔧 This article is work in progress. Maybe I’ll add a live example screencast with a green webspace provider such as Manitu or Uberspace later to show a real world example. Hope you’ll find it useful, have fun!

🌳 Some of the ninja dev skills described in this article were gained in projects at my current workplace gugler* MarkenSinn. If you’re looking for a sustainability-driven agency or ecological print products, check out gugler.at. Cheers to my colleagues!

♻️ Feel free to copy the content of this article, it is provided as CC0 Public Domain.

Inhaltsverzeichnis

A. Starter example: Create a blank wordpress project

First of all a little example of what can be achieved with WP-CLI and DDEV, see chapter B. for cloning an existing live site:

# Create new project folder
mkdir blank-wordpress
cd blank-wordpress/

# Setup DDEV project, you can also just use 'ddev config' (will ask questions)
ddev config --project-type=wordpress --docroot=web/ --create-docroot

# Fire it up
ddev start

# Jump into linux container
ddev ssh

# go to our docroot folder in DDEV linux (/var/www/html/web/)
cd web/

# Download latest wordpress
wp core download
# (Locale version via 
#  wp core download --locale=de_DE)

# Install wordpress, will ask for admin password
wp core install --url=https://blank-wordpress.ddev.site --title="Blank WordPress" --admin_user=admin --admin_email=admin@example.com --prompt=admin_password

# Check out the newly created sited
exit
ddev launch

# get list of installed themes, install plugin, create editor user
ddev ssh
cd web/
wp theme list
wp plugin install wordpress-seo --activate
wp user create bob bob@example.com --role=author

# change admin password in case you forgot
wp user update admin --prompt=user_pass

# Exit ssh terminal
exit

# Switch off ddev, save energy
ddev poweroff

# If your project is not needed anymore:
# delete DDEV project and database
ddev delete -O
# delete folder afterwards, e.g. rm -rf blank-wordpress

# Optional: If you don't want to jump in via ddev ssh, 
# you can use ddev exec to run commands inside of containers, e.g.
#   ddev exec "wp core version --path=web/"
# 
# Check out custom commands as well, nice feature 
# for sharing commands via git:
# https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/#container-commands

B. Clone a wordpress live site to local DDEV

Here is an example screencast of the following steps (manual download / backup approach), see following text for all details and commands:

1. Create DDEV project

Create a new project folder,

mkdir my-local-wordpress 
cd my-local-wordpress/

configure a new ddev project:

ddev config --project-type=wordpress --docroot=wordpress/ --create-docroot --webserver-type="apache-fpm" --php-version="7.4"


The current default setting for DDEV projects is MariaDB (10.3) and nginx as webserver. In the above example I used the Apache webserver instead of nginx (Permalink settings stored in .htaccess won’t work in nginx. If your live sites runs nginx, just remove –webserver-type flag. See ‚ddev help config‘ for full documentation.)

Hint: It’s best to adjust the DDEV configuration to match your live sites environment. This enables you to run realistic local tests and avoid compatibility issues while importing the database.

You can find out what your live environment is by checking Tools > Site Health Info.

If your live site runs on MySQL 5.7, you can easily switch to this as well:

ddev config --project-type=wordpress --docroot=wordpress/ --create-docroot --webserver-type="apache-fpm" --php-version="7.4" --mariadb-version="" --mysql-version="5.7"


Now we’re ready to start up the containers, run:

ddev start

In the following I describe some optional information for a better understanding DDEV, you can just skip to step 2.) to follow the tutorial.

Optional background information: ddev config

The command ddev-config creates your .ddev/config.yaml-file. You can change the values easily afterwards, just run ddev restart after changes.

With that you can also check if a given site built on PHP 5.6 is compatible with 7.4. Regarding database server changes, in some cases you need to delete and re-import your database though.For more details see Database Server Types and Changing webserver Type in DDEV docs.

name: my-local-wordpress
type: wordpress
docroot: wordpress/
php_version: "7.4"
webserver_type: apache-fpm
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
mariadb_version: "10.3"
mysql_version: ""
use_dns_when_possible: true
composer_version: ""
web_environment: []

Optional background information: project-type=wordpress

/**
#ddev-generated: Automatically generated WordPress settings file.
 ddev manages this file and may delete or overwrite the file unless this comment is removed.
 */

if (getenv('IS_DDEV_PROJECT') == 'true') {
  /** The name of the database for WordPress */
  define('DB_NAME', 'db');
  
  /** MySQL database username */
  define('DB_USER', 'db');
  
  /** MySQL database password */
  define('DB_PASSWORD', 'db');
  
  /** MySQL hostname */
  define('DB_HOST', 'db');

  /** WP_HOME URL */
  define('WP_HOME', 'https://blank-wordpress.ddev.site');
  
  /** WP_SITEURL location */
  define('WP_SITEURL', WP_HOME . '/');
}

/** Enable debug */
define('WP_DEBUG', true);


/** Define the database table prefix */
$table_prefix  = 'wp_';

The ddev config command with project-type=wordpress will create a wordpress/wp-config.php (will be overwritten later by our sync) and wordpress/wp-config-ddev.php (because the database access credentials for the local DDEV database are db,db,db,db). See the file on the left for the content of wp-config-ddev.php.

The wp-config-ddev.php file is included via wp-config.php, this will be overriden by our sync later:


// Include for settings managed by ddev.
$ddev_settings = dirname(__FILE__) . '/wp-config-ddev.php';
if (is_readable($ddev_settings) && !defined('DB_USER') && getenv('IS_DDEV_PROJECT') == 'true') {
  require_once($ddev_settings);
}

2. Download / sync the wordpress files of your live site to local

The following steps can either be done a) manually or b) fully automated via terminal commands if SSH is supported by your webspace provider. Most of the times SSH is not supported on regular / small webspaces.

a) The manual way (FTP or backup plugin)

If your webspace provider doesn’t support SSH/rsync (see step b)), you can connect to your site via FTPS tools like FileZilla, Cyberduck, etc. and just download your whole site into the wordpress/ directory of your local project.

The easy and fast way is to use a backup plugin such as BackWpUp (free / Open Source) to download your sites files a as .zip file instead of transferring all files individually. Just create a new backup job, set backup to folder as destination, run it and download the file:

The backup and download might take some time depending on how large your wordpress site is. Pro tip: Download the file via a FTP app if the download via browser failes.

Un-Zip the backup folder and move all files to the previously created folder my-local-wordpress/wordpress/, overwrite existing wp-config.php.

b) The efficient way (SSH / rsync)

If your webserver supports SSH and rsync (a file syncing tool, see command options here), you can easily pull / download your site with a single command (The advantage of rsync is, that if you want to re-sync again, only new files will be transferred. But most cheaper webspace providers don’t support SSH). We sync the remote folder of wordpress to our local DDEV folder (/var/www/html/wordpress). You need to add a trailing slash to the path retrieved from Site Health Info (see below):

ddev exec "rsync -avzh --delete --exclude 'wp-config-ddev.php' YOUR_SSH_USER@SSH_SERVER.DOMAIN:/html/wordpress/ /var/www/html/wordpress"

You will have to adjust the remote path (:/html/wordpress/) to match the path used by your webspace provider. You can get the path information by checking Tools > Site Health Info > Directories and Sizes.

Screenshot of Site Health, Directory and Sizes, WordPress directory location

Here is a real world example for Uberspace hosting, notice the trailing slash at the end of the path copied from Site Health Info:

ddev exec "rsync -avzh --delete --exclude 'wp-config-ddev.php' anmaeu@charon.uberspace.de:/var/www/virtual/UBERSPACE_USERNAME/html/matthias-andrasch.eu/blog/ /var/www/html/wordpress"

The sync might take some time depending on how large your wordpress site is.

3. Database: use ‚wp config set‘ / include wp-config-ddev.php

In wp-config.php we need to change the database settings to use the local DDEV database. The quickest way is to edit wp-config.php directly via these wp config commands:

ddev exec 'wp config set DB_NAME "db" --path=wordpress && wp config set DB_USER "db" --path=wordpress/ && wp config set DB_PASSWORD "db" --path=wordpress/ && wp config set DB_HOST "db" --path=wordpress/'

Optional: DDEV will always show the following notice if you use your own wp-config.php after re-starts, because automatically created wp-config.php of DDEV is overwritten by our sync. You can hide this message by putting „// wp-config-ddev.php not needed“ anywhere in wp-config.php (see info below).

An existing user-managed wp-config.php file has been detected!
Project ddev settings have been written to:

/Users/admin/webserver/my-local-wordpress/wordpress/wp-config-ddev.php

Please comment out any database connection settings in your wp-config.php and
add the following snippet to your wp-config.php, near the bottom of the file
and before the include of wp-settings.php:

// Include for ddev-managed settings in wp-config-ddev.php.
$ddev_settings = dirname(__FILE__) . '/wp-config-ddev.php';
if (is_readable($ddev_settings) && !defined('DB_USER')) {
  require_once($ddev_settings);
}

If you don't care about those settings, or config is managed in a .env
file, etc, then you can eliminate this message by putting a line that says
// wp-config-ddev.php not needed
in your wp-config.php

Optional: You can as well use the suggested method from the notice for setting the database connection. (If you find doing this on every sync odd, you can add the snippet to your live site, therefore it won’t get overwritten after a sync).

4. Dump the database of your live site and import it

a) The manual way (phpMyAdmin or backup plugin)

If your webspace provider has no support for mysqldump, just export your database with a tool like phpMyAdmin, backup plugins, etc. Download the exported .sql dump file to your project folder and import it with the following command.

If you used BackWPup in the step above, the .sql file will already be in your backup folder.

ddev import-db --src=mysqldump_file.sql

# If you used BackWpUp, .sql file should be already in wordpress/-folder
ddev import-db --src=wordpress/DB_NAME.sql

b) The automated way (SSH / mysqldump)

If your webspace provider supports SSH and mysqldump, you can dump, download and import your database with a single command. The command will ask for ssh password (if no SSH key is used) and afterwards for the database password of your live site:

ddev exec 'ssh YOUR_SSH_USER@SSH_SERVER.DOMAIN "mysqldump -u YOUR_DATABASE_USER -h YOUR_DB_HOST -p YOUR_DB_USER" > /var/www/html/mysqldump_remote.sql' && ddev import-db --src=mysqldump_remote.sql && ddev exec 'rm mysqldump_remote.sql'

# Uberspace real world example
ddev exec 'ssh UBERSPACE_USER@charon.uberspace.de "mysqldump -u UBERSPACE_USER -h localhost -p UBERSPACE_USER" > /var/www/html/mysqldump_remote.sql' && ddev import-db --src=mysqldump_remote.sql && ddev exec 'rm mysqldump_remote.sql'

5. Replace URLs in your local database

Because our local DDEV project has the URL https://my-local-wordpress.ddev.site, we’ll need to replace all URLs of the live website in our local database (that’s the annoying part about cloning / migrating a wordpress site). Good news: There is a WP-CLI command for this called wp search-replace:

ddev exec 'wp search-replace "YOUR_LIVE_WEBSITES.org" "my-local-wordpress.ddev.site" --path=wordpress/'

# Uberspace realworld-example
ddev exec 'wp search-replace "https://matthias-andrasch.eu" "https://my-local-wordpress.ddev.site" --path=wordpress/'

# If your blog was hosted in a subdirectory
ddev exec 'wp search-replace "https://matthias-andrasch.eu/blog" "https://my-local-wordpress.ddev.site" --path=wordpress/'

6. That’s it!

That’s all you need to do to get your live site locally running. You can just run to open your local site in a web browser:

ddev launch

Maybe you need to clear / disable caching plugins. If you run into server errors on subpages, it is always a good idea to save the permalink settings again (Settings > Permalinks).

Have fun developing / experimenting locally!

7. Multisite setup – more steps needed

For a multisite setup there are a few more steps needed. These depend on how your subsite domains are structured. The main difference is the use of –all-tables (I couldn’t get the other flags for multisites to work, therefore I just used this flag):

# For each multisite domain run
ddev exec 'wp search-replace "MY_FIRST_SUBSITE.org" "MY_FIRST_SUBSITE.ddev.site" --all-tables --path=wordpress/'
ddev exec 'wp search-replace "MY_SECOND_SUBSITE.org" "MY_SECOND_SUBSITE.ddev.site" --all-tables --path=wordpress/'

These domains need to be added as Additional Project Hostnames in .ddev/config.yaml:

name: mysite

additional_hostnames:
- "MY_FIRST_SUBSITE" # --> will become: MY_FIRST_MULTISITE.ddev.site
- "MY_SECOND_SUBSITE" 

Run ddev restart afterwards.

You’ll also need to edit wp-config.php and set your primary websites domain. We can use wp config command for this:

ddev exec 'wp config set DOMAIN_CURRENT_SITE "MY_FIRST_SUBSITE.ddev.site" --path=wordpress/'


TODO / unclear: You also need to edit the upload_path setting for all your sub sites (stored in database in wp1_options, wp2_options, etc.). This is maybe possible with WP-CLI site option command, haven’t figured it out yet Upload_path needs to be set to /var/www/html/wordpress/wp-content/uploads/.

C. TODOs 🤔

  • 🤖 The best way to automate all these steps would be to include these command line operations in DDEV Hosting Provider Integration and simply store the SSH_USER, paths, replacements, etc. in .ddev/config.yaml. There is already an example for rsync in the docs. A full automated sync could be done with the following command in future: ddev pull rsync
  • 🧑‍🎨 Also a nice GUI app like localwp.com would be a time saver and also attract new target groups for DDEV (see #2110 on GitHub)
  • 👀 An open question is how to preview locally developed sites for colleagues or customers, there is already ddev share, but it is complicated due the URL replacement needed (as far as I know).
  • 🌱 Sustainability: With help of rsync exclude features, data bandwidth could be saved if cache-, backup- and other extra folders won’t be transferred to local.
  • 🌱 Sustainability: Also if uploaded media files could be just referenced instead of all being downloaded, a lot of transfer size could be saved as well. As far as I know that is not easily possible unfortunately.
  • 📁 For multisites (and single sites?) the upload_path setting must also be updated, see notes in the step above.
  • ⚙️ Maybe a switch from Apache to nginx is not that bad, we just need to run some wp rewrite commands to update permalinks?
  • If wordpress is installed via composer (only possible via third party frameworks, not supported by core yet), transfer bandwith could maybe saved as well and it is easier to maintain wordpress via git. See e.g. Bedrock-framework example (I have no experience using this, it has serious drawbacks as well).

D. Bonus information

In a previous project I experimented with updraftplus Migration and CLI (premium). I also created a symlink-script for an easier usage of child themes in a git repository, see DDEV WP groundstation.

Update: After writing this post, I found github.com/fschmittlein/WordPress-Distribution by Frank Schmittlein, german slides are also available here: „DDEV – eine lokale Entwicklungsumgebung“.


🌱 Sustainability: If your work is finished, you can stop all DDEV projects with poweroff:

ddev poweroff

If you have improvements or suggestions, please let me know. 🙂

And while you’re here, please also check out sustainablewebdesign.org.

Leave a Comment

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.