A Complete Drupal Workflow: Local to Live Deployments on Shared Hosting Using DDEV and a Simple Deploy Script

Objective

In this tutorial, you’ll learn how to build a complete and reliable Drupal 10/11 development workflow that works even on a standard shared hosting account (like DreamHost Shared Unlimited).

Shared hosting environments are limited — you don’t get root access, Composer can be slow or break, and managing multiple environments (local → staging → production) can feel complicated.

But with the right tools, you can still create a professional and efficient workflow that is easy to maintain and doesn’t rely on complex server setups.

To make this possible, we’ll use:

  • DDEV to create a fully isolated and repeatable local environment for development.
  • A lightweight Bash deploy script to safely push code and configuration changes to your DEV and PROD websites on shared hosting.

With just a few commands, you’ll be able to:

  1. Develop new features locally in DDEV
  2. Push your changes to your DEV site for testing
  3. Deploy those same changes to your live site with the exact same script

By the end of this tutorial, you’ll have a streamlined, safe, and easy-to-use Drupal workflow that lets you deploy configuration changes, modules, themes, and new features from your local machine to your DEV and PROD environments without stress or guesswork.

Prerequisites

Before getting started, make sure you have:

✔ DDEV and Docker installed

These tools provide you with a complete local Drupal environment (PHP, MySQL, web server, mailhog, etc.) inside Docker containers — without installing anything directly on your computer.

Docker Installation Guide
https://docs.docker.com/get-started/get-docker

DDEV Installation Guide
https://docs.ddev.com/en/stable/users/install/ddev-installation/

✔ A Linux-based terminal environment

If you’re using Windows, you’ll need WSL (Windows Subsystem for Linux), which lets you run Linux commands and tools directly in Windows.

Microsoft WSL Installation Guide:
How to Install Linux on Windows with WSL

If you want a detailed walkthrough of setting up Drupal locally on Windows with DDEV, you can follow my dedicated step-by-step tutorial:

Quick & Easy Drupal 10/11 Dev Environment on Windows with DDEV & WSL2

✔ A shared hosting account

This tutorial uses DreamHost Shared Unlimited, but any typical Linux-based shared hosting service should work. You need the ability to:

  • Create subdomains
  • Create MySQL databases
  • Enable SSH access
  • Clone a Git repo

DreamHost documentation for reference:

DreamHost Shared Unlimited
https://help.dreamhost.com/hc/en-us/articles/34357904873108-Shared-Unlimited-hosting-overview


Setting Up Your Local Drupal 10/11 Dev Environment with Docker and DDEV

Setting up Drupal locally used to be complicated — you had to install PHP, MySQL, Apache, and configure everything manually. With Docker and DDEV, all of this becomes almost automatic.

In just a few minutes, you’ll have a complete, isolated Drupal environment that behaves exactly like a real server.

Let’s walk through the setup step by step.

Step 1: Create a New Drupal Project with DDEV

Open your Linux terminal and create a new directory for your project.

For this tutorial, we’ll call the project drupal:

mkdir drupal && cd drupal

Now initialize the project with DDEV:

ddev config --project-type=drupal10 --docroot=web

This command tells DDEV:

  • We’re building a Drupal 10/11 project
  • The Drupal installation will live inside a folder named web
  • DDEV should generate all required configuration files

DDEV creates a .ddev folder where it stores its settings.

Step 2: Start the DDEV Environment

Start DDEV with:

ddev start

DDEV will automatically:

  • Start the necessary Docker containers (PHP, Apache, MySQL, Mailhog, etc.)
  • Set up SSL certificates for local HTTPS
  • Generate a nice local URL, for example:

When the setup finishes, DDEV will show a success message letting you know your environment is ready.

Step 3: Install Drupal

Next, we’ll download and install Drupal using Composer and Drush.

Download Drupal with Composer:

ddev composer create-project "drupal/recommended-project" .

Install Drush:

ddev composer require drush/drush

Install Drupal with Drush:

ddev drush site:install --account-name=admin --account-pass=securepassword

You now have a fully installed Drupal site inside your DDEV environment.

Open it in your browser: https://drupal.ddev.site

Step 4 (Optional): Install Helpful Development Modules

These modules aren’t required, but they make development easier and faster — especially when testing themes, layouts, and custom functionality.

Devel Module

The Devel module provides useful developer tools like service inspection and dummy content generators.

Install and enable Devel:

composer require 'drupal/devel:^5.4'
ddev drush en devel

List all available services:

ddev drush devel:services

Generate Dummy Content

The Devel Generate submodule lets you instantly create users, taxonomy terms, and nodes — perfect for testing themes or views.

Enable the submodule:

ddev drush en devel_generate

Create sample data:

ddev drush devel-generate-users 10
ddev drush devel-generate-terms 20 --bundles=tags --max-depth=1
ddev drush devel-generate-content 25

Your site will now contain realistic test content (articles, users, tags, etc.) that you can use while building and testing.

Admin Toolbar Module

The Admin Toolbar module replaces the default Drupal toolbar with a much more usable dropdown menu, making the admin area faster to navigate.

Install and enable it:

ddev composer require 'drupal/admin_toolbar:^3.6'
ddev drush en admin_toolbar
ddev drush cr

Step 5: Log In as Admin

If you ever get logged out or want a quick way to access the site as an administrator, you can generate a one-time login link:

ddev drush uli

This prints a secure login URL in your terminal. Open the link in your browser and you’ll be logged in immediately.

Once inside, it’s a good idea to set your permanent admin username and password.


Initialize a New Git Repository to Start Tracking Your Code Changes

Now that your local Drupal site is up and running, it’s time to start tracking your work with Git.

Version control is essential — it lets you save progress, experiment safely, roll back mistakes, and deploy cleanly to your remote environments.

Let’s set up your Git repository step by step.

Create a New Repository on GitHub (or GitLab)

  • Go to https://github.com/new.
  • Choose a name for your project — for example drupal.
  • You can also add an optional description and choose whether the project should be private or public.

Important:
Leave the following unchecked/default:

  • README
  • .gitignore
  • License

We’ll add these manually later to make sure everything is configured correctly.

Use SSH Instead of HTTPS

When you connect to GitHub using HTTPS, you need to type your credentials or personal access token every time you push code.

Using SSH keys eliminates this hassle — once the key is added, GitHub automatically trusts your device.

If you don’t already have SSH keys set up, follow this guide:

Connecting to GitHub with SSH
https://docs.github.com/en/authentication/connecting-to-github-with-ssh

Once SSH is ready, you’ll clone and push code using URLs like this:

git@github.com:yourusername/drupal.git

Create a .gitignore File

A .gitignore file tells Git which files and folders should not be tracked.

This prevents:

  • Auto-generated files
  • Temporary caches
  • Local environment settings
  • Sensitive info
  • Large folders like /vendor from cluttering your repository.

Move into your project folder:

cd drupal

Create a .gitignore using your preferred editor:

nano .gitignore

Paste the following content (this is based on the official Drupal example .gitignore, plus some extra useful rules):

# Files and directories suggested to be ignored by Drupal
# @see https://git.drupalcode.org/project/drupal/-/blob/10.0.x/example.gitignore
/web/core
/vendor/
/web/sites/*/settings.*.php
/web/sites/*/services*.yml
/web/sites/*/files
/web/sites/*/private
/web/sites/simpletest

# Directories specific to this template
/web/libraries
/web/modules/contrib
/web/profiles/contrib
/web/themes/contrib

# OS X
.DS_Store
.AppleDouble
.LSOverride
._*
.Spotlight-V100
.Trashes

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/

# Eclipse
*.pydevproject
.project
.metadata
tmp/**
tmp/**/*
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
*.launch
.externalToolBuilders/

# CDT / PDT
.cproject
.buildpathk

# Emacs
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*

# IntelliJ / PhpStorm
.idea/
atlassian-ide-plugin.xml

# XHProf
xhprof_*

# Sass
.sass-cache
*.css.map

