(*this post focuses on the usecase of using drush within a DDEV hosted docker container, i.e. running ddev drush ... command, if you are using globally installed drush a portion of this post may not apply to you.)

TLDR;

STEP-1: Add localhost’s SSH private key to the ddev-ssh-agent container via running:

1
2
3
4
5
>  ddev auth ssh
		Adding 1 SSH private key(s)... 
        Adding key id_rsa
        Identity added: id_rsa (XXXX@YYYYYY.local)
        Successfully added 1 SSH private key(s).

STEP-2: Create file ``$PROJECT/drush/sites/self.site.yml` with the following content

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
live:
  host: example.com.au
  user: example
  root: /home/example/public_html/web
  uri: http://example.com.au
  ssh:
	options: "-p 2222"
local: 
  root: /var/www/html/web
  uri: https://ddev-example-website-local.ddev.site

STEP-3: Trail checking Drupal status via running:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
> ddev drush @live status   
  Drupal version   : 10.6.1                                               
  Site URI         : http://example.com.au                                    
  DB driver        : mysql                                                
  DB hostname      : localhost                                            
  DB port          : 3306                                                 
  DB username      : example_drupal                                         
  DB name          : example_drupal                                         
  Database         : Connected                                            
  Drupal bootstrap : Successful                                           
  Default theme    : example_theme                                                
  Admin theme      : gin                                                  
  PHP binary       : /opt/alt/php81/usr/bin/php                           
  PHP config       : /opt/alt/php81/etc/php.ini                           
  PHP OS           : Linux                                                
  PHP version      : 8.1.33                                               
  Drush script     : /home/example/public_html/vendor/bin/drush             
  Drush version    : 12.5.3.0                                             
  Drush temp       : /tmp                                                 
  Drush configs    : /home/example/public_html/vendor/drush/drush/drush.yml 
  Install profile  : standard                                             
  Drupal root      : /home/example/public_html       
  Site path        : sites/default                                        
  Files, Public    : sites/default/files                                  
  Files, Private   : sites/default/files/private                          
  Files, Temp      : sites/default/files/private/tmp                      
  Connection to example.com.au closed.

Preliminary Setup

Part-1: Setup SSH Public Key Authentication

  1. generate ssh key pair cd ~/.ssh && ssh-keygen -t rsa -f ~/.ssh/id_rsa_example, copy content in ~/.ssh/id_rsa_example.pub file for later

  2. add to target server: ssh example.com.au then cd ~/.ssh && vi authorised_keys, then paste the copied content from file ~/.ssh/id_rsa_example.pub in previous step

  3. trial ssh key connection via ddev ssh then ssh example@example.com.au -i ~/.ssh/id_rsa_example (add -p port-number if applies to you)

Part-2: Setup $PROJECT/drush/sites/self.site.yml File

  1. create emptu self.site.yml file under $PROJECT/drush/sites directory

  2. for each alias (development / stage / production) add an alias item in the self.site.yml file, for instance:

    1
    2
    3
    4
    5
    6
    7
    
    + live:
    +   host: example.com.au
    +   user: example
    +   root: /home/example/public_html/web
    +   uri: http://example.com.au
    +   ssh:
    +     options: "-p 2222 -i ~/.ssh/id_rsa_example"
    

    Note for parameters:

    • host : The fully-qualified domain name of the remote system hosting the Drupal instance.
    • user : The username to log in as when using ssh or docker. If you don’t declare it here, you can declare it in the ~/.ssh/config file in DDEV container (NOT in your host machine !!!!)
    • uri: The value of –uri should always be the same as when the site is being accessed from a web browser
    • ssh: Additional SSH options, here I’ve specified the flag and manually used certain private key for authentication

Part-3: Trial via ddev drush @alias-name status Command

  1. Run ddev drush @live status command, you should get something like the following:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    Drupal version   : 10.6.1                                               
    Site URI         : http://example.com.au                                    
    DB driver        : mysql                                                
    DB hostname      : localhost                                            
    DB port          : 3306                                                 
    DB username      : example_drupal                                         
    DB name          : example_drupal                                         
    Database         : Connected                                            
    Drupal bootstrap : Successful                                           
    Default theme    : example_theme                                                
    Admin theme      : gin                                                  
    PHP binary       : /opt/alt/php81/usr/bin/php                           
    PHP config       : /opt/alt/php81/etc/php.ini                           
    PHP OS           : Linux                                                
    PHP version      : 8.1.33                                               
    Drush script     : /home/example/public_html/vendor/bin/drush             
    Drush version    : 12.5.3.0                                             
    Drush temp       : /tmp                                                 
    Drush configs    : /home/example/public_html/vendor/drush/drush/drush.yml 
    Install profile  : standard                                             
    Drupal root      : /home/example/public_html       
    Site path        : sites/default                                        
    Files, Public    : sites/default/files                                  
    Files, Private   : sites/default/files/private                          
    Files, Temp      : sites/default/files/private/tmp                      
    Connection to example.com.au closed.
    
  2. If you are encountering error, please refer to the “Common Issue” section for resolution for various error.

Part-4: (optional) Alias for @Local Environment

If you would like the local environment to also have an alias, you can add the following to your self.site.yml file (only provide root and uri, do not specify host and user fields ):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
live:
  host: example.com.au
  user: example
  root: /home/example/public_html/web
  uri: http://example.com.au
  ssh:
    options: "-p 2222 -i ~/.ssh/id_rsa_example"
+ local:
+   root: /var/www/html/web
+   uri: https://ddev-example-website-local.ddev.site
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
> ddev drush @local status
    Drupal version   : 11.2.8                                               
    Site URI         : https://ddev-example-website-local.ddev.site                            
    DB driver        : mysql                                         
    DB hostname      : db                                            
    DB port          : 3306                                          
    DB username      : db                                            
    DB name          : db                                            
    Database         : Connected                                     
    Drupal bootstrap : Successful                                    
    Default theme    : olivero                                       
    Admin theme      : gin                                           
    PHP binary       : /usr/bin/php8.4                               
    PHP config       : /etc/php/8.4/cli/php.ini                      
    PHP OS           : Linux                                         
    PHP version      : 8.4.14                                        
    Drush script     : /var/www/html/vendor/bin/drush.php            
    Drush version    : 13.7.0.0                                      
    Drush temp       : /tmp                                          
    Drush configs    : /var/www/html/vendor/drush/drush/drush.yml    
    Install profile  : standard                                      
    Drupal root      : /var/www/html/web                             
    Site path        : sites/default                                 
    Files, Public    : sites/default/files                           
    Files, Temp      : /tmp                                          
    Drupal config    : sites/default/files/sync                        
  Connection to example.com.au closed.

Common Usage

Show Status

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
> ddev drush @live status   
  Drupal version   : 10.6.1                                               
  Site URI         : http://example.com.au                                    
  DB driver        : mysql                                                
  DB hostname      : localhost                                            
  DB port          : 3306                                                 
  DB username      : example_drupal                                         
  DB name          : example_drupal                                         
  Database         : Connected                                            
  Drupal bootstrap : Successful                                           
  Default theme    : example_theme                                                
  Admin theme      : gin                                                  
  PHP binary       : /opt/alt/php81/usr/bin/php                           
  PHP config       : /opt/alt/php81/etc/php.ini                           
  PHP OS           : Linux                                                
  PHP version      : 8.1.33                                               
  Drush script     : /home/example/public_html/vendor/bin/drush             
  Drush version    : 12.5.3.0                                             
  Drush temp       : /tmp                                                 
  Drush configs    : /home/example/public_html/vendor/drush/drush/drush.yml 
  Install profile  : standard                                             
  Drupal root      : /home/example/public_html       
  Site path        : sites/default                                        
  Files, Public    : sites/default/files                                  
  Files, Private   : sites/default/files/private                          
  Files, Temp      : sites/default/files/private/tmp                      
  Connection to example.com.au closed.

Clear Cache / Update Database

1
2
3
> ddev drush @live cache:rebuild
  [success] Cache rebuild complete.
  Connection to example.com.au closed.
1
2
3
> ddev drush @live updatedb
  [success] No pending updates.
  Connection to example.com.au closed.

Connect to Server via SSH (and Run Shell Command)

1
2
3
4
5
6
7
> ddev drush @live ssh
  [example@newvps web]$ cd ~
  [example@newvps web]$ ls -al | grep "public_html"
     drwxr-x--- 17 example  nobody       4096 Dec 24 09:05 public_html
     lrwxrwxrwx  1 example  example      11 Jan 17  2019 www -> public_html
  # (then press ctrl+d to exit)
  Connection to example.com.au closed.
1
2
3
4
> ddev drush @live ssh "cd ~ && ls -al | grep "public_html""
    drwxr-x--- 17 example  nobody     4096 Dec 24 09:05 public_htm
    rwxrwxrwx  1 example  example     11 Jan 17  2019 www -> public_html
  Connection to example.com.au closed.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
> ddev drush @live ssh "cd ~ && cd public_html && composer show --tree drupal/core"
	drupal/core 10.6.1 Drupal is an open source content management platform powering millions of websites and applications.
	|--asm89/stack-cors ^2.3
	|  |--php ^7.3|^8.0
	|  |--symfony/http-foundation ^5.3|^6|^7
	|  |  |--php >=8.1
	|  |  |--symfony/deprecation-contracts ^2.5|^3
	|  |  |  `--php >=8.1
	|--symfony/yaml ^6.4
	...
	...
	...
    |  |--php >=8.1
    |  |--symfony/deprecation-contracts ^2.5|^3
    |  |  `--php >=8.1
    |  `--symfony/polyfill-ctype ^1.8
    |     `--php >=7.2
    `--twig/twig ^3.22.0
       |--php >=8.1.0
       |--symfony/deprecation-contracts ^2.5|^3
       |  `--php >=8.1
       |--symfony/polyfill-ctype ^1.8
       |  `--php >=7.2
       `--symfony/polyfill-mbstring ^1.3
          |--ext-iconv *
          |  `--php >=7.2
          `--php >=7.2
  Connection to example.com.au closed.

Rsync Files

1
2
# Rsync Drupal root from Drush alias "live" to the alias "local"
> ddev drush rsync @live @local
1
2
3
# Rsync custom file path from @live to @local
#   (note that you need to use relative path, tilda (~) may cause unexpected error)
> ddev drush rsync @live:../../public_html/custom_path @local:custom_path 
1
2
3
4
# Rsync public files from @live to @local
#    (OR Rsync private files from @live to @local)
> ddev drush rsync @live:%public @stage:%public
> ddev drush rsync @live:%private @stage:%private
1
2
# Rsync Drupal root from the Drush alias dev to the alias stage, excluding all .sql files and delete all files on the destination that are no longer on the source.
> ddev drush rsync @dev @stage -- --exclude=*.sql --delete
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Synchronise (one-way) database from @source to @target
# (Equivalant to ddev drush sql-dump @source | drush @target sql-cli)

>   ddev drush sql:sync @source @target
    You will destroy data in db and replace with data from example.com.au/example_drupal.

     ┌ Do you really want to continue? ─────────────────────────────┐
     │ Yes                                                          │
     └──────────────────────────────────────────────────────────────┘

     [notice] Starting to dump database on source.
     [notice] Starting to discover temporary files directory on target.
     [notice] Copying dump file from source to target.

     // Copy new and override existing files at /tmp/example_drupal_20251230_010316.sql.gz. The source is                     
     // example@example.com.au:/home/example/drush-backups/example_drupal/20251230010316/example_drupal_20251230_010316.sql.gz?: yes.   

     [notice] Starting to import dump file onto target database.
1
2
3
4
# export database to file (@live VS @local)
> ddev drush @live ssh 
  mysqldump -u $username -p $database_name > file_name_database_export.sql
> ddev export-db -f "./file_name_database_export.sql.gz"
1
2
3
4
# import database from file (@live VS @local)
> ddev drush @live ssh
  mysql -u root -p database_name < file_name_database_export.sql
> ddev import-db --file=file_name_database_export
1
2
# Export config from @prod and transfer to @stage.
> ddev drush config:pull @prod @stage
1
2
# Export config from @prod and transfer to the vcs config directory of current site.
> ddev drush config:pull @prod @self --label=vcs
1
2
Export config to a custom directory. Relative paths are calculated from Drupal root.
> ddev drush config:pull @prod @self:../config/sync

Common Issue

Issue-1: SSH Authentication Issue when running ddev drush site-aliases

During my trial experiment, I get held back for a long time because the SSH key authentication kept failing, claiming the private key I provided via -i ~/.ssh/id_rsa_cpanel is not found (since I am using SSH public key to establish the connection)

1
2
3
4
5
6
7
>  ddev drush @live status --debug
    [preflight] Config paths: /var/www/html/vendor/drush/drush/drush.yml
    ...
    [debug] Redispatch hook status [0.42 sec, 3.15 MB]
    [info] Executing: ssh -t -p 2222 -i ~/.ssh/id_rsa example@example.com.au '/home/example/public_html/vendor/bin/drush status -vvv --uri=http://example.com.au' [0.43 sec, 3.29 MB]
   Warning: Identity file /home/user_name/.ssh/id_rsa_cpanel not accessible: No such file or directory.
   example@example.com.au's password: ...

I soon realised that since I am using ddev to run the drush command, it only have access to the files inside its own container (it has no access to the SSH keys in its host machine), hence in order for it to work, I need to copy the (already setup and working) SSH private keys in the localhost machine into the DDEV container:

  1. copy the SSH keys into the container using ddev auth ssh command

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    > cd ~/.ssh && ls
            config	
            id_rsa
            id_rsa.pub
            known_hosts
            known_hosts.old
    
    >  ddev auth ssh
    		Adding 1 SSH private key(s)... 
            Adding key id_rsa
            Identity added: id_rsa (XXXX@YYYYYY.local)
            Successfully added 1 SSH private key(s).
    
  2. (in fact this command is not physically copying the SSH key flies, but rather adding adding SSH key authentication to the ddev-ssh-agent container, hence no private key is required) edit your self.site.yml file to remove the -i ... flag

    1
    2
    3
    4
    5
    6
    7
    8
    
    live:
      host: example.com.au
      user: example
      root: /home/example/public_html/web
      uri: http://example.com.au
      ssh:
    -   options: "-p 2222 -i /path/to/ssh-key-in-host-machine" <-- unreachable from docker POV 
    +	options: "-p 2222"
    

(OR) Alternatively you can also generate a new SSH key and add it to the authorised_keys file in the target server:

  1. generate ssh key pair cd ~/.ssh && ssh-keygen -t rsa -f ~/.ssh/id_rsa_example, copy content in ~/.ssh/id_rsa_example.pub file for later

  2. add to target server: ssh example.com.au then cd ~/.ssh && vi authorised_keys, then paste the copied content from file ~/.ssh/id_rsa_example.pub in step-1

  3. finally edit your self.site.yml file:

    1
    2
    3
    4
    5
    6
    7
    8
    
    live:
      host: example.com.au
      user: example
      root: /home/example/public_html/web
      uri: http://example.com.au
      ssh:
    -   options: "-p 2222 -i /path/to/ssh-key-in-host-machine" <-- unreachable from docker POV 
    +   options: "-p 2222 -i ~/.ssh/id_rsa_example"
    

Issue-2: No such file or directory (Cannot find drush)

On my trial, the drush site:alias seems to keep ignoring my last bit of the path in root attribute, for instance when I set root:/home/example/public_html, it seems to always run ssh -t example@example.com.au '/home/example/vendor/bin/drush status --uri=http://example.com.au instead:

1
2
3
4
5
6
7
>  ddev drush @live status --debug
     [preflight] Config paths: /var/www/html/vendor/drush/drush/drush.yml
     ...
     [debug] Redispatch hook status [0.27 sec, 3.15 MB]
     [info] Executing: ssh -t -p 2222 -i ~/.ssh/id_rsa_cpanel example@example.com.au '/home/example/vendor/bin/drush status -vvv --uri=http://example.com.au' [0.28 sec, 3.29 MB]
   bash: /home/exampleit/vendor/bin/drush: No such file or directory
   Connection to example.com.au closed.

After some digging it is found that Drupal by default assumes your website to live inside a web folder and will escape from that folder level that contains the vendor/bin/drush file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# FILE: src/Drupa Finder/DrushDrupalFinder.php
<?php
public function getDrupalRoot()
{
    $core = InstalledVersions::getInstallPath('drupal/core');
    return $core ? Path::canonicalize(realpath(dirname($core))) : false;
    # ↑
    # dirname($core)       - Truncates/removes the last path segment (core), leaving the parent directory (e.g., /path/to/drupal/web)
    # realpath()           - Resolves any symlinks and relative paths to get the absolute path
    # Path::canonicalize() - Normalizes the path format for the current OS
}

To accommodate this, no matter the website lives inside the web folder or not, always put trailing web in your root file path :

1
2
3
4
5
6
7
8
live:
  host: example.com.au
  user: example
- root: /home/example/public_html
+ root: /home/example/public_html/web
  uri: http://example.com.au
  ssh:
    options: "-p 2222 -i ~/.ssh/id_rsa_example"

(via running drush @live status and you can find the actual website root path:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Drupal version   : 10.6.1                                               
Site URI         : http://example.com.au                                    
DB driver        : mysql                                                
DB hostname      : localhost                                            
DB port          : 3306                                                 
DB username      : example_drupal                                         
DB name          : example_drupal                                         
Database         : Connected                                            
Drupal bootstrap : Successful                                           
Default theme    : example_theme                                                
Admin theme      : gin                                                  
PHP binary       : /opt/alt/php81/usr/bin/php                           
PHP config       : /opt/alt/php81/etc/php.ini                           
PHP OS           : Linux                                                
PHP version      : 8.1.33                                               
Drush script     : /home/example/public_html/vendor/bin/drush             
Drush version    : 12.5.3.0                                             
Drush temp       : /tmp                                                 
Drush configs    : /home/example/public_html/vendor/drush/drush/drush.yml 
Install profile  : standard                                             
Drupal root      : /home/example/public_html        <-------[HERE]                           
Site path        : sites/default                                        
Files, Public    : sites/default/files                                  
Files, Private   : sites/default/files/private                          
Files, Temp      : sites/default/files/private/tmp                      
Connection to example.com.au closed.

Issue-3: rsync command not found

When running rsync command (OR sql:sync command, that uses rsync for transferring files), if you ever see error like the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ddev drush rsync @live:~/public_html @local

  Copy new and override existing files at /var/www/html/web. The source is example@example.com.au:/home/example/public_html/web/ 
  Yes                                                                                                                     
 └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

> bash: rsync: command not found
> rsync: connection unexpectedly closed (0 bytes received so far) [Receiver]
> rsync error: error in rsync protocol data stream (code 12) at io.c(232) [Receiver=3.2.7]

In RsyncCommands.php line 84:
                                                                                                        
  Could not rsync from example@example.com.au:/home/example/public_html/web/~/public_html to /var/www/html/web  
                                                                                                        

Failed to run drush rsync @live:~/public_html @local: exit status 1

This is likely caused by one of your alias (either @live or @local in this instance) does not have the rsync command line utility installed, you can verify this via running:

1
2
3
4
> ddev drush @local ssh "which rsync"
  /usr/bin/rsync
> ddev drush @live ssh "which rsync"
  /usr/bin/which: no rsync in (/usr/local/cpanel/3rdparty/lib/path-bin:/usr/share/Modules/bin:/usr/local/cpanel/3rdparty/lib/path-bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin)

Reference

  • Drush Documentation - site:aliases: link

  • Drush Documentation - site:ssh: link

  • Drush Documentation - sql:sync: link

    • so you can synchronise local/stage/production databases via drush sql:sync @source @target (it uses rsync to transfer the files between different environment)
    • (equivalent to running drush sql-dump @source | drush @target sql-cli)
  • Drush Documentation - core:rsync: link

    • so you can synchronise local/stage/production files via drush rsync @source @target
  • Drush Documentation- config:pull: link