Skip to main content

GitHub Actions

GitHub Actions is GitHub's built-in CI/CD platform that runs automated workflows in YAML files under .github/workflows/. Workflows trigger on repository events — push, pull request, schedule — and run jobs on GitHub-hosted or self-hosted runners.

Understanding GitHub Actions

What Is GitHub Actions in Simple Terms

GitHub Actions is the CI/CD system built directly into GitHub. Instead of connecting a separate Jenkins server or CircleCI account, you create a YAML file in your repository and GitHub runs it automatically when you push code. No infrastructure to manage, no plugins to install, no separate dashboard to learn — it lives where your code lives.

Every Indian product company building on GitHub — CRED, Razorpay, Meesho — uses GitHub Actions as the default starting point for automation. From running tests on every pull request to deploying to Kubernetes on every merge to main.

How It Works

◈ DIAGRAM
+------------------------------------------+
| .github/workflows/ci.yaml |
| |
| on: push to main |
| or pull_request |
+------------------------------------------+
|
event fires
|
v
+------------------------------------------+
| GitHub schedules the workflow |
| Picks an available runner |
| ubuntu-latest VM spins up |
+------------------------------------------+
|
v
+------------------------------------------+
| Jobs run (parallel where possible) |
| |
| build -> [test, lint] -> deploy |
| (parallel) |
+------------------------------------------+
|
v
+------------------------------------------+
| Results posted back to GitHub |
| PR status check: pass or fail |
| Deployment environment updated |
+------------------------------------------+

Workflow anatomy:

YAML
## .github/workflows/deploy.yaml
name: Build and Deploy
## What triggers this workflow
on:
push:
branches: [main]
pull_request:
branches: [main]
## Environment variables available to all jobs
env:
ECR_REGISTRY: 123456789.dkr.ecr.ap-south-1.amazonaws.com
IMAGE_NAME: payment-api
jobs:
build:
name: Build Docker Image
runs-on: ubuntu-latest ## GitHub-hosted runner
## Expose values to downstream jobs
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
## Check out the repository code
- uses: actions/checkout@v4
## Set up Docker Buildx for layer caching
- uses: docker/setup-buildx-action@v3
## Generate image metadata (tags, labels)
- name: Generate image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=,suffix=,format=short
## Build and push the Docker image
- name: Build and push
uses: docker/build-push-action@v5
with:
push: ${{ github.ref == 'refs/heads/main' }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
test:
name: Run Tests
needs: build ## wait for build to complete
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
deploy:
name: Deploy to Production
needs: [build, test]
runs-on: ubuntu-latest
environment: production ## requires manual approval
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy
run: |
IMAGE_TAG=${{ needs.build.outputs.image-tag }}
kubectl set image deployment/payment-api \
payment-api=$IMAGE_TAG -n production

OIDC with AWS (no long-lived keys):

YAML
permissions:
id-token: write ## required for OIDC
contents: read
steps:
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-actions-deploy
aws-region: ap-south-1
## No access key or secret key needed
- name: Push to ECR
run: |
aws ecr get-login-password | docker login --username AWS \
--password-stdin $ECR_REGISTRY
docker push $ECR_REGISTRY/$IMAGE_NAME:${{ github.sha }}

Troubleshooting

Symptom Command What to Check
Workflow not triggering Check Actions tab in GitHub Branch name matches trigger filter
OIDC auth failing Check IAM trust policy GitHub repo and branch in condition
Cache not hitting Check cache key Lockfile changed, key mismatch
Job stuck in queue Check runner availability All runners busy or labels mismatch
Tip

Use actions/cache with a lockfile-based key for dependency caching. key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} gives a cache hit when dependencies have not changed and a fresh install when they have. This alone can cut 3-4 minutes off every pipeline run.

Remember

Pin action versions to a full commit SHA in production workflows, not a tag. uses: actions/checkout@v4 can be silently updated by the action owner. uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 is immutable. This is supply chain security for your pipeline.

Security

Never use pull_request_target with checkout of the head commit from a fork. This pattern lets a fork PR execute code with access to your repository secrets. Use pull_request for untrusted contributions — it runs in an isolated context without secret access.

Common Mistake

Storing AWS access keys and secret keys as GitHub secrets. OIDC eliminates the need for long-lived credentials entirely. With OIDC, GitHub generates a short-lived token per job that AWS exchanges for temporary credentials scoped to exactly the IAM role you specify. Set up OIDC once and remove all static cloud credentials from your secrets.

Frequently Asked Questions

What differentiates GitHub Actions from a standalone CI tool like Jenkins?

GitHub Actions is built directly into the GitHub product, so workflows trigger on native repo events (push, PR, issue comment, release) without webhooks or a separate integration layer, and workflow status shows inline in PRs automatically. It also has a public Marketplace of reusable Actions (community-built steps like linting or deployment) that can be referenced by name in a workflow YAML file, reducing how much CI glue code teams write from scratch compared to a self-hosted Jenkins pipeline.

What's a common security mistake with GitHub Actions workflows?

Referencing a third-party Action by a mutable tag like `@v2` instead of a pinned commit SHA lets the Action's maintainer (or an attacker who compromises their repo) silently change what code runs in your pipeline, including exfiltrating secrets exposed via `secrets.*`. Best practice is pinning Actions to a full commit SHA and reviewing `pull_request_target` usage carefully, since that trigger runs with access to secrets even for PRs from forks.