Bedrock brings modern PHP development practices to WordPress. Instead of the traditional WordPress setup where core files, plugins, themes, and configuration all live in one tangled directory, Bedrock uses [Composer](https://getcomposer.org/) for dependency management, environment variables for configuration, and a restructured folder layout that makes deployments predictable and repeatable.

If you have ever struggled with deploying WordPress — manually uploading plugins via FTP, editing `wp-config.php` on production, or wondering which files changed since the last release — Bedrock solves those problems. Combined with DeployHQ, you get automated, consistent deployments from your Git repository to any environment.

This guide walks through setting up a Bedrock project and deploying it with DeployHQ, including build pipelines, environment configuration, and post-deploy automation.

---

## Creating a Bedrock Project

Use Composer to scaffold a new Bedrock project:

```bash
composer create-project roots/bedrock my-site
cd my-site
```

This gives you a clean project with Composer managing WordPress core, plugins, and themes as dependencies. Commit the entire project to Git — but not the `vendor/` or `web/wp/` directories. Those get installed during the build.

Your `.gitignore` should already exclude:

```
vendor/
web/wp/
web/app/uploads/
.env
```

If you are converting an existing WordPress site to Bedrock, the key shift is treating WordPress core and plugins as Composer packages rather than committed files. Your `composer.json` becomes the single source of truth for what runs on the server.

---

## Understanding Bedrock's Folder Structure

Bedrock reorganises the standard WordPress directory layout:

```
my-site/
├── config/              # Environment-specific configuration
│   ├── application.php  # Shared settings (replaces wp-config.php)
│   └── environments/
│       ├── development.php
│       ├── staging.php
│       └── production.php
├── vendor/              # Composer dependencies (not committed)
├── web/                 # Document root (point your server here)
│   ├── app/             # wp-content equivalent
│   │   ├── plugins/
│   │   ├── themes/
│   │   ├── mu-plugins/
│   │   └── uploads/
│   ├── wp/              # WordPress core (installed by Composer)
│   └── index.php
├── composer.json
├── .env                 # Environment variables (not committed)
└── .env.example
```

The critical difference: your web server's document root points to `web/`, not the project root. This keeps `config/`, `vendor/`, and `.env` outside the public directory — a meaningful security improvement over standard WordPress where `wp-config.php` sits in the document root.

---

## Managing Plugins and Themes via Composer

Bedrock uses [WP Packages](https://wp-packages.org/) to install plugins and themes as Composer packages:

```bash
composer require wp-plugin/wordfence
composer require wp-plugin/wordpress-seo
composer require wp-theme/flavor
```

Every plugin and theme version is locked in `composer.lock`. When you deploy, the server gets exactly the same versions you tested locally — no surprises from auto-updates or inconsistent plugin states.

WP Packages replaces the previous WPackagist repository — if you maintained an older Bedrock project, swap the `wpackagist-plugin/` and `wpackagist-theme/` prefixes for `wp-plugin/` and `wp-theme/` and run `composer update`.

For premium plugins that are not on WP Packages, add a private Composer repository or use [SatisPress](https://github.com/cedaro/satispress) to serve them from a WordPress install you control. Avoid committing plugin ZIP files directly — it defeats the purpose of dependency management.

A practical tip: run `composer update --dry-run` before committing to see what would change. This catches unintended version bumps before they reach production.

---

## Multi-Environment Configuration

Bedrock replaces `wp-config.php` with a `config/` directory. The `application.php` file loads shared settings, and environment-specific files override them based on the `WP_ENV` variable.

Your `.env` file sets the environment:

```
WP_ENV=development
WP_HOME=https://mysite.test
WP_SITEURL=${WP_HOME}/wp

DB_NAME=my_site
DB_USER=root
DB_PASSWORD=
DB_HOST=127.0.0.1

AUTH_KEY='generate-these-with-wp-cli-or-roots-salts'
SECURE_AUTH_KEY='...'
```

Each environment gets its own `.env` on the server. Development enables `WP_DEBUG` and error display; production disables them and enforces SSL. The `config/environments/production.php` file should include:

```php
Config::define('WP_DEBUG', false);
Config::define('WP_DEBUG_DISPLAY', false);
Config::define('DISALLOW_FILE_MODS', true);
Config::define('DISALLOW_FILE_EDIT', true);
```

Setting `DISALLOW_FILE_MODS` to `true` on production is essential with Bedrock — it prevents WordPress from modifying plugins or themes outside of your Composer workflow. Any change should go through Git and your deployment pipeline.

Use the [wp-cli dotenv command](https://github.com/aaemnnosttv/wp-cli-dotenv-command) to validate that all required environment variables are present before WordPress boots. Missing a database credential should fail loudly, not produce a white screen.

---

## Setting Up DeployHQ

Create a new project in [DeployHQ](https://www.deployhq.com/signup) and connect your Git repository. Under server settings, configure your deployment target and set the deployment path to your project root — not the `web/` directory. Your web server configuration handles pointing the document root to `web/`.

### Build Pipeline

Bedrock projects need Composer to install dependencies before files are transferred to the server. Create a `.deploybuild.yaml` in your project root:

```yaml
build:
  - composer install --no-dev --optimize-autoloader --no-interaction
```

The `--no-dev` flag skips development dependencies (testing tools, debug packages), and `--optimize-autoloader` generates a classmap for faster autoloading in production. DeployHQ's [build pipeline](https://www.deployhq.com/support/build-pipelines/deploybuild-dot-yaml) runs these commands in an isolated environment and deploys the resulting files.

### Excluded Files

Under your server's deployment settings, add these paths to the excluded files list so they are never overwritten on the server:

```
.env
web/app/uploads/
.git/
.github/
.deploybuild.yaml
```

The `.env` file on the server contains production credentials — deploying over it would break the site. The `uploads/` directory contains user-uploaded media that lives on the server, not in Git.

### Config Files

Use DeployHQ's [config files](https://www.deployhq.com/support) feature to manage the `.env` file for each server. Add a config file with the destination path `.env` and paste your production environment variables. DeployHQ will place this file on the server during deployment, keeping secrets out of your repository.

### Zero-Downtime Deployments

Enable [zero-downtime deployments](https://www.deployhq.com/features/zero-downtime-deployments) in your server settings. DeployHQ deploys each release to a new directory and switches a symlink once the build succeeds. If the deployment fails, the previous release continues serving traffic.

With Bedrock, ensure your `uploads/` directory is a shared symlink rather than part of the release directory — otherwise each deployment starts with an empty media library. Configure the shared path in your zero-downtime settings to include `web/app/uploads`.

### Post-Deploy SSH Commands

Add SSH commands that run after each successful deployment:

```bash
cd %deploy_path% && wp cache flush --path=web/wp
cd %deploy_path% && wp rewrite flush --path=web/wp
```

Note the `--path=web/wp` flag — Bedrock's WordPress core lives inside `web/wp/`, not at the project root, so WP-CLI needs to be pointed there. If you use a database migration tool like [WP Migrate](https://deliciousbrains.com/wp-migrate-db-pro/) or Phinx, add the migration command here as well.

---

## Database Migrations Between Environments

WordPress stores absolute URLs in the database, which means you cannot simply copy a database dump from production to staging without search-replacing URLs. Use WP-CLI:

```bash
wp search-replace 'https://mysite.com' 'https://staging.mysite.com' --path=web/wp --all-tables
```

For structured schema migrations (custom tables, option updates), consider [WP Migrate DB Pro](https://deliciousbrains.com/wp-migrate-db-pro/) or write migration scripts that run as post-deploy SSH commands. Keep migrations idempotent — running them twice should not break anything.

---

## Trellis: Server Provisioning for Bedrock

[Trellis](https://roots.io/trellis/) is the Roots team's Ansible-based tool for provisioning and deploying Bedrock sites to Ubuntu servers. It configures Nginx, PHP-FPM, MariaDB, SSL via Let's Encrypt, and deploys with zero-downtime releases.

If you manage your own VPS or cloud instances, Trellis handles the server setup that you would otherwise script manually. DeployHQ covers the deployment pipeline; Trellis covers the infrastructure. You can use one or both depending on your workflow — DeployHQ for teams that want a visual deployment dashboard and build pipeline, Trellis for teams that want infrastructure-as-code.

---

## Troubleshooting Common Issues

**Composer memory limits**: If the build fails with an "Allowed memory size exhausted" error, add `COMPOSER_MEMORY_LIMIT=-1` before the Composer command in your `.deploybuild.yaml`.

**Missing WordPress tables**: If you see "Error establishing a database connection" after the first deploy, check that your `.env` on the server has the correct `DB_HOST` value. Many hosting providers use a separate database hostname rather than `localhost`.

**Incorrect document root**: If you get a directory listing or 404 after deploying, verify that your web server's virtual host points to the `web/` directory inside your deployment path, not the deployment path itself.

**Plugin activation errors after deploy**: If a plugin fails after updating via Composer, check that the plugin's minimum PHP version matches your server. Run `composer why-not [package] [version]` locally to diagnose version conflicts.

**Stale object cache**: If changes do not appear after deployment, add `wp cache flush` to your post-deploy commands. Object caching plugins (Redis, Memcached) can serve stale data until explicitly cleared.

---

## Auto-Deployments

In your DeployHQ project, enable automatic deployments for your main branch. Every push triggers a build and deploy cycle — no manual intervention needed. For staging environments, connect a `staging` branch so your team can preview changes before they reach production.

Check out our other [deployment guides](https://www.deployhq.com/guides) for framework-specific configurations, or visit [DeployHQ support](https://www.deployhq.com/support) for detailed documentation on build pipelines, server types, and advanced configuration.

---

Ready to automate your Bedrock deployments? [Start your free trial of DeployHQ](https://www.deployhq.com/signup) — set up takes less than five minutes, and your first deployment can run today.

If you run into any issues, reach out to us at [support@deployhq.com](mailto:support@deployhq.com) or on [Twitter/X](https://x.com/deployhq).