This guide walks through automating container publishing to Amazon Elastic Container Registry (ECR) using GitHub Actions and OpenID Connect (OIDC) for secure authentication — without relying on long-lived AWS credentials.


Goal

Automate the build and deployment of a Docker image to Amazon ECR directly from a GitHub Actions workflow triggered by a branch push.


Prerequisites

Before proceeding, ensure you have:

  1. An active AWS Account with permissions to create IAM resources.
  2. A GitHub repository with a valid OIDC trust relationship to AWS.
  3. A Dockerfile defining your container image.

Process Overview

The complete process involves:

  1. Creating an IAM Policy & Role with permissions to push images to ECR.
  2. Setting up OIDC-based authentication for GitHub Actions.
  3. Configuring GitHub Secrets and Variables to provide AWS access details.
  4. Defining a GitHub Actions workflow to build, tag, and push the container image.
  5. (Optional) Adding an automated Release step to publish a GitHub release with the image URI.

IAM Policy & Role

The IAM Policy grants permissions to upload and manage images in your ECR repository, as well as limited access to AWS Secrets Manager for sensitive data like keystore credentials.

Attach this policy to a Role that your GitHub Action will assume via OIDC authentication.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PushImageToECR",
            "Effect": "Allow",
            "Action": [
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "ecr:CompleteLayerUpload",
                "ecr:DescribeImageReplicationStatus",
                "ecr:TagResource",
                "ecr:DescribeRepositories",
                "ecr:ListTagsForResource",
                "ecr:UploadLayerPart",
                "ecr:GetImageCopyStatus",
                "ecr:ListImages",
                "ecr:InitiateLayerUpload",
                "ecr:PutImage"
            ],
            "Resource": "arn:aws:ecr:*:<ACCOUNT ID>:repository/*"
        },
        {
            "Sid": "AccessECR",
            "Effect": "Allow",
            "Action": [
                "ecr:BatchCheckLayerAvailability",
                "ecr:DescribeRegistry",
                "ecr:GetAuthorizationToken"
            ],
            "Resource": "*"
        },
        {
            "Sid": "SecretsManagerAccess",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:CreateSecret",
                "secretsmanager:PutSecretValue",
                "secretsmanager:UpdateSecret",
                "secretsmanager:GetSecretValue"
            ],
            "Resource": "arn:aws:secretsmanager:*:<ACCOUNT ID>:secret:users-api-keystore-passphrase*"
        }
    ]
}

you can do so via the Console UI, or using the aws CLI:

aws iam create-policy \
  --policy-name ECRPublishPolicy \
  --policy-document file://ecr-policy.json

Next, define a Role that can be assumed by GitHub’s OIDC provider.
This enables short-lived credentials for your workflow without using static access keys.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::<ACCOUNT ID>:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": [
                        "repo:alertavert/*",
                        "repo:alertavert/*"
                    ]
                }
            }
        }
    ]
}

the Role ARN (arn:aws:iam::<ACCOUNT ID>:role/github-actions) will be used in the role-to-assume parameter for the configure-aws-credentials action later.


Setup GitHub Action to Access AWS

Create the OIDC identity provider in AWS IAM for GitHub, which enables trust between your GitHub workflows and AWS.

In the AWS Console, navigate to IAM → Identity Providers → Add provider, then select:

  • Provider type: OpenID Connect
  • Provider URL: https://token.actions.githubusercontent.com
  • Audience: sts.amazonaws.com

Alternatively, use the AWS CLI as described in the AWS IAM OIDC guide.

Once configured, store your Role ARN (created earlier) as a GitHub Repository Secret (AWS_ROLE_ARN), and your region (e.g. us-west-2) as a Repository Variable (AWS_REGION).


Create the GitHub Action to Build and Push Image to ECR

This workflow builds and tags your Docker image, authenticates with ECR using OIDC, and pushes the resulting image to your AWS registry whenever changes are pushed to the release branch.

# Upon pushing to the release branch a new tag will be created
# in preparation for the release.
#
# Copyright (c) 2023 AlertAvert.com.  All rights reserved.
# Author: Marco Massenzio (marco@alertavert.com)
#
name: Release
on:
  push:
    branches:
      - release

env:
  AUTHOR: ${{ github.event.pusher.name }}
  EMAIL: ${{ github.event.pusher.email }}
permissions:
  id-token: write   # This is required for requesting the JWT (OIDC auth for AWS)
  contents: write   # This is required for actions/checkout & to update the tag

jobs:
  release:
    runs-on: ubuntu-22.04

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          fetch-tags: true
      - name: Create a Release Tag
        run: |
          git config user.name "$AUTHOR"
          git config user.email "<$EMAIL>"
          TAG=$(./scripts/version)
          echo "Preparing to create release tag: ${TAG}"
          # Ensure we have up-to-date tags from origin
          git fetch --tags --prune origin
          # Check if the tag already exists in the remote repository
          if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
            echo "::error title="Duplicate tag"::Tag ${TAG} already exists." \
            "Aborting release to avoid duplicate tags." >&2
            exit 1
          fi
          # Create annotated tag and push just this tag
          git tag -a "${TAG}" -m "Release ${TAG}"
          git push origin "refs/tags/${TAG}"
          echo TAG=${TAG} >> "$GITHUB_ENV"
          echo -e "## New Tag\nCreated release tag: ${TAG} :tada:" >> $GITHUB_STEP_SUMMARY

      # ---- Configure AWS credentials via OIDC ----
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}

      # ---- Login to ECR ----
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build and Push Docker image to ECR
        env:
          REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          REPOSITORY: alertavert/users-api
        run: |
          if [[ -z "${REGISTRY}" ]]; then
            echo "::error title="Missing Registry"::Should have been set by the login-ecr step."
            exit 1
          fi
          set -euo pipefail
          echo "Publishing Image to Amazon ECR: ${REGISTRY}"
          ./scripts/build-image "${REGISTRY}"
          export WORKDIR=$(pwd)
          source ./scripts/common.sh # For the image_name function
          IMAGE_TAG="${REGISTRY}/$(image_name)"
          echo "Pushing ${IMAGE_TAG}"
          docker push "${IMAGE_TAG}"
          echo "🚀 Container image published: ${IMAGE_TAG}" >> release_notes.txt
          echo -e "## ECR Image\nPushed new Image to\n\n\`${IMAGE_TAG}\` :white_check_mark:" >> $GITHUB_STEP_SUMMARY

This workflow uses GitHub’s native OIDC token exchange to authenticate to AWS, securely assuming the role without credentials stored in GitHub.


Create a Release (Optional)

Optionally, the workflow can generate a GitHub Release once the image has been successfully pushed.
This is useful for tagging published artifacts and linking them to release notes.

      # Creates a new release in GitHub, with auto-generated content.
      - uses: ncipollo/release-action@v1
        with:
          tag: "${{ env.TAG }}"
          bodyFile: "release_notes.txt"

      - name: Release Summary
        run: |
          echo -e "## Release Summary\nReleased version: ${TAG} :rocket:" >> $GITHUB_STEP_SUMMARY

Note: The bodyFile option is incompatible with generateReleaseNotes: true.
To include custom release content such as the ECR image URI, use bodyFile exclusively.


References

Leave a comment

Trending