Pantheon and GitHub Actions: Automated Drupal 8 Deployments via GitHub Actions

Tyler Fahey
12 min readApr 7, 2020

--

GitHub Actions allows for easy continuous deployment techniques. In this story, we’ll go over a use case with WebOps platform Pantheon, and a Drupal 8 site.

Pantheon is a fantastic WebOps platform for a Drupal or Wordpress site. It’s deployment system is built entirely on git, the industry standard version control system. The fact that it is git based enforces a best practice workflow. Each Pantheon site provides it’s own repository that connects directly with your application. Any changes you push to the master branch are deployed to the “Dev” environment, and from there, you can deploy changes to “Test” and “Live”, “Live” typically being the environment which is connected to your domain(s), and this is done via automated git tagging. You can also deploy git branches as “Multidevs”, which are environments running any particular branch you want (See here for the specifics)

In the world of git, GitHub.com stands out as a really awesome place to host your git repos. It has a plethora of popular open source projects hosted on it, and is used by many businesses and individuals. While the open source projects are plentiful, some opt to keep their git repo hidden with a private repository. Whatever the case may be — Your team or yourself may already be used to the interface, tools, and workflow that GitHub provides.

When you’re developing a Pantheon site, it’s likely you’d have the site source code in a GitHub repo. This makes sense if you want to take advantage of one or more of their offered features, but because it means you’d have two repositories — you will find yourself pushing to both GitHub and Pantheon repos, always having to make sure things are in sync. That is, unless you want to deploy some automation.

There are lots of options out there when it comes to automation and “continuous integration”. I had heard about GitHub Actions, which is an automation service that GitHub provides, and have been curious for some time. Recently, I finally had a chance to take a closer look at it. After a somewhat detailed read of the docs, it sounded robust enough to accomplish a good deal of automation and integration with Pantheon. Sure enough, after spending some time with it, it seems more than capable to do a *lot* of automation and continuous integration tasks, that I think could go far with a single or a fleet of sites. And, it appears to have a very decent developer community behind it, based upon the observed quality of community provided Actions, which can be integrated with your custom Action to easily provide functionality. I was able to quickly get a lot setup, and that is what I look forward to sharing and exploring in this article.

With GitHub Actions, you can pretty easily connect your Pantheon repository, and any pushes to your GitHub repository can automatically be synced with your Pantheon repo. You can also automatically push changes back to GitHub whenever anything is committed directly in the Pantheon repo. An example of this might be the “one click updates” that Pantheon’s dashboard provides — these commit directly to the repository. Let’s dive into an example of how this could be setup.

First, let’s spin up a Drupal 8 site on Pantheon to use. I’ll use the terminus command line tool to do this — terminus is Pantheon’s command line tool for the platform, they have documentation on getting setup here: https://pantheon.io/docs/terminus/install. Assuming you have terminus setup and authenticated, you can run a command similar to the following, replacing “github-deployed” with a unique site name of your own choosing:

terminus site:create github-deployed "Github Deployed Demo" drupal8

This will spin up a site called “github-deployed”, with the label on the dashboard of “Github Deployed Demo”, using the Drupal 8 upstream.

After a minute or two, our site is spun up:

➜  ~ terminus site:create github-deployed "Github Deployed Demo" drupal8
[notice] Creating a new site...
[notice] Deploying CMS...
[notice] Deployed CMS

Next, let’s use the GitHub CLI to create a repo on GitHub that can be our central repository for this project, and the repo from which we will automatically deploy any changes to Pantheon. If you’re not setup for GitHub CLI, you can get setup here: https://cli.github.com/. You can alternatively just create the repo through GitHub manually, but the CLI is a really handy tool. Assuming you’re using the GitHub CLI, you can run a command similar to the following, again, replacing “github-deployed” with the unique name you chose earlier:

➜  ~ gh repo create github-deployed
Notice: authentication required
Press Enter to open github.com in your browser...
Authentication complete. Press Enter to continue...
✓ Created repository twfahey1/github-deployed on GitHub
? Create a local project directory for twfahey1/github-deployed? No

Now, we can get the connection info for the Pantheon site with terminus, and git clone it to our local system, so we can create an action file, which will be located in .github/workflows/deploy-to-pantheon.yml , which is what we’ll use for our GitHub Action, explained a bit later. You can run this terminus command, replacing github-deployed with your site name:

