Slack Deploy Your Code with GitHub Actions to AWS πŸš€

GitHub Actions is one of the coolest features released by GitHub. Ever since it has been announced, I immediately got excited to give it a spin. But only recently I've received an invite and finally managed to play with this awesome piece of technology over the weekend.

Actions are powered by Docker containers and provide a familiar infrastructure as a code approach to deploy your code - nothing new there, huh? However, the way GitHub executed this feature, which I believe will eventually grow into a primary way of deploying stuff on GitHub, is really amazing! 😍

We can finally have our deployment workflows defined alongside the code, together with a beautiful pipeline editor right inside GitHub and with seamless integration to the rest of GitHub's ecosystem - its repositories, integrations, events, checks, app, etc. Alright, I am not going to make you bored any longer with my affection to GitHub and will dive straight into the Actions.


In this post, I would like to demonstrate to you how easy it is to set up a deployment pipeline with GitHub Actions, completely orchestrated by a Slack chatbot. In the example below, we will deploy a sample application to AWS Elastic Beanstalk since that's what we're using to power SlashDeploy. Although, you can use Actions for pretty much any deployment workflow you can imagine.

GitHub Actions is still in Beta and there is definitely a whole slew of things to improve, so at the end of this post, I will briefly summarize the things that I wish were available during Beta today, which would have definitely simplified the workflow and debugging.

Expoloring Actions

Let's start with exploring Actions and it's runtime environment. We will create a simple Bash action that will print out available environment variables in the system. Create a file named main.workflow under the .github folder of your Git repository:

workflow "Environment" {
  on = "push"
  resolves = ["env"]
}

action "env" {
  uses = "actions/bin/sh@master"
  args = "env"
}

In the workflow definition above, we have specified the following:

  • The name of the workflow is Environment.
  • The workflow will be triggered by a push event to our repository.
  • The workflow resolves when the action named env gets executed.
  • Our single action named env should match resolves statement.
  • The action uses actions/bin/sh repository, which contains a Dockerfile and related scripts to build a shell container during runtime. Also, notice that we've specified the master branch to use. Statement uses could also point to a Docker container with docker:// or a relative path to a subdirectory with action sources.
  • The arguments to container is env, which will print out environment variables.

Fairly simple, isn't it? You could, as well, define this workflow in the visual editor by going to the Actions tab of your GitHub repository:

Simple GitHub Action that prints environment variables

Now, let's push a commit and see GitHub Actions in action πŸ˜‰:

As expected, we're getting a few environment variables, which are described in Accessing the runtime environment. That's really cool because we can use some of these variables to build our deployment pipeline next.

Deployment pipeline

Now that we know how to build a simple workflow to run GitHub Actions, let's create our deployment pipeline with multiple actions and Actions Secrets.

We're going to use GitHub Deployment API to store our deployments, create them with SlashDeploy and deploy to AWS Elastic Beanstalk. Sounds exciting, right?!

If you haven't yet used GitHub Deployment API, you should really consider doing so - it will definitely save you time! Here is the description from the GitHub documentation:

Deployments are requests to deploy a specific ref (branch, SHA, tag). GitHub dispatches a deployment event that external services can listen for and act on when new deployments are created. Deployments enable developers and organizations to build loosely coupled tooling around deployments, without having to worry about the implementation details of delivering different types of applications (e.g., web, native).

So, our deployment flow will look similar to this:

GitHub Deployment flow with SlashDeploy, Actions and AWS Elastic Beanstalk

And our plan to implement it with GitHub Actions is the following:

  1. Trigger workflow on deployment event to the repository.
  2. Create a pending deployment status.
  3. Set up necessary AWS CLI configuration with Actions Secrets.
  4. Deploy to AWS Elastic Beanstalk and capture the output.
  5. Create deployment status, either success or failure.
  6. Print deployment output to STDOUT so it's visible in GitHub Actions UI.

Everything seems pretty straightforward, but there is a catch. As of writing this post, GitHub does not communicate deployment statuses back to itself if you're subscribed to a deployment workflow event. After searching for a solution, I've stumbled upon a github-deploy, which is a custom action with a collection of handy Bash scripts to help you create GitHub Deployment Statuses from the command line, kindly maintained by Unacast.

Ok, let's get to coding our GitHub workflow:

workflow "Deploy to AWS EB" {
  on = "deployment"
  resolves = ["deploy"]
}

