Automating AWS EC2 deployments with GitHub Actions and Systems Manager

This blog is hosted on an AWS EC2 instance; the code for it is stored in a GitHub repository. In order to update the code on the EC2 instance I previously would manually connect to the EC2 instance via SSH client, pull the code from the GitHub repository and then execute the necessary command to redeploy the code. While this got the job done, it always felt rather clunky and tedious. Last week I finally got around to automating the process. In today’s post I’ll be discussing the solution I came up with.

Essentially my approach involves integrating GitHub Actions with AWS Systems Manager to deploy the code on the EC2 instance on a push of the code to a branch of the GitHub repository. The GitHub Actions workflow consists of two steps, which are as follows:

  1. Configuring the AWS credentials
  2. Executing the deployment

The rest of this post will go into a bit more detail about each of these steps.

I’ll start by stubbing out the GitHub Actions workflow I’m using:

name: Deploy to EC2

on:
  push:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Configure AWS credentials
    ...
    - name: Execute deployment script on EC2 instance
    ...

In the “name” section I specify a name for the workflow. In the “on” section I specify that I want the workflow to run on pushes to the master branch of the repository. Finally in the “jobs” section I outline the “deploy” process–“runs-on” specifies the runner for GitHub Actions to use; “steps” specifies the steps for GitHub Actions to execute.

To configure AWS credentials I use AWS’s official configure-aws-credentials action. This action needs to be configured with the following data:

  1. The IAM user’s access key ID
  2. The IAM user’s secret access key
  3. The region of the EC2 instance to which the code is being deployed

This presupposes of course that the IAM user and an EC2 instance already exist. In my case the latter did but the former didn’t so I just went ahead and created an IAM user to represent GitHub Actions. With these things in place the complete step ended up as follows:

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v1
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: us-east-1
(So as not to expose the AWS credentials publicly I store them as secrets in the repo’s Security settings.)
To execute the deployment process on the EC2 instance I use a custom action built around System Manager’s send-command function. The docs reveal the function to be highly configurable; the parameters that are relevant to my use case are as follows:
  1. document-name – The name of the Amazon Web Services Systems Manager document (SSM document) to run.
  2. targets – An array of search criteria that targets managed nodes using a key-value combination that you specify.
  3. parameters – The required and optional parameters specified in the document being run.
  4. timeout-seconds – If this time is reached and the command hasn’t already started running, it won’t run.

For document-name I specify “AWS-RunShellScript”–this is a shared resource available via Systems Manager Documents that enables Systems Manager to run a shell script.

For targets, I specify “instanceids” as “Key” and the instance ID of my EC2 instance as “Values.”

For parameters, I specify a string in the following form (where <command> represents a specific instruction to provide to the EC2 instance):

'commands=[
  "<command>"
]'

Finally for timeout-seconds, I specify a value of 600 (10 minutes).

With these things in place the complete step ended up as follows:

- name: Execute deployment script on EC2 instance
  run: |
    aws ssm send-command \
      --document-name "AWS-RunShellScript" \
      --targets "Key=instanceids,Values=${{ secrets.EC2_INSTANCE_ID }}" \
      --parameters 'commands=[
        "<command>"
      ]' \
      --timeout-seconds 600

(Similar to before I store the EC2 instance’s ID as a secret in the repo’s security settings so as not to expose it publicly.)

So this is pretty much it. A push of the code to the master branch of the repo now results in the code being deployed automatically to the EC2 instance via the GitHub Action. Handily the results of the execution are available in Command History under Systems Manager > Run Command.

Clicking into the detail of a command exposes further info such as output and error logging, plus the ability to re-run the command from Systems Manager itself.

All in all this was a fun little project that took a day or two of tinkering to get working.

Leave a Reply

Your email address will not be published. Required fields are marked *