Building WordPress with Sage + Bedrock + Trellis

By Nick Taylor on

Sage, Bedrock and Trellis is a trio of utilities created by roots.io to help you “build better WordPress sites”. I used all three of these to develop the blog that you’re reading right now. They’ve made my whole WordPress development and deployment experience much nicer from start to finish.

I will give you a rundown of each of the parts of the puzzle and how you can get started with each.

On the whole, I found the documentation provided by roots.io to be great and it was easy to get up and running quickly. I had some small issues when using Trellis for deployment, which I’ll also cover along the way.

TL;DR, if you don’t have time…

Trellis: Provides you with deployment scripts to completely set up everything you need on a fresh install of Ubuntu 18.10 whether this is locally or on a remote server. Trellis installs and sets up Nginx, MySQL and PHP, among others, for you. It also provides you with a local environment utilising Vagrant and VirtualBox. Finally, there is a separate script for deploying each WordPress site.

Bedrock: A clean, reorganised WordPress installation, utilising Composer to install all your dependencies, including plugins.

Sage: A base WordPress theme, with a basic structure for the main templates provided. It utilises Laravel’s Blade for the templates, SASS for styles, WebPack for building along with BrowserSync for development. Everything has been nicely organised so everything’s easy to find and in a logical location.

Trellis for automated server provisioning and deployment

Trellis is used to keep your environment setup identical across development, staging and production. It uses Vagrant, VirtualBox and Ansible as its main components to automatically create each environment for you. Ansible Playbooks are your configuration scripts containing the instructions on what to install and how to install them. Both server provisioning and site deployment utilise these scripts.

Development environment

Vagrant and VirtualBox are the main components of your development environment. After cloning the project you start your development environment by typing vagrant up from the terminal within the main trellis folder. If this is the first run, it will set up an Ubuntu 18.10 installation within VirtualBox along with all the required software, Nginx, MySQL etc and that’s it. You now have a WordPress development environment up and running. It even changes your hosts file for you based on the domain you set in the group_vars/developments/wordpress_sites.yml file.

You’ll notice that a Bedrock site needs to be created alongside Trellis. We’ll discuss Bedrock next, but for now, just know it’s a modified WordPress setup.

Debugging is also ready to rock and roll out of the box with xDebug set up already. I came across an issue when attempting to debug. The latest version of xDebug is 3.0, however, the Ansible script is set up for xDebug 2. To resolve, I had to change the Ansible scripts manually, copied across from this pull request.

Production environment

For production, you will need a host machine with Ubuntu 18.10 installed, but that’s it. Remote server set up is a bit more involved than the development setup, so I won’t go into detail here. You can find full instructions over here.


Once you’ve set this up for, say, AWS, it’s a doddle from then on to provision new environments. Running the Ansible Playbook will fully provision the server with everything you need. If, for example, you need to install additional software, you can add these to the Ansible scripts. You can run a Playbook against the same server multiple times, so you can simply rerun to get that extra software installed.

The biggest issue I had initially was when running the Ansible Playbooks they would fail to connect via SSH. For AWS, I converted the SSH key PEM file to a PUB file:

ssh-keygen -y -f your_aws_key.pem > ~/.ssh/your_aws_key.pub

Then I added the following to the ~/.ssh/config file, replacing your hostname, hostname IP and AWS key PUB file accordingly:

Host your.host.name  
  HostName  # Your host IP
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/your_aws_key.pub

There may be a better way of doing this. When I specified the PUB file within the config it said the public key was denied and therefore the provisioning failed. This problem is likely specific to an AWS setup.

Should you require additional information to help you debug a deployment or provisioning issue, simply add --verbose to the end of the command.

A Let’s Encrypt SSL certificate will be automatically applied, when enabled, and auto-renewal set up for you so you never need to worry about it expiring.


Running the Ansible Playbook only provisions the server with everything required to run WordPress, your site has not yet been deployed. To deploy your site, you run the following command:

./bin/deploy.sh <environment> <domain>

That was simple, right? And you can set up multiple sites within the wordpress_sites.yml file by simply adding a few extra lines of configuration.

One thing to note, this doesn’t deploy the content for you from development to staging or production. You will start off with a completely blank database and therefore you will need to set up users, date/time format, activate your theme and plugins etc. For plugin activation, you could add this to your theme’s functions.php file as described here. Note that this does then couple your theme with the associated plugins, which you might not want to do.

Bedrock a WordPress base installation

