An Intro to Bedrock for WordPress

Share this:

The following is a guest post by Alessandro Vendruscolo, who wrote to me excited to write a guest post about a WordPress tool that I didn't know much about: Bedrock. It's not a theme, it's a way to install, configure, and manage WordPress with security and modern development practices in mind.

Bedrock is an open source project made by Roots that you use as base for WordPress projects.

Why should you care about Bedrock? Let's take one step back and think about a typical way to work on a WordPress project.

Classic WordPress Workflow

You can download and install WordPress both on your local machine and production server. You can have a git repository for your theme. Deployment can be FTP transferring of theme or git pulling the repo from the server. For some projects, you might want to keep the entire site (even WordPress files) under git as well.

This has issues:

Updating WordPress/plugins from production may break your production site

You have to manually keep track of WordPress/plugin versions

Deploys can be cumbersome

Configuring WordPress is difficult, because the wp-config.php can't be under version control

Here's an example of what I mean. You update a plugin locally because you need some new functionality that the updated plugin provides. You write code based on that new functionality. Now it's time to deploy. You:

Install or update the plugin on the production site (make sure it's the same version!)

If it's a new plugin, make sure it's been activated on the production site

This is a rather manual approach and can be quite error prone. Most importantly, it can cause downtime or fatal errors. For instance, you update the plugin and it's not backwards-compatible, so it throws errors until the deployment is finished. Or you deploy first and the site throws errors until you update the plugin.

Enter Bedrock

If the issues above sound like something you have dealt with in the past, you'll like Bedrock. It brings:

You'll end up with a self-contained WordPress project that installs an explicit version of WordPress and required plugins (composer install) which can be easily configured. All configuration is kept in a .env text file) and deployed (cap production deploy).

Less troubles and more automation! Even onboarding a new developer is easier.

You also get a multistage environment for free. You can have a development configuration for working locally and, a staging configuration for tests, and a production configuration for the live site. You can also define custom configuration, such as backup where you can have a 1:1 copy of the production environment, on a different server.

Requirements

Here are the requirements for working with Bedrock:

You should be comfortable working on the command line.

You need shell access to the server: if your site is hosted on shared host you're probably out of luck1.

You need to have a working Ruby and gem environment on your machine to perform deploys.

You need to have WP-CLI installed locally and on the server if you plan to use it. It's not required, but you'll see later that's useful (install instructions).

This is in addition to all the normal WordPress server requirements, like MySQL.

Getting Started

Let's assume a brand new WordPress project. First, you'll need a copy of Bedrock, which you can get from its repository.

Should I clone or download the repo?

Bedrock's README tells you to clone the repository, but in my opinion you're better off downloading it. If it's something that you plan to regularly keep up to date, go for the clone route; if it's something that you plan to set-and-forget, go for the download route.

Keeping Bedrock up to date is certainly a good thing (improvements, security, etc) but it can be tricky. You may need to hand-pick changes or try to merge two upstreams. Since it is a starting point, downloading it means you can set it up once and forget about it.

Installation

Now that you have downloaded the repo, you can initialize your own repository for the project.

I keep all my web projects in the Sites folder in my home directory (I develop on OS X) and keep them indexed by their name. I use the `.dev` TLD so I can configure nginx with virtual hosts. This way, even when working locally, I have a configuration that's almost identical to the live website. (Bonus points compared to using localhost: you get a new environment to work on: cookies, local storage, URLs for caching, etc are not shared with any other site you previously worked on.)

On my machine, I use this minimal configuration to instruct nginx how to host the website. Please note that the root folder isn't the same as the project. Bedrock puts all public files in the web folder.

As you can see by the output, Composer will install dependencies listed in the composer.lock file, including WordPress itself2.

So, what's happening?

If you're new to Composer, I'll quickly explain what it is and how it works.

Composer is a tool written in PHP that lets you manage PHP dependencies (just like Ruby gems, npm modules, CocoaPods, etc). These days all languages have a dependency manager.

You declare the packages you need in the composer.json file, then declare them with constraints. Having constraints lets you define how Composer will update them when you run composer update.

For example, Bedrock requires an explicit version for WordPress (4.5 at the time of writing) and any version
greater than 2 but less than 3 for phpdotenv.

Every time you execute composer update Composer will try to update your packages to the latest version allowed, and will write down that version number to the composer.lock file.

Now, when you run composer install (just like you did earlier) it'll install that specific version no matter what, reading from the composer.lock file (This is a reason that using Composer is very safe, just like gem and CocoaPods, and unlike npm, unless you use the shrink-wrap feature).

If you now list the contents of the web and web/wp directory, you will see something you're familiar with: Composer has moved the wp-config.php file to web and all WordPress files are in the web/wp folder. If you inspect that file you can understand why it's there.

We've everything in place, it's now time to configure WordPress.

Copy the `.env.example` to `.env` and edit it:

cp .env.example .env
vim .env

It asks you for:

The name of the database you use (I'll use example_development)

The username that will be used to connect to the database (on my machine I have just one root user)

The password of that user

The host of the database (127.0.0.1 or localhost is fine)

The environment of this installation (development is fine, you can set whatever you want: usually you have development, staging, and production)

The homepage URL that WordPress will use to build URLs among other things (I set http://example.dev)

The URL for the admin page. Leave it as it is and you'll access WordPress admin pages at http://example.dev/wp. If you change this you'll also need to move the wp folder contents accordingly

After you've saved the file you can proceed to install WordPress (it needs to create all the tables in the database!). You can do this by visiting the website on http://example.dev/ or by using WP-CLI:

config: this is where you configure WordPress. These files can't be accessed from the Internet, because we set nginx to serve files from the web folder.

config/application.php: this file contains the usual WordPress configuration and is intended to include base settings that are common to all environments.

config/environments/*: these contain environment-specific settings. For example in production it disables errors output.

vendor: dependencies managed by Composer will be installed there, except WordPress plugins and themes; if you inspect the composer.json file you'll see that these kind of packages will be moved in web/app/{mu-plugins,plugins,themes}/.

web: files included in this directory are publicly available
— only the files that are required are in the web folder (see config above).

web/app: this is the old wp-content folder. It's been renamed to better reflect its content (this also matches other frameworks conventions, such as Rails and Symfony). This is where your plugins and themes will end up.

web/wp: the whole WordPress package. This should be put in vendor but can't be because of WordPress limitations.

web/wp-config.php: this file is well-known, but in Bedrock it acts as a loader (it loads settings from the config directory). It needs to stay here because WordPress core hard codes paths.

Configuration and environment variables

As we saw earlier we have multiple configuration files under the `config` directory, and we also have the `.env` file where we put the username/password for the database.

The key aspect is this: if it's something that's sensitive (a password or an external service access key) you should put that in the `.env` file and read that with the env('<name>') function. Sensitive information will never be stored in your git repository (the `.env` file is ignored by git) which is a huge benefit. It also allows you to define a different password/key for each machine. Imagine if every developer has to generate their Twitter consumer key or has to generate a new one for testing purposes: if the key is stored in a file that's tracked by git you have to remember not to add that file to the staging area.

The solution is to add it to the .env file:

[…]
DB_PASSWORD=<password>
[…]

and read that value in the configuration file:

define('DB_PASSWORD', env('DB_PASSWORD'));

Remember that environment-specific files (`config/environment/.php`) are required before the main (`config/application.php`). This means that you can't override settings. Say that you have the same configuration for development and staging but a different one for production. You can:

define it in every environment file (`development.php`, `staging.php`, `production.php`, etc).

put it in the `.env` file and define it in the main application (`application.php`) file.

Plugins

If your plugin is available in the official plugin registry, you can install using Composer and WordPress Packagist, which is a Composer repository that mirrors WordPress' official plugin and theme registry.

Bedrock already added wpackagist's repository, so installing a plugin is just a matter of running the following command to install the latest version:

Another difference is analytics. I only include Mint Analytics in production, using an approach similar to above. Google Analytics is available even in development or staging, but these two environments use a different site ID, so it's easier to perform tests.

Upgrading WordPress

At the time of this writing, WordPress is at 4.5.2. Let's say 4.5.3 version comes out. We need to change the required version. You can edit the composer.json file and run composer install or you can just issue the following command:

composer require johnpbloch/wordpress 4.5.1

It will update the `composer.json` file (and also the lock file, since we upgraded a dependency) and install the dependency.

Usually after each WordPress update there's a database schema upgrade to be performed which you can do by visiting the WordPress admin page. But that's tedious, so WP-CLI has a command to perform that:

wp core update-db

There's also another thing to keep in mind when upgrading the database schema: if you use the web UI to do that, and the operation takes too much time, you may encounter HTTP timeouts. WP-CLI skips the HTTP layer and performs the operations running PHP from the command line, which doesn't have any timeout issues.

Upgrading plugins

Upgrading plugins is similar to upgrading WordPress: if you used Composer to install them, use Composer to upgrade them.

You can run composer require wpackagist-plugin/<name> <version> to upgrade a given plugin to a specific version or, even better, use composer update to update every package to their latest version that satisfies the constraints set in the `composer.json` file.

When you composer require <package> something (without specifying a version), Composer will by default use the ^ constraint: running composer update will update the package to the latest version that's not a new major version. Easy peasy.

Deploying your project

Capistrano is the de-facto tool used for deploys, and you can use it to deploy WordPress sites.

What is Capistrano?

Capistrano is a powerful tool written in Ruby used mainly3 to perform deploys. Originally it was born to deploy Ruby on Rails web applications, but then it grew to deploy any kind of web application, with plugins to run custom tasks or connect to external services.

There are plenty of guides on the Internet and many conference talks, but to sum it up: Capistrano will connect to your server using SSH and then follow this flow to update a repository on the server and run tasks. You add hooks on every task to perform what you need.

It's designed to work with SSH keys (so you don't have to use passwords) and it should connect to the server as an unprivileged user. It can issue sudo commands, but if you need to do that, there's probably something wrong with your setup. You can learn more about this on the Authentication & Authorisation documentation page. I use SSH agent forwarding so Capistrano is able to pull from the git host using my local SSH key.

On the server, Capistrano works by keeping multiple directories in the directory you set as the target of deploys:

current: this is a symlink to the current version deployed (either the latest one or a previous one if you rolled back).

releases: every time you deploy Capistrano will create a new folder with a timestamp. You can configure the number of releases to keep, and Capistrano will clean up old releases (defaults to keeping 5 releases).

repo: Capistrano keeps the repository on the server and checkouts the correct revision on the server — nothing is transferred from your machine to the remote server.

revisions.log after every deploy or rollback this log will be updated.

shared: contains files and folders that must be shared between releases, such as user uploads.

Each deploy is run in isolation and has its own folder in the releases folder. If something goes wrong, nothing happens to the current version. When it's done, Capistrano will update the symlink to the version that was just deployed.

In your project you'll have a Capfile that requires the tasks to run (see the example from the flow) and a deploy.rb file that contains settings.

If you don't want to use Capistrano

Bedrock supports Capistrano, but it's not required. If Capistrano doesn't compel you, or you prefer to use a different tool, or prefer to use no tool at all, you're free to do so.

The only requirement is that you run composer install on the server, so Composer can pull down WordPress, plugins, and any other dependency your site needs. Otherwise you'll have a sort of zombie website with just application code but not the application itself.

Deploying with Capistrano

OK, I convinced you to try Capistrano, let's go on!

The Roots team (the team behind Bedrock) created a separate repository to host the Capistrano integration for Bedrock. This is because Capistrano is not required by Bedrock but can be added at any time.

Create or edit a Gemfile file in the root of the project and paste this content:

Now run bundle to have it install the gems. This differs a bit from Roots' setup, because we grouped these gem in the deployment group and also installed the WP-CLI extension.

Capistrano will be installed by Bundler and be treated as a dependency just like we did with WordPress and Composer earlier. This is useful because every project can use a different version of Capistrano and will not depend on a globally installed one. Also, if there are multiple developers on the team, a locally installed (and synchronized) version of Capistrano ensures that everything will be as smooth as possible4.

Commit both `Gemfile` and `Gemfile.lock` - just as with Composer:

git add 'Gemfile*' && git commit -m 'Install Capistrano'

Now we need to set up Capistrano. Capistrano's configuration will be kept in the git repo. You have to copy the Capfile file and the contents of the config directory from the repository. This is the equivalent of running bundle exec cap install and editing Capistrano's configuration files.

Since we also installed the WP-CLI plugin, we have to require it in the Capfile. Add the following line after we required the Composer plugin:

require 'capistrano/wpcli'

Edit the config/deploy.rb and set the :application and :repo_url variables and remove the :deploy_to line. If you want, you can also change other settings here. There are many code comments and the documentation is great.

The :linked_files and :linked_dirs variables are important. If you recall from earlier, Capistrano will create on the server a shared folder (shared between deploys). By default, we'll share the `.env` file and the `web/app/uploads` folder.

The `.env` file contains application settings and needs to be shared for the site to work, and also because this file isn't under version control. The `web/app/uploads` folder is the old `wp-content/uploads` folder and needs to be shared because otherwise uploads would exist only within a release.

You can probably ignore the rest of the config/deploy.rb file. After line 22 it defines two custom tasks. The first is empty and disabled by default. It can be used for example to restart the web server. If you have something to restart after a deploy, update the task and enable it.
The second task has been added by the roots team and is disabled by default. It's used update the stylesheet_root and template_root options of WordPress. I've never had a reason to change them.

Edit the `config/deploy/<stage>.rb` file to set per-stage settings.

You have to update the server setting, because the production env is likely to be on a different server than the staging one.

We'll move the :deploy_to path setting here, as your staging path is probably different from the production one. You can also override settings set in the main `deploy.rb` file. The rationale of these files is the same as Bedrock environment files.

Now we configured Capistrano and everything should work. Capistrano comes with a check task that, well, checks that everything works.

Run the following command to perform the check:

bundle exec cap staging deploy:check

Capistrano will:

Log in on the server with the user you defined (so you're sure the SSH connection works)

List repository remote files (so you know Capistrano is able to connect and authenticate with the git host)

Create the directory structure

Create linked folders

Check for linked files

Here's an example of a Capistrano error we might see:

ERROR linked file /path/to/:deploy_to/shared/.env does not exist on <host>

Create that file (you can use scp .env <user>@<host>:<value of :deploy_to>/shared/.env to upload your development `.env` file to the server. At this time, we're interested in having the file, not having the correct file) and when you're done check again that everything works. Hooray it works!

Push your changes (otherwise Capistrano will clone an empty or old repository) and run the following to perform a real deploy:

bundle exec cap <stage> deploy

This is the command that you'll need to remember and run every time you'll want to deploy your site.

Configuring the remote WordPress

You will have to update the `.env` file on the remote server to set the correct database information and, most importantly, the WP_ENV and WP_HOME settings.

If you deployed to production that must be WP_ENV=production (I don't know your WP_HOME value).

Your web server needs to serve contents from the current folder, so if locally you had this this setting for nginx:

root /Users/MJ/Sites/example.dev/web;

on the server you'll have to update it in this way:

root <value of :deploy_to>/current/web;

Installing WordPress on the remote server is just a matter of doing the same things you did on your local machine. You can use the web UI or use WP-CLI, even from within Capistrano.

When you deploy a new version and in that version you install a new plugin, you can use the following command to enable the plugin:

bundle exec cap <stage> wpcli:run['plugin activate <name>']

To perform database migrations:

bundle exec cap <stage> wpcli:run['core update-db']'

Hooks

You can define custom tasks for Capistrano to run, but before doing so, search for an existing solution. Capistrano comes with many official plugins, like Bundler, npm and many other community driven plugins.

For example, say you have a build script that compiles Sass assets. Because of the benefits we talked earlier, you should require Sass in the Gemfile with:

gem 'sass', '~> 3.4'

Now we need to do two things when we deploy:

Ensure the sass gem is installed

Run our own build script

Capistrano-bundler

The first step is easy to achieve: we'll require the capistrano-bundler gem by adding this line to the :deployment group:

gem 'capistrano-bundler', '~> 1.1.2'

After that you run bundle to install the gem and also edit the Capfile to require it:

require 'capistrano/bundler'

Done. Every time we deploy, Capistrano will run bundle install on the server, after the deploy:updated task.

If you want, set the :bundle_without variable in the `deploy.rb` file:

set :bundle_without, %w{deployment development test}.join(' ')

Doing so will exclude those groups of gems from being installed. Bundler will install less gems (that we won't need!) and our deploys will be faster.

Running custom scripts

I don't know how your build script is structured. I'm a fan of make, but you can have anything you want: a shell script, npm scripts, Grunt/Gulp tasks, etc. No matter what, we have to run them, otherwise our site will be without assets.

We can create a custom task easily. Create your .cap (e.g. make.cap) file in lib/capistrano/tasks to have it automatically loaded (otherwise you'd have to explicitly require from the Capfile) with this content:

namespace :make do
desc "Runs make all"
task :all do
on roles :all do
within release_path do
execute :make
end
end
end
before 'deploy:updated', 'make:all'
end

Capistrano will run our task before the deploy:updated task. Since we used the within release_path do … bit, this command will be executed in the correct release/<timestamp> directory that Capistrano is deploying.

Check out Capistrano's documentation for more. Hot tip: be sure to use the colon before any argument (it has to do with shell escaping).

Adding a new stage

Bedrock comes with three default environments:

Development (Bedrock only)

Staging (Bedrock and Capistrano)

Production (Bedrock and Capistrano)

You can add a new stage if you need to. An example could be a backup environment that is 100% equal to the production one. The database could be synced periodically or you could setup MySQL replica functionality. In case of failure you'd update your DNS to point to the different host and nobody will notice anything.

To add a new stage:

Create a new environment for Bedrock: `config/environment/backup.php` (you can copy an existing one and update what's needed)

Create the Capistrano stage configuration: `config/deploy/backup.rb` (again use an existing one as a base)

Migrating an existing project to Bedrock

In this tutorial we created a new project, but Bedrock can be used with existing projects.

If you have an existing WordPress site and want to convert it to Bedrock, you can treat it as if it was a new project:

Install Bedrock

Configure WordPress

Restore your theme

Install plugins with Composer

If you didn't modify WordPress core files you're done. If you modified WordPress core files you'll have to keep your custom WordPress version under version control (you'll lose easy-Composer-driven WordPress updates, but still have every other feature).

Gotchas and tips

Some tips:

Remember that if [something] created [something else], you shouldn't manage it. This includes for example: files created by plugins, files installed by a package manager, files uploaded by users, etc — don't add them to git. If you ignore them, there's also an high chance they should end up in the linked_files setting of Capistrano.

Don't store sensitive information in git.

Don’t forget to git push before deploying: I've deployed multiple times the same version before realizing that I forgot to push (on the server Capistrano was pulling the same version over and over again).

Use WP-CLI whenever possible: it's faster and can be executed by Capistrano. Examples include:

wp plugin activate <name>

bundle exec cap <stage> wpcli:run['plugin activate <name>']

wp core update-db

bundle exec cap <stage> wpcli:run['core update-db']

You can set Capistrano branch (config/deploy.rb) to set :branch, ENV['BRANCH'] || :master: doing so lets you easily deploy a branch by doing BRANCH=my-feature bundle exec cap <stage> deploy (remember to push, see tip #3); if you don't provide the BRANCH=<branch> environment variable, Capistrano will fall back to deploying master.

Using Slackistrano you can post to a Slack channel after every successful deploy.

Recap

I hope you followed and enjoyed this tutorial. I encourage you to create a new site on your local machine to have something to experiment with.

To recap, these are the advantages that Bedrock brings:

Self-contained repository

Easy deploys

Easy multi-stage support

More consistency between environments

Separation between configuration files and everything-else

Congrats on making it through this tutorial. Sorry if it was long and difficult. It's definitely not for beginners! Hopefully along the way you learned some new tools and leveled up your understanding of all the different moving parts.

The benefits of working like this are well worth it, in my opinion. Now go create something nice!

[^1]: That's not because you can't ssh <user>@<host> (some hosts permit that) but because you can't install some of the required software — if you're using a script that runs on the server to build the assets you're probably fine.

[^2]: Unfortunately at this time it doesn't exist an official version of WordPress distributed via Composer. Bedrock uses this fork that syncs every 15 minutes.

[^3]: In fact, you can deploy whatever you want with Capistrano: you're not constrained to web apps, you could deploy iOS apps to a remote server, if that makes sense to you.

[^4]: Later you'll see cap commands prefixed by bundle exec: doing so ensures that the local gem is used instead of a globally installed one (which might not exist). You should do the same for Sass or any other gem.

Sage + Bedrock + Trellis makes building and deploying WordPress sites/themes a breeze. It is definitely my go to stack from here on out when I have control over the matter. Bravo to the Roots team for their hard work, and thanks for shedding some light on this, Alessandro and Chris.

Used roots on a couple occasions but after a high learning curves and countless bumps in the road due a lot of steps not being covered in the instructions finally got it all working. I think it’s definetly best of breed but overall it’s definetly overkill for solo devs or small projects because with all RH overhead it makes you waste far more time with everything than doing a more manual way.

The documentation for everything has come a long way. I will admit that I didn’t start using the Roots stack until late 2015, so I probably missed many of the growing pains. Plus, everyone in their Discourse forum is usually pretty helpful, and most of my questions/issues were answered there.

I don’t have much experience with WordPress, but based on my experience with other PHP CMSs, it often is the case that a lot of configuration of plug-ins and themes goes in to the database. So the changes I want to make are in the database of the stage server, and are hard to reproduce on production with sane version control. I’ve resorted to dumping the database into a file and storing that in Git, but that gives you very ugly diffs, and there’s no good way to merge database changes made in production with database changes made on stage.

Dumping database content in git and merging that seems like a great way to break your production environment.

Talking about WordPress there are some great options(and other cms’s possibly even better options)

Custom post type UI: Do not use, it’s really easy to just make custom post types etc in php functions. (saved as inc/cpt/name.php)

ACF (the goto plugin to use for additional fields anywhere): This plugin has a great export function to either json or php. During development I’ve set up ACF to export everything to JSON automatically so in case I go missing, others can still find what I’m working on. When I’m done setting up most or all fields of an ACF-group I export the group to PHP(saved as inc/acf/namespace.groupname.php) and delete the dev version. Saving to PHP allows me to include translations for all admin labels.

Other plugins: Usually the rest is already different per environment(like tracking codes or cache settings) but if you must, most plugins save config the right way inside wp_options. You can easily write hooks to either composer, capistrano, (wp-)cli or wordpress actions to update/add settings to wp_config. It would be easy enough to write a single array with all settings that should always be forced upon every environment.

Other content: You should probably not feel the need to keep content across environments exactly the same. Otherwise there are probably some tools around there to handle database syncs between enviroments. If not; I’m for hire.

In case you use downloaded themes from a place like themeforest with a lot of custom config a bedrock(-like) setup is probably already overkill.

So excited to see the Roots team get some props here since I have been using their startup theme for several years now. More importantly, thanks to this article I get a chance to learn how to work with Bedrock. Thanks for this!

This comment thread is closed. If you have important information to share, please contact us.

Related

How do you stay up to date in this fast⁠-⁠moving industry?

A good start is to sign up for our weekly hand-written newsletter. We bring you the best articles and ideas from around the web, and what we think about them.

👋

CSS-Tricks* is created, written by, and maintained by Chris Coyier and a team of swell people. It is built on WordPress and powered up by Jetpack. It is made possible through sponsorships from products and services we like.