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
+------------------------------------------+| .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:
## .github/workflows/deploy.yamlname: Build and Deploy ## What triggers this workflowon: push: branches: [main] pull_request: branches: [main] ## Environment variables available to all jobsenv: 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 productionOIDC with AWS (no long-lived keys):
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 |
TipUse
actions/cachewith 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.
RememberPin action versions to a full commit SHA in production workflows, not a tag.
uses: actions/checkout@v4can be silently updated by the action owner.uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683is immutable. This is supply chain security for your pipeline.
SecurityNever use
pull_request_targetwith checkout of the head commit from a fork. This pattern lets a fork PR execute code with access to your repository secrets. Usepull_requestfor untrusted contributions — it runs in an isolated context without secret access.
Common MistakeStoring 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.