How Trellis’s database-pull Playbook Works (And What We Fixed)

One of the most useful Trellis commands in day-to-day WordPress development is trellis db pull. It syncs your production (or staging) database down to local — switching URLs automatically so your dev site works immediately. But the playbook behind it does more than most developers realise, and the default Trellis version had some issues we needed to fix before it worked reliably.

This post walks through exactly what database-pull.yml does, the safety feature hidden in the middle of it, and the two patches we made to get it working correctly with a Bedrock project.

What the Playbook Does

Running trellis db pull production executes database-pull.yml against your production host. Here’s the sequence:

1. Pre-flight validation

Before touching any database, the playbook checks three things:

- name: Abort if environment variable is equal to development
  fail:
    msg: "ERROR: development is not a valid environment for this mode..."
  when: env == "development"

You cannot pull from development to development — a useful guard if you ever mis-type the environment flag.

It also checks that your local project folder exists:

- name: Check if Jekyll::Drops::SiteDrop local folder exists
  delegate_to: localhost
  stat:
    path: ""
  register: result
  become: no

If the path doesn’t exist the playbook aborts with a clear message rather than partially running and leaving things in an inconsistent state.

2. Create local backup directory

- name: Create local database_backup directory if it doesn't exist
  delegate_to: localhost
  file:
    path: "/database_backup"
    state: directory
    mode: 0755
  become: no

On first run this creates a database_backup/ folder inside your local Bedrock project root. All backups end up here.

3. Dump and transfer the remote database

- name: Create database dump on 
  shell: wp db export --allow-root - | gzip > 
  args:
    chdir: ""

The dump is created on the remote server, piped through gzip. Then it’s fetched to local via Ansible’s fetch module:

- name: Pull database dump from  to development
  fetch:
    src: "/"
    dest: "/"
    flat: yes

After the transfer the remote dump is deleted immediately — no production files left hanging around on the server.

4. The hidden safety feature: backup local first

This is the part most developers don’t expect. Before importing the production dump, the playbook backs up your current local database:

- name: Export development database before importing dump (backup)
  delegate_to: localhost
  shell: wp db export - | gzip > database_backup/
  args:
    chdir: "/web/wp"
  become: no

The backup filename includes a timestamp:

imagewize_com_development_2026_02_27_10_30_45.sql.gz

So if the import causes any problems — or you realise after the fact that you needed that local data — you can restore it from database_backup/. Every pull creates a new timestamped backup, meaning you accumulate a history of your local database states. Worth clearing out periodically.

5. Import and search-replace

- name: Import database dump on development
  delegate_to: localhost
  shell: gzip -c -d  | wp db import -
  args:
    chdir: "/web/wp"
  become: no

Then the URL swap:

- name: Search for  and replace with  on development
  delegate_to: localhost
  command: wp search-replace '//' '//' --allow-root --all-tables --precise

Both url_from (production) and url_to (local) are resolved from config automatically. After this step your local WordPress is fully functional with production content.


What We Fixed

The version of database-pull.yml we started with had two problems.

Fix 1: Broken hostvars references

The original playbook resolved local site config like this:

host: "_host"
from_host: ""
url_from: ""
url_to: ""
local_bedrock_dir: ""

It was trying to read the development site config via hostvars.development_host, which required that host to be in the play’s host inventory. Since the playbook only targets web:& (the remote server), development_host wasn’t always populated at the right time. This caused intermittent failures that were hard to reproduce.

The fix was to load the development config directly using vars_files and a file lookup:

vars_files:
  - group_vars/development/wordpress_sites.yml

vars:
  url_from: ""
  dev_wordpress_sites: ""
  url_to: ""
  project_local_path: ""

Reading the file directly is more reliable than relying on hostvars being populated for a host that isn’t part of the current play.

Fix 2: Wrong delegate_to target

The original used delegate_to: development_host for local tasks:

- name: Create database_backup directory if it doesn't exist
  delegate_to: development_host
  file:
    path: "/database_backup"
    ...

Two problems here: development_host has the same availability issue as above, and the path was using project_web_dir (the remote path) instead of the local Bedrock path.

Replacing with delegate_to: localhost and correcting the path:

- name: Create local database_backup directory if it doesn't exist
  delegate_to: localhost
  file:
    path: "/database_backup"
    state: directory
    mode: 0755
  become: no

The become: no is also important — local tasks shouldn’t run as root.


Using It

Once the playbook is correct, pulling production to local is a single command run from the Trellis directory:

cd trellis
trellis db pull production

For a Bedrock project the WP path is in the web/wp subdirectory, which the playbook handles automatically via the chdir arguments.

If you want to run just the search-replace step again (useful if something went wrong):

trellis db pull production --tags search-replace

Takeaway

The Trellis database-pull playbook is well-designed, but the default version had brittle hostvars lookups that could fail depending on how hosts were configured. Switching to direct file lookups and delegate_to: localhost made it deterministic.

The built-in local backup before import is a genuinely useful safety net that’s easy to miss since it’s buried in the middle of the playbook. Your database_backup/ directory quietly accumulates timestamped snapshots of every pull — worth knowing about before you hit a situation where you need it.

This is part of the Trellis-based WordPress deployment workflow we use at Imagewize for client projects. If you’re running a Trellis stack and want help setting up or debugging your deployment pipeline, get in touch.


Questions or issues with your Trellis setup? Find me on Mastodon at @jfrumau@mastodon.social.