Deploy to AWS S3 securely using GitHub Actions and OpenID Connect

Storing large binary files (PDFs, images, compiled assets) in Git bloats the repository, slows down clones, and makes every checkout heavier. This guide walks through moving those binaries to AWS S3, serving them via CloudFront, and automating builds with GitHub Actions — so your binaries stay out of Git entirely.

Create an AWS S3 bucket

First, create a basic S3 bucket using the AWS console.
Leave “Block all public access” enabled — only CloudFront will access the bucket directly.
You do not have to enable bucket versioning as your source files are already in Git, but you can if you need to.

Create an AWS CloudFront distribution

  • Create a CloudFront distribution using the AWS console: image

  • Allow access to your S3 bucket’s contents by setting an origin. When you select your S3 bucket as the origin, CloudFront will recommend using Origin Access Control (OAC) — accept this. OAC lets CloudFront read from the bucket while keeping “Block all public access” enabled. AWS will provide a bucket policy you can apply automatically, granting CloudFront the necessary read permissions. image

  • Upload an image file test.png inside the bucket and check if you can access it by pasting https://your_distribution_domain_name/test.png into your web browser

  • Note that you cannot access the same file via an S3 link because only CloudFront can retrieve the file (even if the CloudFront link itself makes your file publicly available).

  • If you have your own domain (does not have to be hosted on AWS), you can set it up with a CloudFront TLS certificate. Choose your CloudFront distribution, and click Add domain image AWS will guide you on how to create a TLS certificate. Add the CNAME record at your DNS hosting provider and AWS will automatically deploy a TLS certificate.
    You also need to create a CNAME that will be named after your custom domain (e.g. assets.example.com) and will point to your AWS Distribution domain name.

  • If you can access your S3 bucket’s contents using a web browser via CloudFront link, you can already use your distribution link to point to binary files in your web application, effectively moving binaries out of your Git repository.
    But this post goes further: it covers setting up GitHub Actions to build and push binaries to S3 automatically on every source file change.

Set up IAM role and OpenID Connect

  • Create an IAM policy that will allow access to your S3 bucket image
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ListObjectsInBucket",
            "Effect": "Allow",
            "Action": ["s3:ListBucket"],
            "Resource": ["arn:aws:s3:::YOUR_BUCKET_NAME"]
        },
        {
            "Sid": "AllObjectActions",
            "Effect": "Allow",
            "Action": "s3:*Object",
            "Resource": ["arn:aws:s3:::YOUR_BUCKET_NAME/*"]
        }
    ]
}

Note: s3:*Object is broad. For production, consider restricting this to s3:PutObject and s3:GetObject.

  • Create an identity provider image For the provider URL, use https://token.actions.githubusercontent.com.
    For the audience, use sts.amazonaws.com.
  • After creating an identity provider, assign a new role image

  • Create a new role image

  • Specify a custom trust policy image

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:<GITHUB_ORG>/<GITHUB_REPOSITORY>:ref:refs/heads/<GITHUB_BRANCH>"
        }
      }
    }
  ]
}
  • Attach permissions to the IAM role that allow it to access the AWS resources you need. image

Define a GitHub Actions pipeline

  • Define the S3 bucket name as a repository variable
gh variable set S3_BUCKET --body "YOUR-BUCKET-NAME" --repo YOUR-ORG/YOUR-REPO
  • Create .github/workflows/build.yml inside your Git repository. You can use a LaTeX pipeline as an example (the same pattern works for any binary: compiled assets, images, binaries, etc.):
name: Build CV

on:
  push:
    branches: [main]
    paths:
      - '**.tex'
      - '**.cls'
      - '**.sty'
  pull_request:
    branches: [main]
    paths:
      - '**.tex'
      - '**.cls'
      - '**.sty'
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write   # Required for OIDC authentication with AWS
      contents: read    # Required to check out the repository

    steps:
      - uses: actions/checkout@v4

      - name: Install TeX Live
        uses: zauguin/install-texlive@v3
        with:
          packages: |
            scheme-small
            titlesec
            xcharter
            fontawesome5
            simpleicons

      - name: Find TeX Live binary path
        id: texlive
        run: |
          TEXLIVE_BIN=$(find "$HOME/texlive/bin" -name xelatex -type f -o -name xelatex -type l 2>/dev/null | head -1 | xargs dirname)
          echo "bin=$TEXLIVE_BIN" >> "$GITHUB_OUTPUT"

      - name: Build PDF
        run: |
          export PATH="${{ steps.texlive.outputs.bin }}:$PATH"
          xelatex -interaction=nonstopmode cv_aleksandr_dudkin.tex
          xelatex -interaction=nonstopmode cv_aleksandr_dudkin.tex

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: YOUR-ROLE-ARN-HERE
          aws-region: YOUR-REGION-HERE
          audience: sts.amazonaws.com  # Explicit OIDC configuration

      - name: Upload PDF to S3
        run: |
          aws s3 cp YOUR-BINARY-FILE \
            s3://${S3_BUCKET}/YOUR-BINARY-FILE \
            --content-type application/pdf \
            --cache-control "max-age=3600"
        env:
          S3_BUCKET: ${{ vars.S3_BUCKET }}

The three critical pieces for AWS integration are the id-token: write permission, the aws-actions/configure-aws-credentials@v4 step with your role ARN, and the aws s3 cp command that uploads the built file using the repository variable for the bucket name.

Once pushed, every change to your source files triggers a new build and deploys the updated binary to S3 — no more committing PDFs, images, or compiled assets to Git.

Finally, you can use CloudFront to distribute your binaries publicly

For example, try including:

<a href="https://YOUR_CLOUDFRONT_DISTRO_LINK/FILE_NAME" download>click me</a>

in your HTML source code.

Sources: