Getting Started with Bedrock & DeployHQ
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 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:
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 WordPress Packagist to install plugins and themes as Composer packages:
composer require wpackagist-plugin/wordfence
composer require wpackagist-plugin/wordpress-seo
composer require wpackagist-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.
For premium plugins that are not on WordPress Packagist, add a private Composer repository or use 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:
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 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 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:
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 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 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 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:
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 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:
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 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 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 for framework-specific configurations, or visit DeployHQ support for detailed documentation on build pipelines, server types, and advanced configuration.
Ready to automate your Bedrock deployments? Start your free trial of DeployHQ — 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 or on Twitter/X.