# Netbeans IDE
nbproject
nbproject/*

# DrupalVM
.vagrant/

# DevDesktop
*.dd

# NPM
npm-debug.log

# Drush 9 Checksums
drush/sites/.checksums

# Deprecation detector rules
.rules

# PHPCS
.phpcs-cache

# PHPUnit
.phpunit.result.cache

# Drupal check report.
drupal-check-report.xml

# Local files
local.settings.php
local.drush.yml
local.site.yml
local.services.yml
*.local
local.blt.yml

# oauth keys
oauth_keys/

# env-file
/.env*

# .htaccess
.htaccess

This ensures that only the files you intend to track are committed.

Save and close the file.

Initialize Git and Push Your Code to GitHub

Now let’s set up Git locally and push your first commit.

1. Initialize Git

git init

2. Rename the Default Branch

Most modern Git platforms use main instead of master:

git branch -m main

3. Add All Project Files

git add .

4. Commit Your Code

git commit -m "Initial commit"

5. Add Your Remote Repository (GitHub)

Replace the username and repo name with your own:

git remote add origin git@github.com:astralmemories/drupal.git

6. Push Your Code

git push -u origin main

Verify Your Initial Commit

Visit your GitHub repository in your browser.

You should now see your full Drupal project — minus all the ignored directories listed in .gitignore.

Your project is now safely stored in GitHub and ready for deployments, branching, and all the workflow automation we’ll build in the next sections.


Set Up a Dev Branch for Your Drupal Project in Git

When working on a Drupal site (or any web project), it’s a good idea to organize your code into separate Git branches.

This gives you a safe place to test changes, try new modules, and experiment without risking your live site.

The most common setup is:

  • main → your production (live) code
  • dev → your development/testing branch

This workflow helps you keep things stable in production while still allowing active development.

Let’s set this up using Git and GitHub.

Step 1: Create and Push the dev Branch

From the root of your project, first make sure you’re on the main branch:

git checkout main

Now create a new branch called dev:

git checkout -b dev

Push it to GitHub and link it to the remote:

git push -u origin dev

If you run:

git branch

You should now see:

* dev
  main

You now have two branches — one for stable production code and one for active development.

Step 2 (Optional): Clone the Repository and Work With Both Branches Locally

If you or another developer wants to clone the repository and work with both branches locally, here’s the recommended workflow:

Clone the project:

git clone git@github.com:astralmemories/drupal.git
cd drupal

Fetch all branches from GitHub:

git fetch origin

Create local branches that track the remote ones:

git checkout -b main origin/main
git checkout -b dev origin/dev

Now both your main and dev branches are properly connected to their remote versions.

When working on new features or updates, switch to the dev branch:

git checkout dev

And push your changes when ready:

git push origin dev

Why This Branching Strategy Works Well for Drupal

Here are the benefits of using a main and dev branch setup:

✔ Safe testing

You can try new modules, configuration splits, theme changes, and updates on the dev branch without touching your live website.

✔ Cleaner collaboration

If you’re working with other developers, everyone can use the dev branch for active work while keeping main stable.

✔ Predictable deployments

You merge into main only when everything on dev has been tested — giving you a reliable and repeatable deployment workflow.


Create and Configure Your LIVE and DEV Drupal Websites on Shared Hosting

In this section, we’ll set up the LIVE (production) and DEV (testing) versions of your Drupal site on your shared hosting account.

I’ll be using DreamHost Shared Unlimited as the example platform, but don’t worry — the process is nearly identical on most shared hosting providers (Hostinger, SiteGround, Bluehost, Namecheap, etc.). The names of the menus may differ slightly, but the steps are the same.

By the end of this section, you will have:

  • A LIVE subdomain ready to host your production Drupal site
  • A DEV subdomain ready for testing and staging
  • Both sites fully provisioned and ready to receive code from your local DDEV setup

For this tutorial, I’ll be using the following subdomains:

  • drupal.astralmemories.com → LIVE / Production
  • drupal-dev.astralmemories.com → DEV / Testing

You’ll repeat the same steps for both subdomains.

Create a New Domain or Subdomain

Login to your DreamHost panel and go to: Websites → Manage Websites → “+ Add Website”

DreamHost gives you three options when assigning a domain to your new Drupal site:

  1. Use a domain or subdomain you already own
  2. Create a new subdomain
  3. Register a new domain

For this tutorial, we’ll choose Create a subdomain.

On the “Create a subdomain” page:

  • Enter the subdomain name (for example, drupal)
  • Choose the parent domain (for example, astralmemories.com)

Then click “Next: Hosting”.

On the next screen, DreamHost will ask which hosting plan you want to connect this domain to.

Choose your shared hosting plan (Shared Unlimited), then click “Set Up Website”.

DreamHost will now create the folder structure, configure DNS, and prepare the environment for your new subdomain.

Wait for the process to finish.

You’ve just created the subdomain for your LIVE/production site.

You will later repeat this same process to create your DEV/testing subdomain (drupal-dev.astralmemories.com).

Add an SSL Certificate to Your Domain/Subdomain (Optional but Recommended)

While SSL isn’t strictly required at this stage, it’s always a good idea to secure your website — even during development.

DreamHost’s Shared Unlimited plan provides free SSL certificates through Let’s Encrypt.

Once your subdomain finishes provisioning:

  • Click the ••• menu next to the new domain
  • Select SSL Security

On the next screen, under Free Let’s Encrypt SSL Certificate, click “Select this Certificate”.

DreamHost will issue the certificate. This usually takes around 5–15 minutes. Once active, all traffic to your site will automatically redirect to HTTPS.

Changing Your Domain’s Username (Optional)

DreamHost allows you to assign each domain/subdomain to a specific user account.

This is optional, but it can help you:

  • Keep websites organized
  • Separate permissions
  • Use different SSH users for different projects

To change the assigned user:

  1. Go to Manage Websites
  2. Click Manage next to the domain
  3. Then click Switch user

From the dropdown list, choose the user you want to assign to this domain.

Click “Save Changes”.

DreamHost will update file ownership and directory structure.

Wait until the process completes before making further changes.

Repeat These Steps for Your DEV Environment

You have now finished creating and configuring the LIVE website:

  • drupal.astralmemories.com

Next, repeat the exact same steps to create the DEV version of your site.

For this tutorial, we’ll use:

  • drupal-dev.astralmemories.com

This DEV site will be used for:

  • Testing module updates
  • Trying new features
  • Importing configuration changes
  • Checking deployments before going live

Once both subdomains are created and configured, you’re ready to prepare their databases and connect them to your codebase.


Database Creation and Domain Directory Configuration for LIVE and DEV

Now that your LIVE and DEV subdomains are set up, the next step is to create a separate MySQL database for each site and point each domain to the correct Drupal webroot.

Having separate databases ensures that:

  • Your DEV site can safely test changes
  • Your LIVE site stays stable and protected
  • Each environment stores its own content, users, and configuration

Let’s start by creating the database for your LIVE site.

Create the Database for the LIVE Drupal Site

Log in to your DreamHost panel. Navigate to “Websites → MySQL Databases”. Under “Create a New MySQL Database”, fill out the fields as follows:

  • Database name: drupal_database
  • Use Hostname: Create a new hostname now…
  • New Hostname: mysql.drupal.astralmemories.com
  • First User: Create a new user now…
    • New Username: YOUR_USERNAME
  • New Password: CREATE A SECURE PASSWORD
  • New Password Again: SAME SECURE PASSWORD

Click “Add new database now!”.

Once DreamHost creates the database and MySQL hostname, your LIVE environment will be ready to connect to Drupal.

Configure the Domain Directory for the LIVE Site

Every new domain or subdomain starts with its own default folder. We need to point your LIVE domain to the web directory inside your Drupal project. This ensures that when someone visits https://drupal.astralmemories.com, the server loads:

drupal.astralmemories.com/web

To update the path:

  1. Navigate to Websites → Manage Websites
  2. Find your LIVE domain and click Manage
  1. Navigate to Additional Settings
  2. Click the Paths tab
  3. In Web directory, enter:
drupal.astralmemories.com/web

This tells DreamHost exactly where your Drupal site lives.

Repeat These Steps for the DEV Drupal Site

Now we’ll repeat the process for your testing/staging environment.

Create the Database for the DEV Site

Go to Websites → MySQL Databases → Create a New MySQL Database.

Fill in the fields:

  • Database name: drupal_dev_database
  • Use Hostname: Create a new hostname now…
  • New Hostname: mysql.drupal-dev.astralmemories.com
  • First User: Create a new user now…
    • New Username: YOUR_USERNAME
  • New Password: CREATE A SECURE PASSWORD
  • New Password Again: SAME SECURE PASSWORD

Click “Add new database now!”.

This creates a completely separate MySQL environment specifically for your DEV site.

Configure the Domain Directory for the DEV Site

Just like the LIVE domain, you need to point your DEV domain to its Drupal web folder.

  1. Go to Websites → Manage Websites
  2. Click Manage next to drupal-dev.astralmemories.com
  3. Navigate to Additional Settings → Paths
  4. Enter:
drupal-dev.astralmemories.com/web

Your DEV and LIVE sites are now completely isolated:

  • Each has its own subdomain
  • Each has its own database
  • Each points to its own Drupal installation directory

This separation is what allows you to safely test changes in DEV before deploying to LIVE.


Match the Same PHP Version Between Your Local, DEV, and LIVE Websites

To avoid unexpected errors, it’s important that all of your environments use the same PHP version (or at least very close). Drupal core, contributed modules, and Drush can behave differently depending on the PHP version—so keeping everything aligned helps ensure that your code works the same everywhere.

In this step, we’ll check which PHP version your local DDEV site is using, and then configure your DEV and LIVE sites on shared hosting to match it.

Check Your PHP Version in DDEV

Inside your local Drupal project folder, run:

ddev drush status

Look for the “PHP version” line.

Here is an example output:

Drupal version   : 11.2.5
Site URI         : https://drupal.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      : claro
PHP binary       : /usr/bin/php8.3
PHP config       : /etc/php/8.3/cli/php.ini
PHP OS           : Linux
PHP version      : 8.3.21
Drush script     : /var/www/html/vendor/bin/drush.php
Drush version    : 13.6.2.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

In this example, the local environment is running PHP 8.3.21.

You can also see your PHP version in your Drupal admin UI:

Administration → Reports → Status report

(Located under “General System Information”)

Match the PHP Version on Your DEV and LIVE Websites

Now that you know your local PHP version, the next step is to configure your shared hosting account so that both your DEV and LIVE Drupal sites use the same version.

Using the same PHP version everywhere prevents issues such as:

  • Modules working locally but failing on the server
  • Drush commands behaving differently
  • Random errors caused by missing PHP extensions
  • Inconsistent behavior between environments

Change the PHP Version in DreamHost

On DreamHost, you can easily change the PHP version used by each domain or subdomain.

Follow these steps for both:

  • drupal.astralmemories.com (LIVE)
  • drupal-dev.astralmemories.com (DEV)

Steps:

  1. Go to Manage Websites in the DreamHost panel.
  2. Click Manage next to the domain/subdomain you want to update.
    • In Grid view, the button appears at the bottom of the tile.
    • In List view, it appears on the far right.
  3. Click the Settings tab.
  4. Find the PHP section and click the Modify icon.
  5. Choose the appropriate PHP version from the dropdown menu (make sure it matches your DDEV PHP version).
  6. Click Change PHP Version.

DreamHost will update the version within a minute or two.

DreamHost’s official documentation on this step:

Change the PHP version of a site
https://help.dreamhost.com/hc/en-us/articles/214895317-Change-the-PHP-version-of-a-site

Make Sure All Environments Match

After updating both subdomains, double-check that:

  • Local DDEV
  • DEV subdomain
  • LIVE subdomain

are all running the same (or very close) PHP version—for example:

PHP 8.3.21

This gives you consistency across all environments and prevents unexpected issues during deployment.


Make Sure You Can Connect to Your Shared Hosting Server via SSH

Before we continue configuring your DEV and LIVE Drupal sites, it’s important to verify that you can log in to your shared hosting server using SSH.

We need SSH access because you will:

  • Configure the PHP version used by the shell/CLI
  • Configure Drush inside each environment
  • Run commands that manage your site from the terminal

This ensures the command-line tools on your server (Drush, PHP CLI, Composer, etc.) behave the same way as your local DDEV environment.

Why SSH Is Important for This Workflow

Even though your DEV and LIVE websites already use the correct PHP version for web traffic (via Apache/Nginx), the shell can still be using a completely different PHP version.

For example:

  • Website may run PHP 8.3
  • Server shell may still use PHP 8.1

This mismatch may cause issues when running Drush or other Drupal-related commands directly on the server.

To avoid errors during deployment, configuration imports, or cron runs, we need everything aligned.

1. Configure SSH Access to Your Shared Hosting Account

Each hosting company has its own process for enabling SSH access. If you’re on DreamHost, follow their guide:

Logging into your DreamPress site via SSH
https://help.dreamhost.com/hc/en-us/articles/360001231463-Logging-into-your-DreamPress-site-via-SSH

Once SSH is enabled, you can log into your server from your terminal.

2. Connect to Your Shared Hosting Server

Open your terminal and connect:

ssh useraccount@yourdomain.com

Replace useraccount and yourdomain.com with your actual hosting credentials.

If the connection is successful, you’ll land inside your hosting user’s home directory.

Run:

ls

You should see your DEV and LIVE site folders, something like:

ls

...
drupal-dev.yourdomain.com/
drupal.yourdomain.com/
other-site-folder/
...

This confirms you’re inside the correct server environment.

3. Check the PHP Version Used by the Shell

Now check the PHP version your server’s CLI is using:

php -v

Example output:

PHP 8.1.31 (cli) (built: Nov 21 2024 20:15:37) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.31, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.31, Copyright (c), by Zend Technologies

If the version matches your Local DDEV website PHP version, you’re done.

If not, you’ll need to change it.

4. Change the PHP Version the Shell Uses

DreamHost has a dedicated guide for this step:
https://help.dreamhost.com/hc/en-us/articles/214202148-Change-the-PHP-version-the-shell-uses

Here’s the short version:

Go to Your User’s Home Directory

Run:

cd ~

Edit the .bash_profile File

Open it with nano:

nano .bash_profile

If the file doesn’t exist, nano will create it.

Add the PHP 8.3 Path

Add this line at the end of the file:

export PATH=/usr/local/php83/bin:$PATH

This forces the shell to use PHP 8.3 instead of an older version.

Save and exit:

  • Press CTRL + O, then Enter to save
  • Press CTRL + X to exit

Reload the Profile

Run:

. ~/.bash_profile

This applies your changes immediately.

Confirm the New PHP Version

Now check again:

php -v

You should see something like:

useraccount@iad1-shared-b7-33:~$ php -v
PHP 8.3.14 (cli) (built: Nov 21 2024 20:15:39) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.14, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.14, Copyright (c), by Zend Technologies
useraccount@iad1-shared-b7-33:~$

This means your shell is now using PHP 8.3 — matching your local DDEV, LIVE site, and DEV site.

Why This Matters

By aligning the PHP CLI version across local, DEV, and LIVE:

  • Drush commands run consistently everywhere
  • Configuration imports/export behave the same
  • Deployment scripts work reliably
  • You avoid strange server-side warnings or fatal errors

This creates a smooth foundation for the deployment workflow we’ll build later.


Install Composer Globally (Optional)

Composer is the tool Drupal uses to manage all dependencies (core, modules, themes, libraries). You already have Composer available locally through DDEV (ddev composer). However, you can also install Composer on your shared hosting account if you want to manage dependencies directly on your server.

This is optional, and later in this tutorial you’ll see why using Composer on shared hosting is often not ideal.

Why Composer on Shared Hosting Is Optional

Many developers initially think they’ll install modules or update Drupal directly on the server using Composer. That’s possible on VPS or dedicated servers, but shared hosting environments (like DreamHost’s Shared Unlimited plan) have important limitations:

  • Low memory: Composer often needs more memory than shared hosting allows.
  • Low CPU: Composer commands can take too long and fail.
  • Restricted permissions: Some operations require access that shared hosting doesn’t provide.

Because of this, Composer commands often fail during install, update, or dependency resolution.

The better solution

You’ll run Composer locally inside DDEV—where everything is fast and reliable—and then deploy the resulting code (including the vendor folder) to your DEV and LIVE sites.

We’ll build this workflow later in the tutorial.

Still, if you’d like Composer installed for light usage (such as checking versions or running small commands), here’s how to do it.

How to Install Composer on DreamHost (Optional)

DreamHost documentation:
https://help.dreamhost.com/hc/en-us/articles/214899037-Installing-Composer-overview

1. SSH into your hosting account

ssh useraccount@yourdomain.com

2. Go to your home directory

cd ~

3. Create a directory for Composer

mkdir -p ~/.php/composer
cd ~/.php/composer

4. Install Composer

curl -sS https://getcomposer.org/installer | php

5. Add Composer to your PATH

Edit your .bash_profile:

cd ~nano .bash_profile

Add this line (replace username with your DreamHost username):

export PATH=/home/username/.php/composer:$PATH

Save, close, then reload your profile:

. ~/.bash_profile

6. Rename the executable

mv ~/.php/composer/composer.phar ~/.php/composer/composer

7. Test the installation

composer

If you see the Composer version output, the installation worked.

astralmemories@iad1-shared-b7-33:~$ composer
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 2.8.10 2025-07-10 19:08:33

Important Note

Even with Composer installed, most real Drupal operations will still fail on shared hosting due to memory and performance limitations. That’s why this tutorial uses a safer and more reliable approach:

  • Run Composer locally
  • Deploy the generated files to your server

This avoids all shared hosting restrictions and ensures consistent deployments.


My Drupal Folder Deployer Bash Script

To streamline the Local → DEV → LIVE deployment workflow, I created a simple Bash script that helps you sync key Drupal folders (like vendor, modules, themes, and core) to your shared hosting environments using rsync.

This script makes it easy to:

  • Push changes from your local DDEV environment to your DEV site for testing
  • Deploy the same changes to your LIVE site when they’re ready
  • Run Drush and Git commands remotely (cache clear, config import, git pull, etc.)

It’s intentionally simple and can absolutely be improved—but it’s more than enough to get a smooth deployment workflow running on shared hosting.

Add the folder-deployer.sh Script to Your Drupal Project

Place the script in the root of your Drupal project—right next to the config directory and the web folder.

You can create the file using any editor, including nano:

nano folder-deployer.sh

Paste the entire script into the file.

#!/bin/bash
# Author: Leonardo Signorelli
# Websites: astralmemories.com, freewebtools.net, codesnippets.freewebtools.net
#
# VENDOR, MODULES, AND SITES FOLDERS DEPLOYER FOR DRUPAL (DreamHost)
#

# Instructions
# ------------
# 1 - Add/Copy this bash script to the root of your Drupal codebase folder (Next to the config, and docroot folders).
# 2 - Add your DreamHost user, domain, and project name to the configuration section below.
# 3 - Make this bash script executable with the following command:
#     chmod +x folder-deployer.sh
# 4 - Execute this script with the following command:
#     ./folder-deployer.sh
# 5 - Follow the instructions on the screen.
# 6 - Deploy!

# -------------
# CONFIGURATION
# -------------
dreamhost_user="astralmemories"     # e.g. yourdreamhostuser
domain="astralmemories.com"         # e.g. yourdomain.com
project_name="drupal"               # e.g. drupal_project (the folder name of your Drupal codebase)

# Domains and paths
dev_domain="${project_name}-dev.${domain}"      # e.g. drupal_project-dev.yourdomain.com
prod_domain="${project_name}.${domain}"         # e.g. drupal_project.yourdomain.com

local_path="/home/${dreamhost_user}/${project_name}"    # e.g. /home/yourdreamhostuser/drupal_project
remote_base="/home/${dreamhost_user}"                   # e.g. /home/yourdreamhostuser

# ------------
# SAFETY CHECK
# ------------
current_path=$(pwd)

if [ "$current_path" != "$local_path" ]; then
    echo "⚠️  Error: This script must be run from:"
    echo "   $local_path"
    echo "But you are running it from:"
    echo "   $current_path"
    echo "Exiting..."
    exit 1
fi

# ----------------------
# DEPLOY VENDOR FUNCTION
# ----------------------
deploy_vendor_to_env () {
    env_name=$1 # dev, prod
    environment_domain=$2 # domain for the environment (e.g. drupal_project-dev.yourdomain.com)
    remote_path="${remote_base}/${environment_domain}"  # e.g. /home/yourdreamhostuser/drupal_project-dev.yourdomain.com

    echo " "
    echo "Starting deployment of the 'vendor' folder to ${env_name^^} (${environment_domain})..."
    echo " "

    confirmation="novalue"
    while true; do

        # Vendor cleanup
        echo " "
        echo -e "Backup current 'vendor' folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
ssh "${dreamhost_user}@${domain}" << EOF
cd "${remote_path}"
[ -d "vendor_backup" ] && rm -rf "vendor_backup"
[ -d "vendor" ] && mv "vendor" "vendor_backup"
EOF
        fi
        [ "$confirmation" == "c" ] && break

        # Upload vendor
        echo " "
        echo -e "Upload local 'vendor' folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
rsync -avz --delete "${local_path}/vendor/" "${dreamhost_user}@${domain}:${remote_path}/vendor/"
        fi
        [ "$confirmation" == "c" ] && break

        echo " "
        echo "Deployment to ${env_name^^} complete!"
        echo " "
        break
    done

    if [ "$confirmation" == "c" ]; then
        echo " "
        echo "Deployment canceled. Returning to main menu..."
        echo " "
    fi
}

# -----------------------
# DEPLOY MODULES FUNCTION
# -----------------------
deploy_modules_to_env () {
    env_name=$1 # dev, prod
    environment_domain=$2 # domain for the environment (e.g. drupal_project-dev.yourdomain.com)
    remote_path="${remote_base}/${environment_domain}"  # e.g. /home/yourdreamhostuser/drupal_project-dev.yourdomain.com

    echo " "
    echo "Starting deployment of the 'web/modules' folder to ${env_name^^} (${environment_domain})..."
    echo " "

    confirmation="novalue"
    while true; do

        # Modules cleanup
        echo " "
        echo -e "Backup current modules folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
ssh "${dreamhost_user}@${domain}" << EOF
cd "${remote_path}/web"
[ -d "modules_backup" ] && rm -rf "modules_backup"
[ -d "modules" ] && mv "modules" "modules_backup"
EOF
        fi
        [ "$confirmation" == "c" ] && break

        # Upload modules
        echo " "
        echo -e "Upload local modules folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
rsync -avz --delete "${local_path}/web/modules/" "${dreamhost_user}@${domain}:${remote_path}/web/modules/"
        fi
        [ "$confirmation" == "c" ] && break

        echo " "
        echo "Deployment to ${env_name^^} complete!"
        echo " "
        break
    done

    if [ "$confirmation" == "c" ]; then
        echo " "
        echo "Deployment canceled. Returning to main menu..."
        echo " "
    fi
}

# ----------------------
# DEPLOY THEMES FUNCTION
# ----------------------
deploy_themes_to_env () {
    env_name=$1 # dev, prod
    environment_domain=$2 # domain for the environment (e.g. drupal_project-dev.yourdomain.com)
    remote_path="${remote_base}/${environment_domain}"  # e.g. /home/yourdreamhostuser/drupal_project-dev.yourdomain.com

    echo " "
    echo "Starting deployment of the 'web/themes' folder to ${env_name^^} (${environment_domain})..."
    echo " "

    confirmation="novalue"
    while true; do

        # Themes cleanup
        echo " "
        echo -e "Backup current 'web/themes' folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
ssh "${dreamhost_user}@${domain}" << EOF
cd "${remote_path}/web"
[ -d "themes_backup" ] && rm -rf "themes_backup"
[ -d "themes" ] && mv "themes" "themes_backup"
EOF
        fi
        [ "$confirmation" == "c" ] && break

        # Upload themes
        echo " "
        echo -e "Upload local 'web/themes' folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
rsync -avz --delete "${local_path}/web/themes/" "${dreamhost_user}@${domain}:${remote_path}/web/themes/"
        fi
        [ "$confirmation" == "c" ] && break

        echo " "
        echo "Deployment to ${env_name^^} complete!"
        echo " "
        break
    done

    if [ "$confirmation" == "c" ]; then
        echo " "
        echo "Deployment canceled. Returning to main menu..."
        echo " "
    fi
}

# ---------------------------
# DEPLOY CORE FOLDER FUNCTION
# ---------------------------
deploy_core_to_env () {
    env_name=$1 # dev, prod
    environment_domain=$2 # domain for the environment (e.g. drupal_project-dev.yourdomain.com)
    remote_path="${remote_base}/${environment_domain}"  # e.g. /home/yourdreamhostuser/drupal_project-dev.yourdomain.com

    echo " "
    echo "Starting deployment of the 'web/core' folder to ${env_name^^} (${environment_domain})..."
    echo " "

    confirmation="novalue"
    while true; do

        # web/core folder cleanup
        echo " "
        echo -e "Backup current 'web/core' folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
ssh "${dreamhost_user}@${domain}" << EOF
cd "${remote_path}/web"
[ -d "core_backup" ] && rm -rf "core_backup"
[ -d "core" ] && mv "core" "core_backup"
EOF
        fi
        [ "$confirmation" == "c" ] && break

        # Upload web/core folder
        echo " "
        echo -e "Upload local 'web/core' folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
rsync -avz --delete "${local_path}/web/core/" "${dreamhost_user}@${domain}:${remote_path}/web/core/"
        fi
        [ "$confirmation" == "c" ] && break

        echo " "
        echo "Deployment to ${env_name^^} complete!"
        echo " "
        break
    done

    if [ "$confirmation" == "c" ]; then
        echo "Deployment canceled. Returning to main menu..."
    fi
}

# ----------------------------
# DEPLOY SITES FOLDER FUNCTION
# ----------------------------
deploy_sites_to_env () {
    env_name=$1 # dev, prod
    environment_domain=$2 # domain for the environment (e.g. drupal_project-dev.yourdomain.com)
    remote_path="${remote_base}/${environment_domain}"  # e.g. /home/yourdreamhostuser/drupal_project-dev.yourdomain.com

    echo " "
    echo "Starting deployment of the 'web/sites' folder to ${env_name^^} (${environment_domain})..."
    echo " "

    confirmation="novalue"
    while true; do

        # web/sites folder cleanup
        echo " "
        echo -e "Backup current 'web/sites' folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
ssh "${dreamhost_user}@${domain}" << EOF
cd "${remote_path}/web"
[ -d "sites_backup" ] && rm -rf "sites_backup"
[ -d "sites" ] && mv "sites" "sites_backup"
EOF
        fi
        [ "$confirmation" == "c" ] && break

        # Upload web/sites folder
        echo " "
        echo -e "Upload local 'web/sites' folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
rsync -avz --delete "${local_path}/web/sites/" "${dreamhost_user}@${domain}:${remote_path}/web/sites/"
        fi
        [ "$confirmation" == "c" ] && break

        echo " "
        echo "Deployment to ${env_name^^} complete!"
        echo " "
        break
    done

    if [ "$confirmation" == "c" ]; then
        echo "Deployment canceled. Returning to main menu..."
    fi
}

# -----------------
# GIT PULL FUNCTION
# -----------------
git_pull_in_env () {
    env_name=$1 # dev, prod
    environment_domain=$2 # domain for the environment (e.g. drupal_project-dev.yourdomain.com)
    remote_path="${remote_base}/${environment_domain}"  # e.g. /home/yourdreamhostuser/drupal_project-dev.yourdomain.com

    echo " "
    echo "Starting 'git pull' in ${env_name^^} (${environment_domain})..."
    echo " "

    confirmation="novalue"
    while true; do

        # Git pull
        echo " "
        echo -e "Git pull in remote folder? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
ssh "${dreamhost_user}@${domain}" << EOF
cd "${remote_path}"
git pull
EOF
        fi
        [ "$confirmation" == "c" ] && break

        echo " "
        echo "Git pull in ${env_name^^} complete!"
        echo " "
        break
    done

    if [ "$confirmation" == "c" ]; then
        echo " "
        echo "Git pull canceled. Returning to main menu..."
        echo " "
    fi
}

# ----------------------
# IMPORT CONFIG FUNCTION
# ----------------------
import_config_in_env () {
    env_name=$1 # dev, prod
    environment_domain=$2 # domain for the environment (e.g. drupal_project-dev.yourdomain.com)
    remote_path="${remote_base}/${environment_domain}"  # e.g. /home/yourdreamhostuser/drupal_project-dev.yourdomain.com

    echo " "
    echo "Starting config import in ${env_name^^} (${environment_domain})..."
    echo " "

    confirmation="novalue"
    while true; do

        # Import config
        echo " "
        echo -e "Import Drupal config? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
ssh "${dreamhost_user}@${domain}" << EOF
cd "${remote_path}"
drush cim -y
EOF
        fi
        [ "$confirmation" == "c" ] && break

        echo " "
        echo "Config import in ${env_name^^} complete!"
        echo " "
        break
    done

    if [ "$confirmation" == "c" ]; then
        echo " "
        echo "Config import canceled. Returning to main menu..."
        echo " "
    fi
}

# --------------------
# CLEAR CACHE FUNCTION
# --------------------
clear_cache_in_env () {
    env_name=$1 # dev, prod
    environment_domain=$2 # domain for the environment (e.g. drupal_project-dev.yourdomain.com)
    remote_path="${remote_base}/${environment_domain}"  # e.g. /home/yourdreamhostuser/drupal_project-dev.yourdomain.com

    echo " "
    echo "Starting cache clear in ${env_name^^} (${environment_domain})..."
    echo " "

    confirmation="novalue"
    while true; do

        # Clear cache
        echo " "
        echo -e "Clear Drupal cache? (Yes/No/Cancel = y/n/c)"
        read confirmation
        if [ "$confirmation" == "y" ]; then
ssh "${dreamhost_user}@${domain}" << EOF
cd "${remote_path}"
drush cr
EOF
        fi
        [ "$confirmation" == "c" ] && break

        echo " "
        echo "Cache clear in ${env_name^^} complete!"
        echo " "
        break
    done

    if [ "$confirmation" == "c" ]; then
        echo " "
        echo "Cache clear canceled. Returning to main menu..."
        echo " "
    fi
}

# -------------
# SPLASH SCREEN
# -------------
echo "    ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓   "
echo "    ┃ ╺┳┓┏━┓╻ ╻┏━┓┏━┓╻    ┏━╸┏━┓╻  ╺┳┓┏━╸┏━┓ ┃   "
echo "    ┃  ┃┃┣┳┛┃ ┃┣━┛┣━┫┃    ┣╸ ┃ ┃┃   ┃┃┣╸ ┣┳┛ ┃   "
echo "    ┃ ╺┻┛╹┗╸┗━┛╹  ╹ ╹┗━╸  ╹  ┗━┛┗━╸╺┻┛┗━╸╹┗╸ ┃   "
echo "    ┃       ╺┳┓┏━╸┏━┓╻  ┏━┓╻ ╻┏━╸┏━┓         ┃   "
echo "    ┃        ┃┃┣╸ ┣━┛┃  ┃ ┃┗┳┛┣╸ ┣┳┛         ┃   "
echo "    ┃       ╺┻┛┗━╸╹  ┗━╸┗━┛ ╹ ┗━╸╹┗╸         ┃   "
echo "    ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛   "

# --------------------------------
# ENVIRONMENT SELECTOR & MAIN MENU
# --------------------------------

env_name="dev"
env_domain="$dev_domain"

while true; do
    # Environment selection menu
    echo "                  /'''''''''''\                 "
    echo "               ~o| ENVIRONMENT |o~              "
    echo "                  \.........../                 "
    echo "o-----------------------------------------------o"
    echo "|   1 - DEV (Development)                       |"
    echo "|   2 - PROD (Production)                       |"
    echo "o-----------------------------------------------o"
    echo "|   Q - Exit                                    |"
    echo "o-----------------------------------------------o"
    echo " "
    echo -n "Select environment: "
    read env_selection
    echo " "

    case "$env_selection" in
        1)
            env_name="dev"
            env_domain="$dev_domain"
            ;;
        2)
            env_name="prod"
            env_domain="$prod_domain"
            ;;
        Q|q)
            echo "Exiting..."
            exit 0
            ;;
        *)
            echo "Invalid selection. Please try again."
            continue
            ;;
    esac

    # Main menu for selected environment
    while true; do
        # Pad environment label for menu alignment
        if [ "$env_name" = "dev" ]; then
            env_label="DEV "
        else
            env_label="PROD"
        fi

        echo "                   /'''''''''\                   "
        echo "                ~o| MAIN MENU |o~                "
        echo "                   \........./                   "
        echo "o-------------- Environment: ${env_label} --------------o"
        echo "|   1 - Deploy 'vendor' folder to ${env_label}          |"
        echo "|   2 - Deploy 'web/modules' folder to ${env_label}     |"
        echo "|   3 - Deploy 'web/themes' folder to ${env_label}      |"
        echo "|   4 - Deploy 'web/core' folder to ${env_label}        |"
        echo "|   5 - Deploy 'web/sites' folder to ${env_label}       |"
        echo "|   6 - Git pull in ${env_label}                        |"
        echo "|   7 - Import config in ${env_label}                   |"
        echo "|   8 - Clear cache in ${env_label}                     |"
        echo "|   9 - Select Environment (DEV/PROD)           |"
        echo "o-----------------------------------------------o"
        echo "|   Q - Exit                                    |"
        echo "o-----------------------------------------------o"
        echo " "
        echo -n "Select a number from the Main Menu: "
        read selection
        echo " "

        case "$selection" in
            1)
                deploy_vendor_to_env "$env_name" "$env_domain"
                ;;
            2)
                deploy_modules_to_env "$env_name" "$env_domain"
                ;;
            3)
                deploy_themes_to_env "$env_name" "$env_domain"
                ;;
            4)
                deploy_core_to_env "$env_name" "$env_domain"
                ;;
            5)
                deploy_sites_to_env "$env_name" "$env_domain"
                ;;
            6)
                git_pull_in_env "$env_name" "$env_domain"
                ;;
            7)
                import_config_in_env "$env_name" "$env_domain"
                ;;
            8)
                clear_cache_in_env "$env_name" "$env_domain"
                ;;
            9)
                break # Go back to environment selector
                ;;
            Q|q)
                echo "Exiting..."
                exit 0
                ;;
            *)
                echo "Invalid selection. Please try again."
                ;;
        esac
    done
done

Configure the Script for Your Project

Inside the script, update the configuration section so it matches:

  • your DreamHost/Shared Hosting SSH username
  • your domain name
  • your project folder name

Configuration section:

# -------------
# CONFIGURATION
# -------------
dreamhost_user="yourdreamhostuser"  # Your hosting username
domain="astralmemories.com"         # Your domain (e.g. yourdomain.com)
project_name="drupal"               # Folder name of your Drupal project (e.g. drupal_project)

# Domains and paths
dev_domain="${project_name}-dev.${domain}"      # e.g. drupal_project-dev.yourdomain.com
prod_domain="${project_name}.${domain}"         # e.g. drupal_project.yourdomain.com

local_path="/home/${dreamhost_user}/${project_name}"    # e.g. /home/yourdreamhostuser/drupal_project
remote_base="/home/${dreamhost_user}"                   # e.g. /home/yourdreamhostuser

Make sure project_name matches the name of the folder on your local machine, not the one on the server.

Understanding the Safety Check

The script includes a simple safety check that prevents accidental execution from the wrong directory:

# ------------
# SAFETY CHECK
# ------------
current_path=$(pwd)

if [ "$current_path" != "$local_path" ]; then
    echo "⚠️  Error: This script must be run from:"
    echo "   $local_path"
    echo "But you are running it from:"
    echo "   $current_path"
    echo "Exiting..."
    exit 1
fi

This ensures the script only runs when executed from your correct local Drupal environment path.

Adjust local_path so it matches your actual machine:

local_path="/home/localusername/${project_name}"
# e.g. /home/yourlocalusername/drupal_project

Make the Script Executable

Before you can run the script, make it executable:

chmod +x folder-deployer.sh

Run the Script

Execute it with:

./folder-deployer.sh

You’ll immediately see the splash screen and the environment selector:

    ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃ ╺┳┓┏━┓╻ ╻┏━┓┏━┓╻    ┏━╸┏━┓╻  ╺┳┓┏━╸┏━┓ ┃
    ┃  ┃┃┣┳┛┃ ┃┣━┛┣━┫┃    ┣╸ ┃ ┃┃   ┃┃┣╸ ┣┳┛ ┃
    ┃ ╺┻┛╹┗╸┗━┛╹  ╹ ╹┗━╸  ╹  ┗━┛┗━╸╺┻┛┗━╸╹┗╸ ┃
    ┃       ╺┳┓┏━╸┏━┓╻  ┏━┓╻ ╻┏━╸┏━┓         ┃
    ┃        ┃┃┣╸ ┣━┛┃  ┃ ┃┗┳┛┣╸ ┣┳┛         ┃
    ┃       ╺┻┛┗━╸╹  ┗━╸┗━┛ ╹ ┗━╸╹┗╸         ┃
    ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
                  /'''''''''''\
               ~o| ENVIRONMENT |o~
                  \.........../
o-----------------------------------------------o
|   1 - DEV (Development)                       |
|   2 - PROD (Production)                       |
o-----------------------------------------------o
|   Q - Exit                                    |
o-----------------------------------------------o

Select environment:

Just type 1 for DEV or 2 for PROD and press Enter to continue.

To exit the script, type q, in the main menu, and press Enter.

At this point, we won’t deploy anything yet—you still need to complete a few more configuration steps before using the script in practice.


Configure the Database connection in your Drupal settings.php for your DEV and Live sites

Right now, only your local Drupal site is connected to a database—this is handled automatically by DDEV. If you open:

web/sites/default/settings.php

You’ll see this block at the very end:

// Automatically generated include for settings managed by ddev.
if (getenv('IS_DDEV_PROJECT') == 'true' && file_exists(__DIR__ . '/settings.ddev.php')) {
  include __DIR__ . '/settings.ddev.php';
}

This tells Drupal to load the local DDEV database configuration. We won’t touch or modify this block—it’s required for your local site to work.

Now let’s prepare the same file to support your DEV and LIVE sites on shared hosting.

Move the Configuration Sync Directory

By default, Drupal saves exported configuration inside:

sites/default/files/config_HASH/

This location is:

  • inside the web-accessible directory
  • inside a hashed folder that is not very convenient to find or manage
  • not ideal for version control

Best practice is to move your sync directory outside of the public webroot, ideally to a location you can easily commit to Git.

For this workflow, we’ll use:

drupal_root/config/sync

Add this line before the DDEV include block in settings.php:

// Set the location of the directory used for syncing configuration data.
$settings['config_sync_directory'] = '../config/sync';

This ensures:

  • exported config is cleanly stored
  • version control remains simple
  • you can reuse the same settings.php in all environments (local, dev, live)

Use Environment Variables for Database Credentials

Instead of hard-coding database credentials in settings.php, we will use environment variables. This is the modern and recommended Drupal practice.

Environment variables allow you to safely store secrets like:

  • database name
  • database user
  • database password
  • database host
  • hash salt

Where will these variables be stored?

On DreamHost shared hosting, the easiest place to define environment variables is inside an .htaccess file in the root folder of each site (DEV and LIVE). We will create those .htaccess files later.

Example .htaccess content:

SetEnv DB_NAME yourDBname
SetEnv DB_USER yourDBuser
SetEnv DB_PASS yourDBpassword
SetEnv DB_HOST yourDBhost

(You’ll add HASH_SALT later too.)

Add Database Settings to settings.php

In your local settings.php, add the following block before:

  • the DDEV include block
  • the config sync directory line
$databases['default']['default'] = [
    'database' => getenv('DB_NAME'),
    'username' => getenv('DB_USER'),
    'password' => getenv('DB_PASS'),
    'prefix' => '',
    'host' => getenv('DB_HOST'),
    'port' => '3306',
    'isolation_level' => 'READ COMMITTED',
    'namespace' => 'Drupal\\mysql\\Driver\\Database\\mysql',
    'driver' => 'mysql',
    'autoload' => 'core/modules/mysql/src/Driver/Database/mysql/',
  ];

This tells Drupal to read all credentials from environment variables.

Your DEV and LIVE sites will automatically connect to their correct database once you create the .htaccess files.

Generate a Unique $settings[‘hash_salt’] for Each Environment

Drupal’s hash_salt is critical for security. It’s used for:

  • CSRF tokens
  • session keys
  • private file URLs
  • cache keys
  • one-time login links

Every environment must have its own unique hash salt. Sharing the same salt between environments is a security risk.

Find (or add) this in settings.php:

$settings['hash_salt'] = '';

Replace it with:

$settings['hash_salt'] = getenv('HASH_SALT');

Just like the database credentials, the actual salt value will be stored in your site’s .htaccess file as an environment variable. We will create those .htaccess files in a later step.

This section is now complete—your settings.php is ready to support Local, DEV, and LIVE using clean environment-based configuration.


Create a Database Dump of Your Local Drupal Website

To set up your DEV and LIVE sites on your shared hosting account, we’ll first export a copy of your local Drupal database. This exported file can then be imported into both environments so they start with the exact same data, configuration, users, and content as your local site.

We’ll use Drush (inside DDEV) to create this database dump.

Export the Drupal Database Using Drush

From the root folder of your local DDEV project, run the following command:

ddev drush sql:dump --extra-dump=--no-tablespaces --result-file=../drupaldb_2025-11-07.sql

This will create a database export named:

drupaldb_2025-11-07.sql

saved one directory above your Drupal project web/ folder.

You can change the filename to any name/date format you prefer.

What Each Part of the Command Does

drush sql:dump

This tells Drush to export the entire Drupal database—similar to using mysqldump, but wrapped inside a convenient Drush command.

--extra-dump=--no-tablespaces

This flag is important. It passes the –no-tablespaces option to the mysqldump process that Drush uses internally.

–no-tablespaces helps avoid issues when importing the database on different servers (like shared hosting), because:

  • It removes TABLESPACE and DATA DIRECTORY instructions
  • These instructions often reference directory paths that only exist on the original server
  • Without this option, imports can fail with “tablespace” or “no such file or directory” errors

In short: this flag makes your SQL dump more portable and compatible across environments.

Now you have a clean database dump that we’ll later import into your DEV and LIVE sites.


Commit and Push Your Changes to Your Repository

Now that you’ve created and configured the deployer script, updated your settings.php file and generated your database dump, it’s time to commit everything to your Git repository.

From the root folder of your DDEV project, run:

git status

You should see something like:

  • web/sites/default/settings.php marked as modified
  • Your database dump (e.g., drupaldb_2025-11-07.sql) listed as untracked
  • Your deployment script (e.g., folder-deployer.sh) also untracked

1. Add the files to the staging area

git add web/sites/default/settings.php
git add drupaldb_2025-11-07.sql
git add folder-deployer.sh

2. Commit the changes

git commit -m "Database connection in settings.php, Local database dump, and Deployer script."

3. Push everything to your remote repository

git push

Your repository will now include:

  • The updated database configuration logic
  • Your local site’s SQL dump
  • Your deployment script

These will be used later when setting up your DEV and LIVE environments.


Finish Setting Up Your DEV and LIVE Websites

Now that your code is in your Git repository, it’s time to finish preparing your DEV and LIVE environments inside your Shared Hosting account.

In this step, you will:

  • Clone your repository into each site folder
  • Create .htaccess files containing environment variables
  • Assign each site its own HASH_SALT
  • Configure Drush to work correctly on shared hosting
  • Configure automatic loading of environment variables for CLI (Drush, PHP, etc.)

By the end, both your DEV and LIVE environments will understand:

  • How to connect to their correct databases
  • How to load their own hash salts
  • How to run Drush independently

1. Connect to Your Shared Hosting via SSH

On your local machine, open a terminal and log into your hosting account:

ssh useraccount@yourdomain.com

Navigate to your home directory (where DreamHost places your website folders):

cd ~

You should see both site folders:

drupal-dev.astralmemories.com/
drupal.astralmemories.com/

2. Clone Your Repository Into the DEV Website Folder

First, remove the existing DEV folder (DreamHost creates a placeholder folder when you create the domain).

rm -rf drupal-dev.astralmemories.com/

Now clone your repository into a fresh folder:

git clone git@github.com:astralmemories/drupal.git drupal-dev.astralmemories.com

3. Create the DEV .htaccess With Environment Variables

Open the .htaccess file inside your new DEV project directory:

nano drupal-dev.astralmemories.com/.htaccess

Paste the following template:

SetEnv DB_NAME yourDevDBname
SetEnv DB_USER yourDevDBuser
SetEnv DB_PASS yourDevDBpassword
SetEnv DB_HOST yourDevDBhost
SetEnv HASH_SALT yourDevDrupalHashSalt

Replace each placeholder with the real values for your DEV database.

Generate a Secure Hash Salt for the DEV Environment

Each site (Local, DEV, LIVE) needs its own HASH_SALT.

Use either Drush or PHP inside your local DDEV environment.

Using Drush

ddev drush php-eval "echo \Drupal\Component\Utility\Crypt::randomBytesBase64(55) . PHP_EOL;"

Using PHP

ddev php -r 'echo base64_encode(random_bytes(55)), "\n";'

Copy the generated string and replace the HASH_SALT placeholder in your .htaccess file, for example:

SetEnv HASH_SALT Jrq9em2vEE2d1/i9XYNLUud56yJ1FzLe6dTzOum85LMLFSRrNG//EP55nSby0piS7sMrH0pb4Q=

Save and exit nano (CTRL + X, then press Y).

4. Clone Your Repository Into the LIVE Website Folder

Remove the existing LIVE site folder:

rm -rf drupal.astralmemories.com/

Clone the repo:

git clone git@github.com:astralmemories/drupal.git drupal.astralmemories.com

5. Create the LIVE .htaccess With Environment Variables

Open the .htaccess file:

nano drupal.astralmemories.com/.htaccess

Paste the LIVE template:

SetEnv DB_NAME yourLiveDBname
SetEnv DB_USER yourLiveDBuser
SetEnv DB_PASS yourLiveDBpassword
SetEnv DB_HOST yourLiveDBhost
SetEnv HASH_SALT yourLiveDrupalHashSalt

Just like before:

  • Replace database values
  • Generate a different HASH_SALT for production
  • Paste the new secure salt into the file

Save and exit nano.

6. Configure Drush to Work on Shared Hosting

At this point, your DEV and LIVE folders contain your Drupal code — but Drush won’t work yet because:

  • No vendor folder has been deployed yet
  • Drush doesn’t know where its binary lives
  • The CLI does not automatically load .htaccess environment variables

We’ll fix all of that now.

Auto-Detect Drush in Each Site Folder

We will use the .bash_profile file which is a configuration file in a user’s home directory that runs commands for an interactive login shell. Changes made in .bash_profile take effect after you log out and log back in, or you can run source ~/.bash_profile to apply them immediately.

Edit your ~/.bash_profile:

cd ~nano .bash_profile

Add this function:

# Auto-Detect Local Drush
function drush() {
  if [ -f ./vendor/bin/drush ]; then
    ./vendor/bin/drush "$@"
  else
    echo "⚠ Drush not found in current directory's vendor/bin. Please run from a valid Drupal project."
  fi
}

Save and exit nano (CTRL + X, then press Y).

This ensures Drush automatically uses the correct binary located in each site’s vendor/bin/ directory.

Apply the update:

source ~/.bash_profile

Make Environment Variables Available to Drush (CLI)

Here’s the problem:

  • Your .htaccess defines environment variables
  • But .htaccess is only read by Apache
  • Drush runs via CLI → it never sees those variables

To fix this, we will automatically load the environment variables from the corresponding .htaccess file whenever you enter a Drupal site folder.

Re-open your .bash_profile:

nano ~/.bash_profile

Add this block at the bottom:

# ========================================================
# Drupal Environment Variable Loader for CLI (Drush, etc.)
# ========================================================

# Function to load SetEnv values from an .htaccess file
load_htaccess_env() {
  local htaccess_file="$1"
  if [[ -f "$htaccess_file" ]]; then
    # Read lines starting with SetEnv, extract key/value, and export
    while read -r line; do
      if [[ "$line" =~ ^SetEnv[[:space:]]+([A-Za-z0-9_]+)[[:space:]]+(.+) ]]; then
        export "${BASH_REMATCH[1]}"="${BASH_REMATCH[2]}"
      fi
    done < "$htaccess_file"
  fi
}

# Detect which site folder you're currently in and load its .htaccess
current_dir=$(pwd)

if [[ "$current_dir" == *"drupal-dev.astralmemories.com"* ]]; then
  echo "Loading DEV environment variables..."
  load_htaccess_env "$HOME/drupal-dev.astralmemories.com/.htaccess"
elif [[ "$current_dir" == *"drupal.astralmemories.com"* ]]; then
  echo "Loading PROD environment variables..."
  load_htaccess_env "$HOME/drupal.astralmemories.com/.htaccess"
fi

# Auto-load .htaccess env when entering Drupal directories
cd() {
  builtin cd "$@" || return
  if [[ "$PWD" == *"drupal-dev.astralmemories.com"* ]] || [[ "$PWD" == *"drupal.astralmemories.com"* ]]; then
    source ~/.bash_profile >/dev/null 2>&1
  fi
}

Replace folder names with your own if different.

Save and exit nano, then apply the changes:

source ~/.bash_profile

How This Helps

✔ Drush now automatically points to each site’s local vendor/bin/drush
✔ Database credentials load automatically when you cd into a Drupal site
✔ The correct HASH_SALT loads per environment
✔ Your deploy script will be able to run:

  • drush cr
  • drush cim
  • other Drush commands

Your DEV and LIVE sites are now fully prepared for deployment.


Deploy the Missing Drupal Folders to Your DEV Website

With your DEV environment cloned from Git and fully configured, the next step is to deploy the missing folders (vendor, web/modules, web/core, and web/sites) from your local DDEV environment to your DEV site on shared hosting.

This is exactly what your folder-deployer.sh script is designed to do.

1. Run the Deployer Script From Your Local DDEV Environment

Navigate to the root of your Local Drupal project (inside DDEV) and run:

./folder-deployer.sh

You’ll see the ASCII splash screen and the environment selector:

    ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃ ╺┳┓┏━┓╻ ╻┏━┓┏━┓╻    ┏━╸┏━┓╻  ╺┳┓┏━╸┏━┓ ┃
    ┃  ┃┃┣┳┛┃ ┃┣━┛┣━┫┃    ┣╸ ┃ ┃┃   ┃┃┣╸ ┣┳┛ ┃
    ┃ ╺┻┛╹┗╸┗━┛╹  ╹ ╹┗━╸  ╹  ┗━┛┗━╸╺┻┛┗━╸╹┗╸ ┃
    ┃       ╺┳┓┏━╸┏━┓╻  ┏━┓╻ ╻┏━╸┏━┓         ┃
    ┃        ┃┃┣╸ ┣━┛┃  ┃ ┃┗┳┛┣╸ ┣┳┛         ┃
    ┃       ╺┻┛┗━╸╹  ┗━╸┗━┛ ╹ ┗━╸╹┗╸         ┃
    ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
                  /'''''''''''\
               ~o| ENVIRONMENT |o~
                  \.........../
o-----------------------------------------------o
|   1 - DEV (Development)                       |
|   2 - PROD (Production)                       |
o-----------------------------------------------o
|   Q - Exit                                    |
o-----------------------------------------------o
Select environment: 1

Type 1 and press Enter to select the DEV environment.

This opens the main deployment menu:

                  /'''''''''\
               ~o| MAIN MENU |o~
                  \........./
o-------------- Environment: DEV  --------------o
|   1 - Deploy 'vendor' folder to DEV           |
|   2 - Deploy 'web/modules' folder to DEV      |
|   3 - Deploy 'web/themes' folder to DEV       |
|   4 - Deploy 'web/core' folder to DEV         |
|   5 - Deploy 'web/sites' folder to DEV        |
|   6 - Git pull in DEV                         |
|   7 - Import config in DEV                    |
|   8 - Clear cache in DEV                      |
|   9 - Select Environment (DEV/PROD)           |
o-----------------------------------------------o
|   Q - Exit                                    |
o-----------------------------------------------o

Select a number from the Main Menu:

2. Deploy the vendor Folder to the DEV Website

Choose option 1, Deploy ‘vendor’ folder to DEV:

The script asks if you want to back up the current vendor folder on the server.

Since this is your first deployment, select No:

Backup current 'vendor' folder? (Yes/No/Cancel = y/n/c)
n

Then it asks if you want to upload your local vendor folder, select Yes:

Upload local 'vendor' folder? (Yes/No/Cancel = y/n/c)
Y

You’ll be prompted to enter your shared hosting password so the script can sync via SSH/rsync:

(youraccount@yourhostingdomain.com) Password:

Once the deployment finishes, the script returns you to the main menu.

3. Deploy the web/modules Folder to DEV

Now deploy your modules (core, contributed, and custom). Type 2 and press Enter to select “Deploy ‘web/modules’ folder to DEV”:

Select a number from the Main Menu: 2

Starting deployment of the 'web/modules' folder to DEV (drupal-dev.astralmemories.com)...

Again, skip the backup:

Backup current modules folder? (Yes/No/Cancel = y/n/c)
n

Upload the local folder, select Yes:

Upload local modules folder? (Yes/No/Cancel = y/n/c)
Y

Enter your password when prompted.

Once the process is done, you will go back to the Deployer script main menu.

4. Deploy the web/core Folder to DEV

We still need to deploy the ‘web/core’ folder to the DEV website. Type 4 and press Enter to select “Deploy ‘web/core’ folder to DEV”:

Select a number from the Main Menu: 4

Starting deployment of the 'web/core' folder to DEV (drupal-dev.astralmemories.com)...

Skip the backup:

Backup current 'web/core' folder? (Yes/No/Cancel = y/n/c)
n

Upload your local ‘web/core’ folder, select Yes:

Upload local 'web/core' folder? (Yes/No/Cancel = y/n/c)
y

Enter your password again.

Verify Drush Connectivity on the DEV Website

At this point, you should be able to run “drush status” against your DEV website. 

Connect via SSH to your hosting account, go to your DEV site folder:

cd ~/drupal-dev.yourdomain.com/

Run:

drush status

You should see output similar to:

your-user-account@iad1-shared-b7-33:~/drupal-dev.yourdomain.com$ drush status
Drupal version : 11.2.5
Site URI       : http://default
DB driver      : mysql
DB hostname    : yourDevDBhost
DB port        : 3306
DB username    : yourDevDBuser
DB name        : yourDevDBname
PHP binary     : /usr/local/php83/bin/php
PHP config     : /etc/php83/php.ini
PHP OS         : Linux
PHP version    : 8.3.14
Drush script   : /home/your-user-account/drupal-dev.yourdomain.com/vendor/bin/drush.php
Drush version  : 13.6.2.0
Drush temp     : /tmp
Drush configs  : /home/your-user-account/drupal-dev.yourdomain.com/vendor/drush/drush/dru
                sh.yml
Drupal root    : /home/your-user-account/drupal-dev.yourdomain.com/web
Site path      : sites/default
Drupal config  : ../config/sync
your-user-account@iad1-shared-b7-33:~/drupal-dev.yourdomain.com$

This confirms:

✔ Drush is working
✔ Environment variables are loading
✔ Database credentials are detected correctly

5. Deploy the web/sites Folder to DEV (Assets, Files, Images)

Finally, deploy the web/sites folder, which contains:

  • Files uploaded by Drupal
  • Image styles
  • Any public/private files

Type 5 and press Enter to select “Deploy ‘web/sites’ folder to DEV”:

Select a number from the Main Menu: 5

Starting deployment of the 'web/sites' folder to DEV (drupal-dev.astralmemories.com)...

Skip the backup:

Backup current 'web/sites' folder? (Yes/No/Cancel = y/n/c)
n

Upload your local ‘web/sites’ folder, select Yes:

Upload local 'web/sites' folder? (Yes/No/Cancel = y/n/c)
y

Enter your hosting password when prompted.

When finished, you will go back to the Deployer script main menu.

Type q and press Enter to exit the script:

Select a number from the Main Menu: q
Exiting...

Your DEV website now contains:

✔ Full Drupal core
✔ All contrib + custom modules
✔ All site assets
✔ A complete vendor folder

The only thing missing is the database.

6. Import the Local Database Into Your DEV Website

Now that all files are deployed, you’re ready to import your Local DDEV database dump into your DEV site.

Still inside your DEV folder via SSH:

drush sql:cli < drupaldb_2025-11-07.sql

Replace the filename with your actual database dump.

When the import finishes, clear the Drupal cache:

drush cr

You should see:

drush cr
[success] Cache rebuild complete.

7. Visit the DEV Website

Now open your DEV site in the browser: https://drupal-dev.yourdomain.com

You should now see a fully functional clone of your Local Drupal site:

At this point, the DEV website is fully configured, and the entire workflow is validated.


Deploy the Missing Drupal Folders to Your Live Website (PROD)

Now that your DEV environment is fully deployed and functional, the next step is to repeat the process for your Live (PROD) website.

This ensures your production environment becomes an identical clone of your Local DDEV Drupal site before you start working with future deployments.

You’ll use the same folder-deployer.sh script — this time selecting the PROD environment.

1. Run the Deployer Script and Select the PROD Environment

In your Local DDEV Drupal project root, run:

./folder-deployer.sh

You’ll see the environment selector again:

    ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃ ╺┳┓┏━┓╻ ╻┏━┓┏━┓╻    ┏━╸┏━┓╻  ╺┳┓┏━╸┏━┓ ┃
    ┃  ┃┃┣┳┛┃ ┃┣━┛┣━┫┃    ┣╸ ┃ ┃┃   ┃┃┣╸ ┣┳┛ ┃
    ┃ ╺┻┛╹┗╸┗━┛╹  ╹ ╹┗━╸  ╹  ┗━┛┗━╸╺┻┛┗━╸╹┗╸ ┃
    ┃       ╺┳┓┏━╸┏━┓╻  ┏━┓╻ ╻┏━╸┏━┓         ┃
    ┃        ┃┃┣╸ ┣━┛┃  ┃ ┃┗┳┛┣╸ ┣┳┛         ┃
    ┃       ╺┻┛┗━╸╹  ┗━╸┗━┛ ╹ ┗━╸╹┗╸         ┃
    ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
                  /'''''''''''\
               ~o| ENVIRONMENT |o~
                  \.........../
o-----------------------------------------------o
|   1 - DEV (Development)                       |
|   2 - PROD (Production)                       |
o-----------------------------------------------o
|   Q - Exit                                    |
o-----------------------------------------------o
Select environment: 2

Type 2 and press Enter to select the PROD environment. This opens the main menu for the Live site:

                  /'''''''''\
               ~o| MAIN MENU |o~
                  \........./
o-------------- Environment: PROD --------------o
|   1 - Deploy 'vendor' folder to PROD          |
|   2 - Deploy 'web/modules' folder to PROD     |
|   3 - Deploy 'web/themes' folder to PROD      |
|   4 - Deploy 'web/core' folder to PROD        |
|   5 - Deploy 'web/sites' folder to PROD       |
|   6 - Git pull in PROD                        |
|   7 - Import config in PROD                   |
|   8 - Clear cache in PROD                     |
|   9 - Select Environment (DEV/PROD)           |
o-----------------------------------------------o
|   Q - Exit                                    |
o-----------------------------------------------o

Select a number from the Main Menu:

2. Deploy the vendor Folder to PROD

Start by deploying Composer dependencies. Type 1 and press Enter to select “Deploy ‘vendor’ folder to PROD”.

Skip backup:

Backup current 'vendor' folder? (Yes/No/Cancel = y/n/c)
n

Upload your local vendor folder, select Yes:

Upload local 'vendor' folder? (Yes/No/Cancel = y/n/c)
Y

Enter your shared hosting password:

(youraccount@yourhostingdomain.com) Password:

When complete, the script returns to the main menu.

3. Deploy web/modules to the PROD Website

Now push modules (core, contributed, custom). Type 2 and press Enter to select “Deploy ‘web/modules’ folder to PROD”:

Select a number from the Main Menu: 2

Starting deployment of the 'web/modules' folder to PROD (drupal.astralmemories.com)...

Skip the backup:

Backup current modules folder? (Yes/No/Cancel = y/n/c)
n

Upload your local modules folder, select Yes:

Upload local modules folder? (Yes/No/Cancel = y/n/c)
Y

Enter your hosting password again.

4. Deploy web/core to PROD

Next, deploy the ‘web/core’ folder to the PROD website. Type 4 and press Enter to select “Deploy ‘web/core’ folder to PROD”:

Select a number from the Main Menu: 4

Starting deployment of the 'web/core' folder to PROD (drupal.astralmemories.com)...

Skip backup:

Backup current 'web/core' folder? (Yes/No/Cancel = y/n/c)
n

Upload your local ‘web/core’ folder, select Yes:

Upload local 'web/core' folder? (Yes/No/Cancel = y/n/c)
y

Enter your shared hosting password:

(youraccount@yourhostingdomain.com) Password:

Verify Drush Connectivity on the PROD Website

SSH into your hosting account and navigate to your PROD site folder:

cd ~/drupal.yourdomain.com/

Run:

drush status

You should see output similar to:

your-user-account@iad1-shared-b7-33:~/drupal.yourdomain.com$ drush status
Drupal version : 11.2.5
Site URI       : http://default
DB driver      : mysql
DB hostname    : yourLiveDBhost
DB port        : 3306
DB username    : yourLiveDBuser
DB name        : yourLiveDBname
PHP binary     : /usr/local/php83/bin/php
PHP config     : /etc/php83/php.ini
PHP OS         : Linux
PHP version    : 8.3.14
Drush script   : /home/your-user-account/drupal.yourdomain.com/vendor/bin/drush.php
Drush version  : 13.6.2.0
Drush temp     : /tmp
Drush configs  : /home/your-user-account/drupal.yourdomain.com/vendor/drush/drush/dru
                sh.yml
Drupal root    : /home/your-user-account/drupal.yourdomain.com/web
Site path      : sites/default
Drupal config  : ../config/sync
your-user-account@iad1-shared-b7-33:~/drupal.yourdomain.com$

This confirms that:

✔ Drush works correctly
✔ Environment variables are loading
✔ PHP is configured
✔ The PROD database credentials are detected

5. Deploy the web/sites Folder to PROD (Files, Images, Assets)

Finally, deploy the file system. Type 5 and press Enter to select “Deploy ‘web/sites’ folder to PROD”:

Select a number from the Main Menu: 5

Starting deployment of the 'web/sites' folder to PROD (drupal.astralmemories.com)...

Skip backup:

Backup current 'web/sites' folder? (Yes/No/Cancel = y/n/c)
n

Upload your local ‘web/sites’ folder, select Yes:

Upload local 'web/sites' folder? (Yes/No/Cancel = y/n/c)
y

Enter your password when prompted.

(youraccount@yourhostingdomain.com) Password:

When finished, you will go back to the Deployer script main menu.

Type q and press Enter to exit the script:

Select a number from the Main Menu: q
Exiting...

At this point, all Drupal code, assets, and vendor files are in place on your Live server.

6. Import Your Local Database Into the Live Website

Now that your PROD site has all necessary files, the final step is loading the Local database into production.

Run from the PROD folder:

drush sql:cli < drupaldb_2025-11-07.sql

(Replace the filename with your actual SQL dump.)

When the import completes, clear the Drupal cache:

drush cr

7. Verify the Live Site in the Browser

Visit your Live site: https://drupal.yourdomain.com

You should now see a complete clone of your Local DDEV Drupal site, running fully on shared hosting:

You’ve now successfully created a full Local → DEV → PROD Drupal workflow using:

  • DDEV for local development
  • Git for version control
  • Drush for CLI operations
  • Environment variables for secure credential handling
  • A custom Bash deployer script for managing deployments
  • Shared hosting (DreamHost Unlimited or similar)
  • And a workflow that bypasses common shared-hosting limitations by avoiding Composer on the server

You now have a professional, modern Drupal 10/11 workflow — even on low-cost shared hosting that normally can’t run complex Composer operations.


Conclusion

By following this tutorial from start to finish, you’ve set up a complete and reliable Drupal development workflow that works seamlessly across Local, Development (DEV), and Production (PROD) environments—even on a shared hosting platform with tight resource limitations.

This process achieves something many Drupal developers struggle with when starting out: a workflow that is simple, secure, repeatable, and predictable, without relying on expensive hosting or complex DevOps tooling.

Here’s what you accomplished:

✔ Built a modern local development environment using DDEV

You created a fully isolated Drupal environment with matching PHP/MySQL versions, giving you faster iteration and complete control over your development setup.

✔ Prepared two remote environments (DEV and LIVE)

You configured fresh DreamHost site directories, databases, PHP versions, SSH access, and folder structures—creating a solid foundation for multi-environment Drupal development.

✔ Implemented best practices inside settings.php

You migrated your sync directory, switched to environment variable–based database credentials, and added secure, per-environment hash salts.

✔ Enabled Drush and PHP version consistency across environments

Through .bash_profile configuration and environment-variable loading, your shared hosting server now behaves much more like a modern CLI-ready Drupal environment.

✔ Created and used a custom deployment script

Your Bash deployer script allows you to:

  • Deploy specific folders (vendor, modules, themes, core, sites)
  • Run Drush commands remotely
  • Safely back up and sync code
  • Move changes from Local → DEV → PROD with simple menu options

This script turns a tedious, error-prone workflow into a streamlined, predictable system.

✔ Cloned your local Drupal site into both DEV and PROD

By exporting your local database and importing it into both remote sites, you created full working clones of your local environment—ensuring consistency across the entire development pipeline.

You Now Have a Complete Local → DEV → PROD Drupal Workflow

With everything in place, you can:

  • Develop confidently on your local machine
  • Test safely in a controlled DEV environment
  • Deploy to production with repeatable, reliable steps
  • Sync changes easily without running Composer on shared hosting
  • Maintain environment parity and avoid configuration drift

You now have a foundation you can extend, customize, and improve over time—whether you want to enhance your deployer script, add automatic backups, integrate frontend build steps, or move toward a more advanced CI/CD setup in the future.