Nifty bits

Deploying on AWS via Github Actions

I recently decided to move the deployment of this site to GitHub actions. In this post I’ll document the parts that went into it.

Since the site is hosted on AWS (a combo of S3 + Cloudfront), the Github action needed to access AWS.

This was done via the aws-actions/configure-aws-credentials action, that at a high level does the following:

  1. Requests a JWT ID token from the GitHub OIDC provider.
  2. Requests a set of short-lived tokens via the AWS STS AssumeRoleWebIdentity (using the JWT from step 1). These tokens make it possible to interact with different AWS services.

So for deploys to work we need some work on both the AWS and GitHub action side of things.

AWS

You’ll need a role on the AWS role with the permissions required to deploy your application. This role is assumed from the Github pipeline.

Using Pulumi it looks something like this:

const currentCaller = aws.getCallerIdentity()

const githubActionsRole = new aws.iam.Role("GitHubActionsRole", {
    name: "GitHub_Actions_Role",
    assumeRolePolicy: currentCaller.then(identity => JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Principal: {
                Federated: `arn:aws:iam::${identity.accountId}:oidc-provider/token.actions.githubusercontent.com`
            },
            Action: "sts:AssumeRoleWithWebIdentity",
            Condition: {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:GITHUB_USER/repo:*"
                },
                "StringEqualsIgnoreCase": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
                }
            }
        }]
    }))
});


const githubActionsDeployPolicy = new aws.iam.Policy("GitHubActionsDeployPolicy", {
    name: "GitHubActionsDeployPolicy",
    policy: s3Bucket.arn.apply((arn) => JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Sid: "AllowS3Actions",
            Effect: "Allow",
            Action: ["s3:PutObject", "s3:ListBucket", "s3:GetObject", "s3:DeleteObject"],
            Resource: [
                arn,
                `${arn}/*`
            ]
        }]
    }))
});

new aws.iam.RolePolicyAttachment("GitHubActionsDeployAttachment", {
    role: githubActionsRole.name,
    policyArn: githubActionsDeployPolicy.arn
});

Github action

The GitHub workflow needs two permissions:

  • id-token: write to create the OIDC JWT.
  • contents: read for the actions/checkout, i.e. checking out code.
on:
  workflow_dispatch:

jobs:
  get_caller_identity:
    name: Deploy Site
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@v4.1.1

      - name: Assume AWS role
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHub_Actions_Role
          aws-region: eu-north-1

      - name: Install node
        uses: actions/setup-node@v4
        with:
          node-version: 22.14.0

      - name: Install deps
        run: npm ci
        working-directory: web

      - name: Build site
        run: npm run build
        working-directory: web

      - name: Deploy site
        run: aws s3 sync ./dist/ s3://BUCKET_NAME/ --delete
        working-directory: web