I’ll take Bedrock first, as that, as the name suggests, provides the groundwork for the rest. Roots.io have taken a standard WordPress installation and better organised it for you out of the box. To install Bedrock you need Composer installed first and then run the following command:

composer create-project roots/bedrock 

You are provided with a better-organised project structure. This lays the foundation for you to use Composer for all your dependency management. I’ve taken the following directly from the Bedrock homepage:

├── composer.json
├── config
│   ├── application.php
│   └── environments
│       ├── development.php
│       ├── staging.php
│       └── production.php
├── vendor
└── web
    ├── app
    │   ├── mu-plugins
    │   ├── plugins
    │   ├── themes
    │   └── uploads
    ├── wp-config.php
    ├── index.php
    └── wp

To start with you may notice that WordPress has been installed into its own directory called ‘wp’ in the ‘web’ directory. At the same level as this, there’s a folder called ‘app’ which is the ‘wp-content’ folder as you know it, where all your plugins and themes will be installed. I think this is a good way to organise things, keeping your custom code away from the main installation. This is a great way to organise things, separating the configuration, dependencies and content rather than it all being bundled in together.

What about your own plugins?

As I mentioned, all plugins should be installed using Composer wherever possible and should not be installed via the WordPress admin. This allows you to create a fully automated deployable installation. I’d therefore recommend turning off the ‘Plugins’ section for everyone other than the admin and only be accessible codebase’s maintainer.

If you have your own plugin in a private repository, that’s no problem at all either. I added a repository in this way by adding the following lines in bold to the composer.json file:

"repositories": [
    "type": "svn",
    "url": "git@github.com:<username>/<reponame>.git"

The plugin you are referencing must have its own composer.json file at the root of the project. Set the type as ‘wordpress-plugin’ as this will ensure it is installed in the new plugin directory and not in the vendor folder. The location of installation is within the ‘extra’->’installer-paths’ section of the Bedrock composer.json file.

This step is vital if you plan to deploy using Trellis and the plugin is not available publicly within the WordPress ecosystem.

There’s one final step you’ll need to do in order to complete the deployment of your plugins in this way. You generally do not add the distribution files to source code, that therefore leaves the code in place but without the compiled resources you need. In Trellis, within the deploy-hooks folder, you will see a file called build-before.yml. Within this file, there’s already an example script for installing the theme dependencies and building any dependencies using yarn. I took this and changed the paths and commands accordingly for the plugin I hat created, so you’ll be able to do something similar.

Sage a starter theme

I started with Sage after looking for an alternative starter theme to Underscore. This was out of pure curiosity of what else is available rather than there is a specific problem with Underscore. Sage was a recommendation that I saw popping up wherever I looked. It uses WebPack for building, Laravel’s Blade for its templates, Sass for styling and adds controller support within the templates. I found this provided a great base, easing you into good structured and maintainable code out of the box.

Sage is super simple to get up and running once you have composer and a Node package manager installed:

# Create a new Sage project
$ composer create-project roots/sage

# Development theme build with live reloading and injection via Browsersync
$ yarn start

# Production theme build
$ yarn build:production

During the Sage setup, you will be asked whether you would like to install a CSS framework such as Bootstrap or Foundation. Also, you are provided with the option to start from a blank slate, the latter is what I went with. I’m a bit old school when it comes to using frameworks, so tend not to include one unless absolutely necessary. These days we have grid, with grid-gap, and flexbox, which allows us to do everything the frameworks do, and more. So, depending on the needs of the project, I find it quicker to develop without them. I understand there’s a reusability aspect to frameworks as well, but I won’t even begin to go into that discussion here.

There is one issue that I am yet to resolve and it is a little frustrating. BrowserSync runs to watch for any changes to your CSS, refreshing the browser when this happens. The project appears to be set up to refresh your browser when a template file is updated, however, it doesn’t refresh. Should I find out how to make this work, I’ll update this article accordingly.

That’s a wrap!

That just about sums it all up. This toolset created by Roots should be kept in mind whenever you decide to boot up a WordPress installation. Don’t get me wrong, I liked using Local by Flywheel for local development, but then you’re restricted to their services for the automated deployment side.

Finally, Roots have a few other bits and pieces in development. Acorn is in active development as a framework for plugin development. Bud will help you create the scaffolding for Gutenberg blocks. Finally, Clover is to provide “plugin boilerplate with a modern development workflow”.

Needless to say there’s some interesting and useful stuff coming from Roots and I have no doubt this will continue to grow.