# How to use Bedrock with Pantheon

*Disclaimer: I wasn't paid by [Pantheon](https://pantheon.io/) or [CircleCI](https://circleci.com) to write this. I am, however, part of the [Pantheon heroes program](https://community.pantheon.io). I just like [Bedrock](https://roots.io/bedrock/), CircleCI and Pantheon a lot so I wanted to write about them.*Working on large projects with WordPress is often a challenging endeavour. You have a lot of plugins to manage as well as the custom code that you've written for your client. All this code often exists in a single [git](https://en.wikipedia.org/wiki/Git) repository. This makes the whole thing complicated to maintain. Lucky for us, there's [Bedrock](https://github.com/roots/bedrock). Bedrock is a custom WordPress project structure based on the [Twelve-Factor App methodology](https://en.wikipedia.org/wiki/Twelve-Factor_App_methodology). Using such a project structure lets you work using modern web development methodologies. Without a doubt, the foundation of modern web development is the dependency manager. For example, it's impossible to do modern JavaScript without using [npm](https://www.npmjs.com/). This is the dependency management tool used by the JavaScript ecosystem. In PHP, that dependency management tool is [Composer](https://getcomposer.org/). While WordPress has no current plans to support Composer, Bedrock has it as its central feature. This is great for WordPress developers who want to use modern web development methodologies. Now, the problem with using a project structure like Bedrock is that a lot of hosts don't have support for Composer. There's a [guide](https://kinsta.com/blog/bedrock-trellis/) on how to use Bedrock with [Trellis](https://github.com/roots/trellis) on [Kinsta](https://kinsta.com). But besides that, there isn't much out there to help you setup Bedrock with a host. Since I have experience with the Pantheon platform, I figured I'd write about how to get Bedrock on it. ## Using a continuous integration process

Now, like most hosts, Pantheon doesn't have native support for Composer. It also doesn't support direct [SSH](https://en.wikipedia.org/wiki/Secure_Shell) access. This prevents us from using Trellis or other [automated deployment tools](https://carlalexander.ca/introduction-automated-wordpress-deployments/) that depend on SSH access. Instead, we're going to have to rely on a [continuous integration process](https://carlalexander.ca/continuous-integration-wordpress/). It'll be in charge of putting everything together and sending it to the Pantheon servers. Here's a graph describing that process: ![](https://d3nw5tt5i784nr.cloudfront.net/uploads/2019/06/bedrock-pantheon-graph.png)Starting from the left side, we have the [version control system](https://en.wikipedia.org/wiki/Version_control_system) where our Bedrock project resides. For most of us, that'll be a [git](https://en.wikipedia.org/wiki/Git_(software)) [repository](https://en.wikipedia.org/wiki/Repository_(version_control)) hosted on one of the git repository hosting services. For most of us, that service is [GitHub](https://en.wikipedia.org/wiki/GitHub). But you can use whichever service you prefer. Whenever you push a commit or do a [pull request](https://en.wikipedia.org/wiki/Distributed_version_control#Pull_requests) to a repository hosted on either service, the continuous integration service will start the [build process](https://en.wikipedia.org/wiki/Software_build). The build process will combine the code in our Bedrock project with the dependencies what we're managing by Composer. It'll then deploy everything to the Pantheon platform on the right. Most of the heavy lifting in the build process is done by [Terminus](https://pantheon.io/docs/terminus/). It's a [command-line tool](https://en.wikipedia.org/wiki/Command-line_interface) that Pantheon built to interact with their platform. We're going to be talking a lot about it throughout the rest of this article. Now, you might not want to use a continuous integration service. We'll also discuss how you can use Bedrock and Pantheon without it. However, as you'll see in a moment, there are some serious drawbacks to using Bedrock and Pantheon without a continuous integration service. # Pre-made Bedrock project templates

Alright! So this covers the high-level look at how to get Bedrock working with Pantheon. Before we get started looking at the implementation, you should know that I already created two Bedrock project templates for you. So you don't have to worry about doing this yourself, you can just refer to them instead. We're going to go over both project templates in some detail. That said, you should refer to the [README](https://en.wikipedia.org/wiki/README) file for both projects for instructions on how to use them. The article will only focus on explaining how both project templates work. ## Standalone project

The first Bedrock project template is the [standalone one](https://github.com/carlalexander/bedrock-pantheon-standalone). It forgoes the build process using a continuous integration service. This is a handy setup if you're not interested in using a build process to deploy to the Pantheon platform. Instead, you can just SFTP or `git push` your charges to the Pantheon servers yourself. That said, using this Bedrock project template comes with some pretty significant tradeoffs. That's because foregoing the build process means that you're responsible for it now. This translates into a few important changes to the project template compared to a regular Bedrock project. The first and most significant one is that you have to commit the `vendor` and `web` directories. These are directories that Composer normally manages. But, since Composer isn't available on Pantheon servers and we have no build process, we have to commit them into our git repository. You also need to create a symlink for the `uploads` directory. That's because Pantheon stores uploads in a different directory on their servers. This also means that you'll have to create the same directory as the Pantheon servers on your development machine. You'll find more on all this in the README of the standalone project template that I linked at the beginning of the section. But, as mentioned, this isn't the ideal way to use Bedrock with Pantheon. You're trading the complexity of using a continuous integration process for complexity in your Bedrock project structure. ## Project using CircleCI

The [other Bedrock project template](https://github.com/carlalexander/bedrock-pantheon-circleci) is the one that we'll go over for the rest of the article. It uses CircleCI as its continuous integration service. CircleCI will be in charge of building our Bedrock project and package its dependencies. It'll then sends everything to the Pantheon platform. The advantage of doing things this way is that you can just use Bedrock as is. You don't have to make any significant changes to it like you had to do with the standalone version. This also makes your project platform agnostic so you can move away from Pantheon in the future if you have to. Now, the project uses CircleCI as its continuous integration service, but that's not a hard requirement. A lot of what we'll go over isn't specific to CircleCI itself. And the rest can be easily changed to fit another continuous integration service as well. So don't feel like you have to use CircleCI if you don't want to! # Analyzing the CircleCI continuous integration workflow

Now, that we've gone over the two sample Bedrock projects. We're going to move on to the continuous integration workflow used by the CircleCI Bedrock project template. With CircleCI, you define this continuous integration workflow in the `.circleci/config.yml` file in your project repository. ```
version: 2.0

references:
  working_directory: &working_directory
    ~/bedrock

  # Default container configuration
  #
  container_config: &container_config
    docker:
      - image: circleci/php:7.3
    working_directory: *working_directory

  attach_workspace: &attach_workspace
    attach_workspace:
      at: *working_directory

jobs:
  build:
    <<: *container_config
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: composer install -n -o
      - persist_to_workspace:
          root: .
          paths:
            - '*'

  test:
    <<: *container_config
    steps:
      - *attach_workspace
      - run:
          name: Run tests
          command: composer test

  deploy:
    <<: *container_config
    steps:
      - *attach_workspace
      - run:
          name: Remove development dependencies
          command: composer install --no-dev -n -o
      - run:
          name: Deploy to Pantheon
          command: .circleci/deploy_to_pantheon.sh

workflows:
  version: 2
  build_test_deploy:
    jobs:
      - build
      - test:
          requires:
            - build
      - deploy:
          requires:
            - test
```

Above you can see a copy of the `.circleci/config.yml` file from the Bedrock project template. The file is a simpler version of the one designed to [deploy a plugin to the WordPress directory using CircleCI](https://carlalexander.ca/continuous-deployment-wordpress-directory-circleci/). You don't have to worry about reading that article. (It's the longest one on this site!) We'll go over everything here. ## References

The top of the `config.yml` file is the `references` section. This isn't a normal section that you'll see in the CircleCI documentation. Instead, it's there to take advantage of a YAML feature that a lot of people don't know about. We call it "YAML anchors" (and references). (Bitbucket has a [small knowledge base article](https://confluence.atlassian.com/bitbucket/yaml-anchors-960154027.html) on it for their pipeline continuous integration service.) Each subsection of the `references` section (shown below) is an anchor to a reusable part of our CircleCI configuration. ```
# .circleci/config.yml

references:
  working_directory: &working_directory
    ~/bedrock

  # Default container configuration
  #
  container_config: &container_config
    docker:
      - image: circleci/php:7.3
    working_directory: *working_directory

  attach_workspace: &attach_workspace
    attach_workspace:
      at: *working_directory
```

The first anchor is `working_directory`. We use it to define the working directory that CircleCI continuous integration jobs will use. We called the working directory `bedrock`, but you can call it whatever you'd like. The next anchor is `container_config`. We use it to configure the [Docker](https://en.wikipedia.org/wiki/Docker_(software)) container that all our continuous integration jobs will use. We tell it to use the `circleci/php:7.3` docker image. This is the [pre-built CircleCI docker image](https://circleci.com/docs/2.0/circleci-images/) for PHP 7.3. We also set the `working_directory` by using the `working_directory` reference. The last reference relates to the use of [workspaces](https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs). Workspaces are one of the two persistence mechanism available on CircleCI. The other is [caching](https://circleci.com/docs/2.0/caching/). (You can read about the difference between the two [here](https://circleci.com/blog/persisting-data-in-workflows-when-to-use-caching-artifacts-and-workspaces/).) Why do we do workspaces instead of caching? This comes down to personal preference. Since Bedrock uses a `composer.lock` file, it's possible to use caching with it. (Something that wasn't possible when using CircleCI with a plugin.) But caches in CircleCI are [immutable](https://en.wikipedia.org/wiki/Immutable_object). This means that you can't change or delete them once they're created. (You can read more about it [here](https://circleci.com/docs/2.0/caching/#writing-to-the-cache-in-workflows).) This makes them a lot less forgiving to use than workspaces. That's why we're not going to use them, but you're free to do so if that's what you prefer! ## CircleCI jobs

Now that we've covered the `references` section, we can move on to the section in the `config.yml` file. That's the `jobs` section. We use it to define all the jobs in our CircleCI continuous integration workflow. ```
# .circleci/config.yml

jobs:
  build:
    <<: *container_config
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: composer install -n -o
      - persist_to_workspace:
          root: .
          paths:
            - '*'

  test:
    <<: *container_config
    steps:
      - *attach_workspace
      - run:
          name: Run tests
          command: composer test

  deploy:
    <<: *container_config
    steps:
      - *attach_workspace
      - run:
          name: Remove development dependencies
          command: composer install --no-dev -n -o
      - run:
          name: Deploy to Pantheon
          command: .circleci/deploy_to_pantheon.sh
```

You can see all the jobs in the `jobs` section above. But don't worry if you're not too sure what this all means. We're going to go over each of them in the following sections. ### Build job

The first job in the `jobs` section is the `build` job. This is the job that'll install all the dependencies in our `composer.json` file. Once installed, it'll save everything to the workspace that we'll use for the rest of the workflow. ```
# .circleci/config.yml

jobs:
  build:
    <<: *container_config
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: composer install -n -o
      - persist_to_workspace:
          root: .
          paths:
            - '*'
```

The `build` job which you can see above starts by referencing our `container_config` anchor. This is the default Docker container configuration that the `build` job will use. It comes from the `references` section that we saw before. #### Build job steps

Next, we have the `steps` section. This is where we define all the steps that the `build` job has to perform. There are three here. First, there's [`checkout`](https://circleci.com/docs/2.0/configuration-reference/#checkout). This is a special step that CircleCI uses to check out our code from our git repository service. We're only going to do this once. After that, we'll leverage the use of workspaces. Next, we use composer to install all the dependencies in the project's `composer.json` file. This includes all development dependencies under [`require-dev`](https://getcomposer.org/doc/04-schema.md#require-dev). We need those for testing purposes, but we'll clean them up before deploying to Pantheon. The last step of the `build` job is to save everything to the workspace that we'll use in our workflow. To do that, we're going to use the [`persist_to_workspace`](https://circleci.com/docs/2.0/configuration-reference/#persist_to_workspace) special step. We have to specify both a `root` as well as a list of `paths` relative to the `root` to save to the workspace. Since we want to save everything in our working directory, our `root` will be `.`. The [dot symbolizes your current directory](https://en.wikipedia.org/wiki/Path_(computing)#Absolute_and_relative_paths) in Linux and Windows operating systems. For the list of `paths`, we only need to use `*` which means everything. ## Test job

So that covered the `build` job. After it, we have the `test` job. This job will run all the test for our Bedrock project. ```
# .circleci/config.yml

jobs:
  test:
    <<: *container_config
    steps:
      - *attach_workspace
      - run:
          name: Run tests
          command: composer test
```

The `test` job starts by referencing the `container_config` anchor much like the `build` job did. After that, we have the `steps` section. It's a lot simpler than the one for the `build` step. The first thing that we do is restore everything that we saved to our workspace to our working directory. To do that, we use the [`attach_workspace`](https://circleci.com/docs/2.0/configuration-reference/#attach_workspace) special step. This is the last anchor in our `references` section that we hadn't seen yet. The `attach_workspace` step will restore the workspace that we saved earlier in our working directory. We specify where to restore the workspace with the `at` parameter. We pass the `working_directory` reference to it. This tells CircleCI to restore the workspace there. Once the workspace restored, we just have to run the tests for our Bedrock project. By default, bedrock projects have a [custom Composer command](https://getcomposer.org/doc/articles/scripts.md#writing-custom-commands) in their `composer.json` file. This `composer test` command is the command that CircleCI will use to run all the test for our Bedrock project. If you want to edit it, it's under the [`scripts`](https://getcomposer.org/doc/04-schema.md#scripts) section in the `composer.json` file. ## Deploy job

The last job in our CircleCI continuous integration workflow is the `deploy` job. It's the job that will take our tested Bedrock project and deploy it to the Pantheon platform. From a configuration perspective, it's very similar to the `test` job. ```
# .circleci/config.yml

jobs:
  deploy:
    <<: *container_config
    steps:
      - *attach_workspace
      - run:
          name: Remove development dependencies
          command: composer install --no-dev -n -o
      - run:
          name: Deploy to Pantheon
          command: .circleci/deploy_to_pantheon.sh
```

We again start by referencing our `container_config` anchor and reattaching our workspace. The next step is to run `composer install` with the `--no-dev` flag. This will remove all the development dependencies that we had for testing. This will remove all the development dependencies that we had for testing. It's better to not have those as part of our deployment. The final step is to deploy all our code to the Pantheon platform. This is all done using the `deploy_to_pantheon.sh` [Bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) script. You can find it in the `.circleci` directory. ### Pantheon deployment script

Since the `deploy_to_pantheon.sh` Bash script does all the work deploying our Bedrock project to Pantheon, we're going to over it. The good news is that most of the script is generic. So you can use with other continuous integration services and not just with CircleCI. You can see the full complete `deploy_to_pantheon.sh` Bash script below. ```
#!/usr/bin/env bash

if [[ -z "$CIRCLECI" ]]; then
    echo "This script can only be run by CircleCI. Aborting." 1>&2
    exit 1
fi

if [[ -z "$TERMINUS_SITE" ]]; then
    echo "Terminus site not set. Aborting." 1>&2
    exit 1
fi

if [[ -z "$TERMINUS_TOKEN" ]]; then
    echo "Terminus token not set. Aborting." 1>&2
    exit 1
fi

# Add global composer bin directory to $PATH variable
export PATH=$HOME/.composer/vendor/bin:$PATH

# Configure git
git config --global user.email "${GIT_EMAIL:-pantheon@circleci.com}"
git config --global user.name "${GIT_NAME:-Circle CI}"

# Configure SSH
mkdir -p "$HOME/.ssh"
touch "$HOME/.ssh/config"
echo "StrictHostKeyChecking no" >> "$HOME/.ssh/config"

# Convert uploads directory to a symlink
sed -i '/web\/app\/uploads.*/d' .gitignore
rm -r web/uploads
ln -s ../../../files web/uploads

# Install Terminus globally
composer global require pantheon-systems/terminus:^2.0

# Install Terminus plugins
mkdir -p $HOME/.terminus/plugins
composer create-project -n -d $HOME/.terminus/plugins pantheon-systems/terminus-build-tools-plugin:^2.0.0-beta13

# Authenticate with Terminus
terminus auth:login -n --machine-token="$TERMINUS_TOKEN"

# Wake up the main development environment
terminus env:wake -n "$TERMINUS_SITE.dev"

# Push code to Pantheon
if [[ ${CIRCLE_BRANCH} == "master" ]]; then
    terminus build:env:push -n "$TERMINUS_SITE.dev"
elif [[ ! -z "$CIRCLE_PULL_REQUEST" ]]; then
    terminus build:env:create -n "$TERMINUS_SITE.dev" "pr-${CIRCLE_PULL_REQUEST##*/}"
fi

# Clean up unused PR environments (if GITHUB_TOKEN is set)
if [[ ! -z "$GITHUB_TOKEN" ]]; then
    terminus build:env:delete:pr -n "$TERMINUS_SITE" --yes
fi
```

#### Environment variables

The script starts with a few [guard clauses](https://carlalexander.ca/mastering-php-conditionals/). These guard clauses check for environment variables that the script needs. You can find the section of the Bash script with the guard clauses below. ```
if [[ -z "$CIRCLECI" ]]; then
    echo "This script can only be run by CircleCI. Aborting." 1>&2
    exit 1
fi

if [[ -z "$TERMINUS_SITE" ]]; then
    echo "Terminus site not set. Aborting." 1>&2
    exit 1
fi

if [[ -z "$TERMINUS_TOKEN" ]]; then
    echo "Terminus token not set. Aborting." 1>&2
    exit 1
fi
```

The reason we have these guard clauses is because these environment variables are sensitive. We would create a security vulnerability by storing them in our project. Instead, we have to add them in your continuous integration service. (You can read on how to do that with CircleCI [here](https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-project).) The first environment variable that we check is `CIRCLECI`. This is the environment variable that tells us that the script is running on the CircleCI platform. This check is optional. If you're using another continuous integration service, you can either remove it or use another environment variable for that service. The other two environment variables are `TERMINUS_SITE` and `TERMINUS_TOKEN`. We use them with Pantheon's Terminus command-line tool. `TERMINUS_SITE` is the name of the site on the Pantheon platform. Meanwhile, `TERMINUS_TOKEN` is the token used by Terminus to authenticate with the Pantheon platform. The README of the Bedrock project template explains where you can find these values on the Pantheon platform. It also shows you how to add them to CircleCI. If you're using another continuous integration service, you'll have to find the documentation for adding environment variables for that service. #### Configuring Composer, git and SSH

Once through the guard clauses, the script begins to do its work. The first thing it does is configure the command-line tools that we'll use for deploying our Bedrock project. The three command-line tools that we'll use to deploy our Bedrock project are Composer, git and SSH. ```
# Add global composer bin directory to $PATH variable
export PATH=$HOME/.composer/vendor/bin:$PATH

# Configure git
git config --global user.email "${GIT_EMAIL:-pantheon@circleci.com}"
git config --global user.name "${GIT_NAME:-Circle CI}"

# Configure SSH
mkdir -p "$HOME/.ssh"
touch "$HOME/.ssh/config"
echo "StrictHostKeyChecking no" >> "$HOME/.ssh/config"
```

First, we want to add the global Composer bin directory to [`PATH`](https://en.wikipedia.org/wiki/PATH_(variable)) environment variable. The `PATH` environment variable contains a list of directories where we can find executable programs. Composer is going to install Terminus in a different directory than the ones stored in the `PATH` environment variable by default. So we add this new directory so we can use Terminus more easily once we install it later. Next, we want to configure git. We want to set the `user.email` and `user.name` configuration options. These are the default email address and name that git will use when making a commit. We need those because Terminus is going to commit all our Bedrock assets before sending everything to Pantheon. The Bash script has default variables for both values, but you can also change them using environment variables. These environment variables are `GIT_EMAIL` and `GIT_NAME`. The last step is to configure SSH. We start by creating the `.ssh` directory and `config` file if they don't exist. We do that by using the `-p` option for [`mkdir`](https://en.wikipedia.org/wiki/Mkdir) and [touching](https://en.wikipedia.org/wiki/Touch_(command)) the `config` file. Once we're sure that we have a `config` file, we need to add an option to it. That option is `StrictHostKeyChecking` which we want to set to `no`. This disables the host identity verification with SSH. This is often required when automated scripts need to use SSH. #### Symlinking the uploads directory

The next set commands relate to creating a [symlink](https://en.wikipedia.org/wiki/Symbolic_link) for the `uploads` directory. This is necessary because Pantheon doesn't store your media files in the `uploads` directory in your WordPress installation. Instead, they store them in the `files` directory at the root of the server. ```
# Convert uploads directory to a symlink
sed -i '/web\/app\/uploads.*/d' .gitignore
rm -r web/uploads
ln -s ../../../files web/uploads
```

By default, Bedrock doesn't let you commit anything in the `uploads` directory. So, to change that, we have to edit the `.gitignore` file inside the project. We use the [`sed`](https://en.wikipedia.org/wiki/Sed) command to do it. After that, we delete the existing `uploads` directory. We then create the symlink replacing the `uploads` directory and pointing to the `files` directory. It's worth noting that it doesn't matter that the `files` directory doesn't exist on CircleCI. We can create it anyways. We also don't need to commit this change. Terminus will commit the symlink as part of its larger commit before sending everything to Pantheon. We'll discuss that larger commit a bit later. #### Installing Terminus

Speaking of Terminus, we're now at the point in the Bash script where we want to install it. The script does this in two steps. You can see them below. ```
# Install Terminus globally
composer global require pantheon-systems/terminus:^2.0

# Install Terminus plugins
mkdir -p $HOME/.terminus/plugins
composer create-project -n -d $HOME/.terminus/plugins pantheon-systems/terminus-build-tools-plugin:^2.0.0-beta13
```

The first one is to install Terminus using the `composer global require` command. This is the composer way of installing something for all projects as opposed to just a specific one. It's also why we added the global Composer bin directory to the `PATH` environment variable earlier. By default, Terminus can't package our Bedrock project and send it to the Pantheon platform. That capability comes from the [Terminus build tools plugin](https://github.com/pantheon-systems/terminus-build-tools-plugin). So that's the next step once Composer finishes installing Terminus. Before we can install the build tools plugin, we need to create the Terminus `plugins` directory. This isn't done automatically when we install Terminus with Composer. So we need to have our Bash script take care of it. Installing the build tools plugin is also a bit different from how we installed Terminus. We won't use the `composer global require` command. Instead, we have to use the `composer create-project` command. This command clones the package repository then runs `composer install`. #### Authenticating with Pantheon

Once Composer finishes installing the build tools plugin, we can start using Terminus. First, we need to authenticate with the Pantheon platform. We do this by using the `terminus auth:login` command as shown below. ```
# Authenticate with Terminus
terminus auth:login -n --machine-token="$TERMINUS_TOKEN"
```

In this scenario, we're going to authenticate using a machine token. (You can read more about it [here](https://pantheon.io/docs/machine-tokens/).) We store this machine token in the `TERMINUS_TOKEN` environment variable that we checked for earlier. We pass the value to the `machine-token` option of the `terminus auth:login` command. Next, we run the `terminus env:wake` command with `TERMINUS_SITE.dev` as the argument. This is a precautionary step because development environments on Pantheon will automatically go to sleep after a while. So by running this command, we ensure that the development environment is ready to receive our code. #### Deploying with Terminus

At this point, we're ready to have Terminus send our code to Pantheon. There are two possible commands that we can use to do this. (You can see them below.) Which one we use will depend on the context of the commit that triggered our CircleCI workflow. ```
# Push code to Pantheon
if [[ ${CIRCLE_BRANCH} == "master" ]]; then
    terminus build:env:push -n "$TERMINUS_SITE.dev"
elif [[ ! -z "$CIRCLE_PULL_REQUEST" ]]; then
    terminus build:env:create -n "$TERMINUS_SITE.dev" "pr-${CIRCLE_PULL_REQUEST##*/}"
fi
```

The first command is `terminus build:env:push`. We use it whenever we make a commit to the master branch of our repository. It'll then push our code to the main Pantheon development environment. How do we know if the commit was for the master branch of our repository? We check the `CIRCLE_BRANCH` environment variable. It'll say `master` if the commit came from the master branch of our repository. The other command is `terminus build:env:create`. We use this one whenever the commit that triggered the CircleCI workflow was for a [pull request](https://en.wikipedia.org/wiki/Distributed_version_control#Pull_requests). This command will push our code to a [multidev](https://pantheon.io/docs/multidev/) environment. If the multidev environment doesn't exist for the pull request, Terminus will create it for us. For this command, we want to check the `CIRCLE_PULL_REQUEST` environment variable. We do this by using the [test command-line utility](https://en.wikipedia.org/wiki/Test_(Unix)). The `-z` test checks if the length of a string is 0. If the test fails, it means that our commit was for a pull request. We then use the `CIRCLE_PULL_REQUEST` environment variable to create the environment on Pantheon. We use [Bash string manipulation](https://www.tldp.org/LDP/abs/html/string-manipulation.html) to extract the pull request number from the `CIRCLE_PULL_REQUEST` environment variable. We want the environment name on Pantheon to be the pull request number prefixed with `pr-`. #### Cleaning up unused environments

So the reason why we want to prefix our environments with `pr-` is because of this final part of our Bash script. This part of our script handles the clean up of our environment on the Pantheon platform. You can see it below. ```
# Clean up unused PR environments (if GITHUB_TOKEN is set)
if [[ ! -z "$GITHUB_TOKEN" ]]; then
    terminus build:env:delete:pr -n "$TERMINUS_SITE" --yes
fi
```

The first thing that we do is test to see if we have a `GITHUB_TOKEN` environment variable. The `GITHUB_TOKEN` environment variable is an environment variable that contains a [GitHub personal access token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line). Terminus needs this access token to interact with GitHub. Like `TERMINUS_SITE` and `TERMINUS_TOKEN` environment variables, the value stored in the `GITHUB_TOKEN` environment variable is sensitive. The GitHub personal access token gives anyone access to your GitHub account. So we can't store it inside our Bedrock project repository. You'll need it to add it as an environment variable inside CircleCI like you did for `TERMINUS_SITE` and `TERMINUS_TOKEN`. That said, unlike the two Terminus environment variables, the `GITHUB_TOKEN` environment variable is optional. That's why the Bash script tests for it. If the test for the `GITHUB_TOKEN` environment variable shows that it isn't empty, we make a call to the `terminus build:env:delete:pr` command. This Terminus command cleans up the multidev environments that we created for pull requests. Without it, all the environments created by the `terminus build:env:create` command would never get deleted. The command works by going through all our multidev environments prefixed with `pr-`. It then checks the pull request associated with the environment with its status on GitHub. If the pull request was closed, Terminus will delete the environment on the Pantheon platform. # And that's how you do it

Once our Bash script finishes running, your changes should now be on the Pantheon development environment! At this point, you should use the Pantheon admin to deploy your development environment into testing and then production. This makes our workflow more of a [continuous delivery](https://en.wikipedia.org/wiki/Continuous_delivery) workflow than a [continuous deployment](https://en.wikipedia.org/wiki/Continuous_deployment) one. That said, you could still create a continuous deployment workflow if you wanted. Terminus doesn't limit deployments to just the development environment. In practice, you can use it to deploy code to any environment including the production one. However, this is out of scope for this article. The main goal of this article was to look at how to get Bedrock working with the Pantheon. To do that, we didn't need to continuous integration service or continuous delivery. But, as we saw, doing it without a continuous integration service isn't ideal. That's why a lot of the article explained how the CircleCI workflow and Bash script worked. The benefit of having done that is that, because the workflow and Bash script are generic, you can easily use them with another continuous integration service if you wanted.