action "deploy.scripts" {
  uses = "unacast/actions/github-deploy@master"
}

action "deploy" {
  uses = "getslashdeploy/actions/aws-eb@master"
  secrets = [
    "GITHUB_TOKEN",
    "AWS_ACCESS_KEY_ID",
    "AWS_SECRET_ACCESS_KEY",
  ]
  needs = ["deploy.scripts"]
  env = {
    AWS_PROFILE = "eb-cli"
  }
  args = ["deploy", "slashdeploy-$GITHUB_ENVIRONMENT_NAME"]
}

Let me walk you through the code:

  • Lines 1-3: We've defined our workflow named Deploy to AWS EB, which will trigger on deployment event and resolve on action named deploy.
  • Lines 6-8: Prior to deploying, we're running a custom action named deploy.scripts using github-deploy repository, which as mentioned earlier, bundles a few Bash scripts to help us creating deployment statuses from CLI. I've created the fork PR is in review (it's been merged and the code updated) to be able to pass a deployment target_url (link to the deployment output appearing in Slack notifications you'll see below) and description (text appearing in Slack notifications).
  • Lines 10-11: We've defined a custom action named deploy using Β aws-eb repository, which I've created to configure AWS CLI and to actually execute an eb deploy command. In addition, this custom action extracts environment name from the GITHUB_EVENT_PATH (path to a GitHub deployment event that triggered this action, exposed by Actions runtime) and makes it available as an environment variable GITHUB_ENVIRONMENT_NAME, which we use later in the workflow. The entire code in the Docker entrypoint is a simple Bash script that you can check out here.
  • Lines 12-16: We're passing necessary secrets, which I created earlier in the actions visual editor (see the screenshot below). Note, there is a warning in GitHub Actions docs about not using production secrets during limited Beta that I did not understand the reason behind - is it a security constraint (that probably should have been handled from the get-go) or a performance limitation? Hopefully, somebody from GitHub could answer explain it. Anyway, for the sake of this post, we will just ignore it Β―\_(ツ)_/Β―.
  • Line 17: We've added a dependency on deploy.scripts with a needs statement since our deploy action won't work without executing it first.
  • Lines 18-20: We've passed an additional environment variable - AWS_PROFILE, which is specific to Elastic Beanstalk and should match your profile in .elasticbeanstalk/config.yml.
  • Line 21: Finally, we're passing a deploy argument to eb command of the container, along with an environment name exported by an aws-eb action. Neat, huh?!

And here is the visual representation of this workflow:

GitHub Action that deploys to AWS Elastic Beanstalk

Deploying from Slack

Well, now that we have our functional GitHub Actions workflow, all we need is to trigger a deployment creation from Slack chatbot, which by the way, is kindly provided by SlashDeploy 😊:

Triggering deployment from Slack, powered by SlashDeploy and GitHub Actions

Things to improve

As mentioned earlier, I also wanted to summarize the things I wish GitHub had included in Beta. Though, I am quite sure most of these features are already being either discussed or at least considered for implementation since some of them are crucial for production-grade deployment workflows:

  1. Ability to link to an action log - GitHub Actions runtime does not expose a Run or kind of Job ID, which makes it impossible to link back to the deployment log.
  2. Real-time log output - There is no a real-time log output of the action run, so you would have to wait for an action to complete - not ideal.
  3. Cache artifcats - I've noticed that every action run pulls the Docker images even if they haven't changed, resulting in slower execution times. It'd have been nicer to cache the artifacts to speed things up.
  4. Update deployment API - At least for deployment event triggered action runs, GitHub could have created corresponding deployment status events automagically - this has been discussed briefly in this thread.

Developing custom Actions

To get the deployment workflow above up-n-running, we had to use a couple of custom actions. I would very much like to describe the process behind developing and testing own actions, but this post is already quite lengthy, so I will leave it for another day.

However, what really helped me to test things out and debug locally is a handy tool released just yesterday; act - lets you run your GitHub Actions locally and the feedback so far has been awesome!

To conclude...

Just wanna conclude with sharing my excitement about the future of GitHub Actions. I believe it's a feature we, developers, have been waiting for and I will surely try to leverage Actions for all our deployments - SlashDeploy + GitHub Actions is a killer combo to get your code out quickly without worrying much about the underlying deployment infrastructure. πŸ™ŒπŸ½

Show Comments