Skip to content

Deploy using GitHub Actions

This section will describe how to set up your CI/CD pipeline using GitHub Actions including some examples.

GitHub Actions is the preferred CI/CD platform for MACH composer

While any CI/CD platform can run MACH composer, our examples and tooling will focus around GitHub Actions, as this is the preferred platform for many of our clients, and provides the best developer experience.

In case you use a different CI/CD solution, take the below pipelines as examples. Essentially, a CI/CD pipeline should run a combination of the `mach-composer plan` and `mach-composer apply` commands.

MACH stack deployment

This page describes how to deploy your MACH composer configuration to your cloud provider. We recommend to use a PR workflow whereby changes are done via a PR which allows additional rules (e.g. approval flows).

Log outputs

Mach Composer can be configured to output logs in a more Github-friendly way. This can be done by adding the --github flag to the mach-composer plan and mach-composer apply commands. Terraform outputs will then be grouped in accordions in the Github Actions logs.

Authenticating with your cloud provider

For deploying to a cloud provider (e.g. AWS, Azure or GCP) as we recommend to use OpenID Connect. See how this works with GitHub at security hardening your deployments

Example

name: Deploy Test
on:
  pull_request:
    types: [opened, synchronize, closed]
    paths:
      - 'main.yml'

concurrency: test

env:
  TF_PLUGIN_CACHE_DIR: ${{ github.workspace }}/.terraform.d/plugin-cache
  MACH_COMPOSER_VERSION: 2.5.4
  AWS_DEPLOY_ROLE: arn:aws:iam::<AWS_ACCOUNT_ID>:role/mach-deploy-role
  AWS_DEPLOY_REGION: "eu-west-1"
  AWS_PLAN_BUCKET: "bucket name to store terraform plans"
  CONFIG_FILE: "main.yml"

jobs:
  plan:
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged != true && github.base_ref == 'main'
    permissions:
      id-token: write # This is required for requesting the JWT
      contents: read  # This is required for actions/checkout
      pull-requests: write
    steps:
      - uses: actions/checkout@v3

      - name: Prepare credentials
        run: |
          git config --global url."https://oauth2:${{ secrets.ORG_REPO_TOKEN }}@github.com".insteadOf https://github.com

      - name: Install terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.1.7

      - name: Create Terraform Plugins Cache Dir
        run: mkdir --parents $TF_PLUGIN_CACHE_DIR

      - name: Cache Terraform Plugins
        uses: actions/cache@v2
        with:
          path: ${{ env.TF_PLUGIN_CACHE_DIR }}
          key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }}

      - name: Install MACH composer
        uses: mach-composer/setup-mach-composer@main
        with:
          version: ${{ env.MACH_COMPOSER_VERSION }}

      - name: Install sops
        uses: mdgreenwald/mozilla-sops-action@v1.4.1

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ env.AWS_DEPLOY_ROLE }}
          aws-region: ${{ env.AWS_DEPLOY_REGION }}

      - name: MACH composer plan
        uses: mach-composer/plan-action@main
        with:
          filename: ${{ env.CONFIG_FILE }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Store terraform plan
        run: aws s3 cp --sse AES256 --recursive --exclude '*' --include "deployments/*/terraform.plan" . s3://${{ env.AWS_PLAN_BUCKET }}/${{ github.event.pull_request.number }}/


  deploy:
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged == true && github.base_ref == 'main'
    environment:
      name: test
      url: "<your env url>"
    permissions:
      id-token: write # This is required for requesting the JWT
      contents: read  # This is required for actions/checkout
    steps:
      - uses: actions/checkout@v3

      - name: Prepare credentials
        run: |
          git config --global url."https://oauth2:${{ secrets.ORG_REPO_TOKEN }}@github.com".insteadOf https://github.com

      - name: Install terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.1.7

      - name: Create Terraform Plugins Cache Dir
        run: mkdir --parents $TF_PLUGIN_CACHE_DIR

      - name: Cache Terraform Plugins
        uses: actions/cache@v2
        with:
          path: ${{ env.TF_PLUGIN_CACHE_DIR }}
          key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }}

      - name: Install MACH Composer
        uses: mach-composer/setup-mach-composer@main
        with:
          version: ${{ env.MACH_COMPOSER_VERSION }}

      - name: Install sops
        uses: mdgreenwald/mozilla-sops-action@v1.4.1

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ env.AWS_DEPLOY_ROLE }}
          aws-region: ${{ env.AWS_DEPLOY_REGION }}

      - name: Retrieve terraform plan
        run: aws s3 cp --sse AES256 --recursive s3://${{ env.AWS_PLAN_BUCKET }}/${{ github.event.pull_request.number }}/ .

      - name: Run MACH Composer apply
        run: mach-composer apply --auto-approve -f ${{ env.CONFIG_FILE }}

      - name: Cleanup terraform plan
        run: aws s3 rm --recursive s3://${{ env.AWS_PLAN_BUCKET }}/${{ github.event.pull_request.number }}/

Component deployment

For the component CI pipeline we need to be able to test, package and upload the function app ZIP file.

Example

Example GitHub Action to package and deploy a component on AWS.

name: Package and upload

on:
  push:
    branches:
      - main

env:
  PACKAGE_NAME: my-component
  AWS_BUCKET_NAME: my-lambda-bucket

jobs:
  package:
    name: Package Lambda function
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Artifact Name
        id: artifact-name
        run: echo "::set-output name=artifact::$(echo $PACKAGE_NAME-${GITHUB_SHA:0:7}.zip)"

      - name: Use Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 12.x

      - name: Cache modules
        uses: actions/cache@v2
        with:
          path: '**/node_modules'
          key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}

      - name: Install dependencies
        run: yarn

      - name: Package
        uses: dragonraid/sls-action@v1.2
        with:
          args: --stage prod package

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.MACH_ARTIFACT_AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.MACH_ARTIFACT_AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-central-1

      - name: Upload
        run: aws s3 cp .serverless/${{ env.PACKAGE_NAME }}.zip s3://${{ env.AWS_BUCKET_NAME }}/${{ steps.artifact-name.outputs.artifact }}