Customizing a Standard Docker Image and Pushing to AWS ECR with GitHub Actions

Customizing a Standard Docker Image and Pushing to AWS ECR with GitHub Actions

Using pre-built Docker images is convenient, but sometimes, we need to customize a standard Docker image to fit our needs—whether by adding custom CSS, modifying configurations, or installing dependencies.

In this guide, we’ll customize Grafana, add a custom CSS file, and push our modified image to Amazon Elastic Container Registry (ECR) using GitHub Actions with OpenID authentication (OIDC). This approach removes the need for AWS access keys, making deployments more secure.

Step 1: Customizing the Grafana Docker Image

To modify Grafana and add custom styling, we need to extend the base image and inject our files.

1.1 Create a Custom Dockerfile

We’ll start by creating a Dockerfile that extends the official Grafana image and adds our custom CSS:

FROM grafana/grafana:latest

# Copy custom CSS to the correct location
COPY custom-style.css /usr/share/grafana/public/css/custom-style.css

# Set file permissions
RUN chmod 644 /usr/share/grafana/public/css/custom-style.css

Step 2: Building and Testing Locally

Before pushing the image to AWS ECR, we should test it locally:

docker build -t my-custom-grafana .
docker run -d -p 3000:3000 my-custom-grafana

If everything looks good, we can automate the build and push process using GitHub Actions.

Step 3: Setting Up AWS OpenID Connect (OIDC) for GitHub

Instead of using AWS access keys, we’ll authenticate GitHub Actions using AWS OpenID Connect (OIDC).

3.1 Create an IAM Role for GitHub Actions

First, create a trust policy (github-oidc-trust-policy.json):

{
  "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": {
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:<GITHUB_USER>/<REPO>:ref:refs/heads/main"
        }
      }
    }
  ]
}

Then, create the IAM Role:

aws iam create-role --role-name GitHubActionsECRRole \
  --assume-role-policy-document file://github-oidc-trust-policy.json

3.2 Attach Permissions to Push to ECR

Create a permissions policy (github-ecr-policy.json):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:PutImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload"
      ],
      "Resource": "*"
    }
  ]
}

Attach this policy to the IAM role:

aws iam put-role-policy --role-name GitHubActionsECRRole \
  --policy-name GitHubECRPushPolicy \
  --policy-document file://github-ecr-policy.json

Step 4: Creating the AWS ECR Repository

Before pushing our Docker image, we need to create an ECR repository:

aws ecr create-repository --repository-name my-custom-grafana

Step 5: Automating Image Push with GitHub Actions

Now, we’ll create a GitHub Actions workflow that builds and pushes our custom Grafana image to AWS ECR whenever we push to the main branch.

5.1 Define the GitHub Actions Workflow

Create .github/workflows/docker-ecr-push.yml:

name: Build and Push to ECR

on:
  push:
    branches:
      - main  # Runs on every push to main

permissions:
  id-token: write
  contents: read

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Configure AWS Credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::<AWS_ACCOUNT_ID>:role/GitHubActionsECRRole
          aws-region: us-east-1  # Change to your AWS region

      - name: Log in to Amazon ECR
        run: |
          aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com

      - name: Build and tag Docker image
        run: |
          docker build -t my-custom-grafana .
          docker tag my-custom-grafana:latest <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/my-custom-grafana:latest

      - name: Push Docker image to Amazon ECR
        run: |
          docker push <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/my-custom-grafana:latest

Step 6: Testing the Workflow

Once this is set up:

  1. Push your code to GitHub:
git add .
git commit -m "Custom Grafana image with GitHub Actions and ECR"
git push origin main
  1. Monitor the GitHub Actions workflow in your repository (Actions tab).
  2. Once the image is pushed, deploy it using AWS Fargate, ECS, or Kubernetes.

Conclusion

We’ve successfully:

  • Customized a standard Docker image (Grafana)
  • Added custom CSS and modified configurations
  • Built and tested the image locally
  • Set up AWS OpenID authentication for GitHub Actions
  • Automated pushing the image to AWS ECR

This approach removes the need for long-lived AWS credentials while making deployments secure and repeatable. Now, every time you push changes, your custom Docker image is automatically built and updated in AWS ECR.

Overwhelmed by AWS?

Struggling with infrastructure? We streamline your setup, strengthen security & optimize cloud costs so you can build great products.

Related AWS best practices blogs

Looking for more interesting AWS blog posts?

Reduce AWS Fargate pull times with SOCI

One of the major drawbacks of AWS Fargate is that the pull times are relatively slow (compared to EC2). This is because EC2 nodes can have a local image cache on the instance. Fargate is serverless co ...

Read more
build process

How to overcome "Unsupported Wildcard In Principal"

If you want to create an policy that wildcards the Principal AWS element in an IAM trust policy you will get an error.

Read more

Terraform module for Prowler security scans

As a solution architect one of the pillars for a solution is cost. There are a lot of paid security scanners for your AWS accounts out there but most of them are quite pricey. For start-ups this cost ...

Read more

What EC2 instance type should I choose?

Learn what EC2 instance to use in AWS's eu-west-1 region. Explore T-series, Graviton-powered M6G, and other instances for optimal pricing, performance, and cost-saving strategies.

Read more

Why do S3 pre signed URLs expire after 12 hours, despite setting a longer duration?

S3 objects can be requested through a so called pre signed URLs, however the pre signed URL is tied to the identity that generated the URL. This means that if the credentials expire that generated thi ...

Read more

You do not need that bastion host, there are better alternatives

This article discusses why you do not need that bastion host and what the alternatives are. Do you have any further questions after reading this article? If so, please contact me.

Read more

Lost access to your AWS EC2 instance?

If you lose access to your EC2 instance because you have lost your SSH key, here is a quick way you might be able recover the instance with

Read more