~ terminus connection:info github-deployed.dev
Git Command git clone ssh://codeserver.dev.73316928-7d52-4210-8f45-a2bf71ae863a@codeserver.dev.73316928-7d52-4210-8f45-a2bf71ae863a.drush.in:2222/~/repository.git github-deployed

With this git clone command, we simply copy/paste it, and run it in wherever you store your local sites. In my case, I like to use a “Sites” folder in my home directory, so I cd Sites , paste, and run the command:

➜  Sites git clone ssh://codeserver.dev.73316928-7d52-4210-8f45-a2bf71ae863a@codeserver.dev.73316928-7d52-4210-8f45-a2bf71ae863a.drush.in:2222/~/repository.git github-deployed
Cloning into 'github-deployed'...
Warning: Permanently added '[codeserver.dev.73316928-7d52-4210-8f45-a2bf71ae863a.drush.in]:2222' (RSA) to the list of known hosts.
remote: Counting objects: 118143, done.
remote: Compressing objects: 100% (41814/41814), done.
remote: Total 118143 (delta 69326), reused 118135 (delta 69322)
Receiving objects: 100% (118143/118143), 59.49 MiB | 11.11 MiB/s, done.
Resolving deltas: 100% (69326/69326), done.
Checking out files: 100% (17751/17751), done.

Now, we can create our GitHub actions workflow. At a high level, what we need to do is create an “action” that GitHub will act on. What this comes down to is creating an action file in a .github/workflows folder. In our example here, we’ll be creating our workflow in a file called deploy-to-pantheon.yml , located inside .github/workflows .

Now, we can look to creating our Action. From a high level, what we’re wanting is when a push or merge happens to the master branch on the GitHub repo, we want to deploy it to Pantheon automatically. Getting a bit more specific — what we’ll do is activate a workflow on push to master specifically, which will push the master branch to the Pantheon remote. Let’s see how that would look in an example:

name: Deploy to Pantheon
on:
push:
branches:
- 'master'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.PANTHEON_SSH_KEY }}
config: ${{ secrets.SSH_CONFIG }}
known_hosts: ${{ secrets.KNOWN_HOSTS }}
- name: deployer
env:
pantheon_repo: '${{ secrets.PANTHEON_REPO }}'
run: |
git remote add pantheon $pantheon_repo
git push pantheon HEAD:master

To break down what’s happening here — we first specify *when* this action should be started. This is pretty readable — “On a push to the master branch” is fairly self evident when you look at lines 2 through 5. If you wanted to specify other branches, they could simply be added here, and the action would fire for those, too.

The *what* of this action comes next. The “runs-on” key is what sort of operating system this action’s container will use. Remember, an action is basically using a container to do something. You can theoretically use other operating systems, but for our purposes, ubuntu-latest should serve nicely.

In the actual “steps” portion, you can see we start by setting two “uses” keys — these are community Actions. These are sort of like “plugins” for our custom action — the “actions/checkout@v1” is provided by GitHub, and does a checkout of the repo onto the container, so we can interact with the code. The “shimataro/ssh-key-action@v2” is a community written package, which allows for easy configuration of ssh on your action container. You can see in the configuration, specified under the “with”, we’re using our config file, known_hosts, and SSH key earlier defined in our secrets. These are required inputs by the action — without them, the action will fail. Using community provided actions like this one are an awesome way to quickly get some complex workflows with a minimal amount of code. Before setting out to write a custom action, it’s worth a quick Google search, or heading to the Actions Marketplace — https://github.com/marketplace?type=actions — to see if something already exists that might do exactly what you want.

With our two plugins in place, our custom action, arbitrarily called “deployer”, can now be executed. With the “env” key, we setup some environment variables we can use in the actual steps. Since this is an Ubuntu environment, we are using bash syntax. You could theoretically any bash syntax here, which is awesome!

For this action to actually work, we need to add corresponding secrets to the repository, which will allow access to the Pantheon repository. A secret is any sensitive and/or configuration you don’t want to include “hardcoded” with the rest of the site code. Here’s a guide on adding secrets here: https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets. Looking back at our custom action, you can see 4 secrets being used:

PANTHEON_SSH_KEY
SSH_CONFIG
KNOWN_HOSTS
PANTHEON_REPO

We need to create an SSH key, so that we can push from the container to the Pantheon site. We can do that on the command line with ssh-keygen. We can call this github-deployed:

➜  ~ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/tylerfahey/.ssh/id_rsa): github-deployed
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in github-deployed.
Your public key has been saved in github-deployed.pub.

To use this key in GitHub as a secret, let’s convert it to PEM format with the following command:

➜  ~ ssh-keygen -p -m PEM -f github-deployed
Key has comment 'tylerfahey@Tylers-MBP.attlocal.net'
Enter new passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved with the new passphrase.

In our GitHub secrets, add a new one called “PANTHEON_SSH_KEY”, and paste in the *private* key, which is in the github-deployed file. Then, back on our Pantheon dashboard settings, in the “SSH” tab, add the github-deployed.pub , so that we are authorizing this key pair to perform actions on our account.

We’ll want to include an ssh config file, which is required by the shimataro/ssh-key-action action. It’s going to be critical that our GitHub actions container trusts the Pantheon host, and we don’t have to explicitly trust the new connection, since in automation, we don’t want to have to confirm yes every time we deploy. We can do this by adding this snippet to a secret, which for this example we call “SSH_CONFIG”:

Host *.drush.in
StrictHostKeyChecking no

Another required parameter for shimataro/ssh-key-action is known_hosts. We don’t actually need a known_hosts for what we’re doing, but since it’s required, you can add a “empty” secret called KNOWN_HOSTS, which can just contain a single space. Secrets can always be removed and recreated if needed, so if at some point you *want* an actual known_hosts configuration for whatever reason, this is easily done. At this point, this ssh setup setup should pass in the build, and your container now has full fledged ssh configured on it, allowing us to push to our Pantheon repo without being prompted to “trust this host”.

Additionally, let’s create a “PANTHEON_REPO” secret, which will have a value of the Pantheon repository URL from earlier. If you need to pull it up, remember, terminus can quickly display the information via terminus connection:info github-deployed.dev .

With these 4 secrets in place, things should be ready to run. When you do a push to the GitHub repo, you should be able to go to the “Actions” tab, and watch the Workflow in progress. If anything fails, this is where you can get detailed output. Otherwise, you should see that the build succeeded. You should then be able to go to your Pantheon site dashboard, and see the code being synced and committed.

Great! We’ve now implemented a simple, but powerful continuous integration action. Our development team can continue to develop features and do pull requests, and whenever anything merges to master, it will automatically be moved up to Pantheon’s repo, and deployed to the development environment.

Now, we may want to expand on this further. For example, to automate even more, let’s say we now want to push any changes to master straight through to Test, and then immediately to the Live environment. In this sort of workflow, we assume that changes to master have already been vetted and tested in a multidev, and so we can immediately deploy them to live environment when any changes make it to master. Terminus provides us an easy way to deploy, and so the key here will be running terminus from our action, and after pushing, trigger a deploy to test, and then trigger a deploy to live. Taking a look at the marketplace, we see a community package for running terminus already exists: kopepasah/setup-pantheon-terminus. Looking at the overview page, we can see an easily understandable example for implementing this action in our workflow:

- name: Installing Terminus
uses: kopepasah/setup-pantheon-terminus@master
with:
pantheon-machine-token: ${{ secrets.PANTHEON_MACHINE_TOKEN }}

With this knowledge in hand, let’s generate a Pantheon machine token, which will allow access to our account. The docs on how to create a machine token are here: https://pantheon.io/docs/machine-tokens.

With the machine token copied to your clipboard, head to your GitHub repository settings, and create a secret called “PANTHEON_MACHINE_TOKEN”, paste the machine token in as the value, and save it. While we’re here in the secrets configuration, we can add a “PANTHEON_SITE_NAME” secret — This is something we can leverage in our action, and allows us to reuse this action in other projects more easily. The PANTHEON_SITE_NAME should be whatever site name you would use in the terminus command, so in this example: github-deployed .

Now, we should be able to add the terminus action integration prior to our deployer action definition, and that should allow us to use terminus in our deployer to deploy changes through to Live. Here’s what that might look like:

