Skip to main content

Command Palette

Search for a command to run...

I Stopped Using AWS Access Keys in GitHub Actions — Here’s the Secure Way Instead.

Deploying Docker to AWS ECS with GitHub OIDC

Updated
5 min read
O
Cloud Engineer | AWS/Azure Architect | Web Developer |Optimizing scalable systems & reducing latency | Sharing daily insights on Infrastructure as Code (IaC) and DevOps.

One of the most common ways developers deploy applications to AWS using GitHub Actions looks like this:

  1. Create an IAM User

  2. Generate an Access Key and Secret Key

  3. Store them inside GitHub Secrets

  4. Hope they never leak

This approach works, but it has a major problem.

Long-lived credentials are one of the biggest causes of cloud security incidents.

Access keys can be leaked, forgotten, or exposed in logs, and rotating them manually is often neglected.

Fortunately, AWS and GitHub provide a much better solution: OpenID Connect (OIDC).

In this article, I’ll walk through how I built a zero-secret CI/CD pipeline that securely deploys a Dockerized Node.js application to AWS ECS Fargate using GitHub Actions and OIDC authentication.


What We’re Building

The pipeline automatically deploys our application whenever code is pushed to GitHub.

Workflow Overview

Git Push
   ↓
GitHub Actions
   ↓
OIDC Authentication
   ↓
Docker Build
   ↓
Amazon ECR
   ↓
ECS Fargate Deployment
   ↓
CloudWatch Logs

Pipeline Features

  • Secure OIDC authentication (no stored secrets)

  • Docker image builds on every push

  • Images pushed to Amazon ECR

  • Automated deployment to AWS ECS Fargate

  • CloudWatch logging

  • Self-healing containers

Deployment time from push to production is about 3 minutes.


Understanding OIDC in 60 Seconds

Traditional Approach

GitHub Secrets → AWS Access Keys → AWS API
(long-lived credentials stored in GitHub)

Problems:

  • Keys can leak

  • Must rotate manually

  • Security audits become painful

OIDC Approach

GitHub Workflow → OIDC Token → AWS STS → Temporary Credentials

AWS verifies that the request came from a trusted GitHub repository and issues temporary credentials valid for about 1 hour.

No credentials are stored anywhere.

This is why OIDC is now considered a best practice for CI/CD pipelines.


Phase 1 — The Application

The application is a simple Node.js HTTP server returning a JSON response.

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });

  res.end(JSON.stringify({
    message: 'Hello from ECS!',
    version: process.env.APP_VERSION || '1.0.0'
  }));
});

server.listen(3000, () => console.log('Server running on port 3000'));

Containerizing the Application

We package the application using Docker.

Using Alpine Linux keeps the image lightweight.

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install --production

COPY . .

EXPOSE 3000

CMD ["node", "app.js"]

Build and test locally:

docker build -t my-app .
docker run -p 3000:3000 my-app

Test the API:

curl http://localhost:3000

If everything works locally, we are ready to deploy.


Phase 2 — Amazon ECR

Amazon Elastic Container Registry stores our Docker images.

Create the repository:

aws ecr create-repository \
  --repository-name my-app \
  --region us-east-1 \
  --image-scanning-configuration scanOnPush=true \
  --image-tag-mutability IMMUTABLE

Why these settings matter

scanOnPush

Automatically scans images for security vulnerabilities.

IMMUTABLE tags

Prevents overwriting image tags, making deployments reproducible.

Push the image:

docker tag my-app:latest ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/my-app:v1.0.0
docker push ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/my-app:v1.0.0

We also add a lifecycle policy to keep the registry clean.


Phase 3 — ECS Cluster and Service

Amazon ECS runs our containerized application.

Four key ECS concepts:

Concept Meaning
Cluster Pool of compute resources
Task Definition Container blueprint
Task Running container
Service Maintains desired number of tasks

Create the cluster:

aws ecs create-cluster --cluster-name my-app-cluster --capacity-providers FARGATE

Create CloudWatch logs:

aws logs create-log-group --log-group-name /ecs/my-app

Deploy the ECS service:

aws ecs create-service \
  --cluster my-app-cluster \
  --service-name my-app-service \
  --task-definition my-app \
  --desired-count 1 \
  --launch-type FARGATE

Once deployed, ECS runs the container automatically.


Phase 4 — GitHub Actions CI/CD Pipeline

Now we automate everything.

The workflow runs on every push to the main branch.

name: Build & Deploy to ECS

on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-actions-deploy
          aws-region: us-east-1

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

      - name: Build and Push Image
        run: |
          docker build -t my-app .
          docker push IMAGE_URI

Every deployment uses the Git commit SHA as the image tag, making releases fully traceable.


Monitoring with CloudWatch

Application logs are sent to Amazon CloudWatch.

You can stream logs in real time:

aws logs tail /ecs/my-app --follow

This makes debugging production issues much easier.


Self-Healing Containers

ECS services automatically maintain the desired number of tasks.

To demonstrate this, you can manually stop a running task.

aws ecs stop-task --cluster my-app-cluster --task TASK_ID

Within seconds, ECS launches a new container automatically.

No human intervention required.


Cost Breakdown

Resource Monthly Cost
Fargate (0.25 vCPU / 0.5GB RAM) ~$9.00
ECR Storage ~$0.05
CloudWatch Logs ~$0.50

Total cost is about $9.55 per month.

Scaling the service to 0 tasks when idle reduces costs even further.


Key Takeaways

  • OIDC eliminates the need for long-lived AWS credentials

  • Git commit SHA tags make deployments traceable

  • ECS Fargate removes server management

  • CloudWatch provides centralized logging

  • ECS services provide built-in self-healing

This architecture provides a secure, scalable, and automated deployment pipeline suitable for modern cloud applications.


Final Thoughts

Moving away from IAM access keys toward OIDC-based authentication is one of the simplest ways to improve the security of your CI/CD pipelines.

By combining:

  • GitHub Actions

  • OIDC authentication

  • Docker containers

  • AWS ECS Fargate

you can build a fully automated, zero-secret deployment pipeline that is both secure and production-ready.