name: Deploy Master to Pantheon
on:
push:
branches:
- 'master'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.PANTHEON_SSH_KEY }}
config: ${{ secrets.SSH_CONFIG }}
known_hosts: ${{ secrets.KNOWN_HOSTS }}
- name: Installing Terminus
uses: kopepasah/setup-pantheon-terminus@master
with:
pantheon-machine-token: ${{ secrets.PANTHEON_MACHINE_TOKEN }}
- name: deployer
env:
pantheon_repo: '${{ secrets.PANTHEON_REPO }}'
pantheon_site_name: '${{ secrets.PANTHEON_SITE_NAME }}'
run: |
echo ${{ secrets.KNOWN_HOSTS }}
git remote add pantheon $pantheon_repo
git push pantheon HEAD:master
commit_message=$(git log -1 --pretty=%B)
terminus env:deploy $pantheon_site_name.test --note="Automated deploy: $commit_message"
terminus env:deploy $pantheon_site_name.live --note="Automated deploy: $commit_message"

The changes here are adding the “Installing Terminus” step, which is directly from the kopepasah/setup-pantheon-terminus action. We also add the terminus env:deploy commands — we get the last commit message as the “note”, which is what will show up in the Pantheon commit log. The “note” is optional, but can be a quick way to see what the last commit was if you’re looking at the site dashboard on Pantheon.

Now, when we commit and push this change, we should see it deployed through to the Live environment.

Great! Now, we mentioned earlier that we’d be working on new features for this site in multidev environments, which are just git branches running on a dedicated environment. What might be *really* cool is if we setup a multidev environment automatically whenever we push a branch to GitHub. We’ve got all the pieces we need setup — with a simple GitHub action definition that runs a specific terminus command, we can spin up our multidev environments whenever a non-master branch is pushed, using the name of the branch for the name of the multidev, and sync those environments with subsequent pushes to the branch automatically at the same time. This new action can live in a new file: .github/workflows/deploy-branch-to-pantheon-multidev.yml :

name: Deploy Branch to Pantheon Multidev
on:
push:
branches:
- '*'
- '!master'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.PANTHEON_SSH_KEY }}
config: ${{ secrets.SSH_CONFIG }}
known_hosts: ${{ secrets.KNOWN_HOSTS }}
- name: Installing Terminus
uses: kopepasah/setup-pantheon-terminus@master
with:
pantheon-machine-token: ${{ secrets.PANTHEON_MACHINE_TOKEN }}
- name: deployer
env:
pantheon_repo: '${{ secrets.PANTHEON_REPO }}'
pantheon_site_name: '${{ secrets.PANTHEON_SITE_NAME }}'
run: |
BASE_BRANCH=${GITHUB_REF##*/}
git remote add pantheon $pantheon_repo
git push -u pantheon HEAD:refs/heads/$BASE_BRANCH
terminus multidev:create $pantheon_site_name.live $BASE_BRANCH

This is very similar to our deploy-master-to-pantheon.yml Action. The only real difference is in our run of the custom deployer step, we extract the branch name to the $BASE_BRANCH variable, and use that in a terminus command which creates a multidev. This should gracefully fail if the multidev already exists, thanks to how terminus handles a request to make a new multidev.

This is really a simple, first round approach to this. There’s still so much more to do and refine in these custom actions. (For the part of automatically pushing back changes from Pantheon to GitHub, I think we could base it off of https://github.com/pantheon-systems/quicksilver-pushback) It’s just the tip of the iceberg of what’s possible in automation with GitHub Actions. It’s just so cool that we can throw an entire Linux Ubuntu container at any task of our choosing, and run scripts in a wide variety of languages. I’ve used other automation tools before, so the concept of automation wasn’t completely new to me, but I certainly had a good time with GitHub Actions. I have to say that I was really impressed with what I found in the community, coming into this for the first time. The composable feel of community Actions and custom Actions feels very intuitive. The actual interface was on par with what we’ve come to expect from automation tools — providing a pretty output of what’s going on in the container. The rate limits seem very reasonable. It’s a really awesome tool, and I’m looking forward to leveraging the power and potential more and more in my projects. I’m excited to see where things go from here in this community, and I’ll certainly be looking to contribute when I can!

--

--

Tyler Fahey

Open source advocate, husband, father, believer, developer.