---
title: "GitHub Actions CI/CD Pipeline: From Push to Deploy, Fast"
description: "Build a GitHub Actions CI/CD pipeline from push to deploy: triggers, build, test, caching, approvals and OIDC deploys, with one complete workflow to copy."
url: https://starsling.dev/github-actions-ci-cd
canonicalUrl: https://starsling.dev/github-actions-ci-cd
dateModified: 2026-10-06
---

# GitHub Actions CI/CD from push to deploy

- [How StarSling works](https://starsling.dev/)

A GitHub Actions CI/CD pipeline takes a commit from push to production in one workflow file. This guide builds it stage by stage: triggers, build, test, caching, environments and approvals, secrets and OIDC credentials, and the deploy step. Each stage is fast by default and links to the guide that teaches it, and the guide ends with one complete workflow to copy.

**Topics covered:** This guide covers triggers and path filters, parallel build and test jobs, dependency caching, test sharding, environments and approvals, secrets and OIDC credentials, deploy steps for AWS, Azure, Google Cloud, GitHub Packages and GitHub Pages, and a complete workflow file.

## Table of contents

- [Canonical answer](#canonical-answer)
- [Real results](#real-results)
- [TL;DR](#tldr)
- [Start here: hand this to your agent](#agent-prompt)
- [The GitHub Actions CI/CD pipeline, stage by stage](#pipeline-stages)
- [Triggers: run on the events that matter](#triggers)
- [Build and test as parallel jobs](#build-and-test)
- [Caching dependencies](#caching)
- [Environments and approvals](#environments-and-approvals)
- [Secrets and OIDC credentials for deploys](#secrets-and-oidc)
- [Deploy steps for common targets](#deploy-targets)
- [The complete GitHub Actions CI/CD workflow](#complete-workflow)
- [Make the pipeline fast](#make-the-pipeline-fast)
- [Sources: GitHub's documentation](#github-docs-sources)

<a id="canonical-answer"></a>

## Canonical answer

A GitHub Actions CI/CD pipeline is one workflow file. Push and pull request events trigger it, build and test run as parallel jobs with dependencies cached on the lockfile and tests sharded across a matrix, and a deploy job that needs both runs on main through a protected environment, signing in to the cloud with short-lived OIDC credentials. Path filters, a concurrency group, job timeouts and actions pinned to commit SHAs keep it fast and safe by default. StarSling runs the same workflow on faster drop-in Ubuntu runners, and its agents open pull requests that keep the pipeline fast.

<a id="real-results"></a>

## Real results

- [16x shorter CI queue time at Partcl](https://starsling.dev/customers/partcl)
- [6x faster test suites at Mastra](https://starsling.dev/customers/mastra)
- [2x faster E2E tests at Better Auth](https://starsling.dev/customers/better-auth)

<a id="tldr"></a>

## TL;DR

- A GitHub Actions CI/CD pipeline is one workflow file in `.github/workflows/`: triggers decide when it runs, jobs build and test the commit, and a deploy job ships it once both pass.
- Fast by default means four habits from the first commit: build and test run as parallel jobs, dependencies come from a cache keyed on the lockfile, long test suites shard across a matrix, and a new push cancels the pull request run it supersedes.
- Production deploys run in a protected environment with required reviewers, and sign in to the cloud with short-lived OIDC credentials scoped to the deploy job.
- [Copy the complete workflow](#complete-workflow) to start, or hand the prompt below to your coding agent to bring an existing pipeline up to this shape.

<a id="agent-prompt"></a>

## Start here: hand this to your agent

Paste this prompt into a coding agent pointed at your repository. It maps the pipeline you have today, then opens one small PR that brings each stage up to the fast default this guide describes, with its reasoning for every change.

Inspect .github/workflows/*.yml and .github/workflows/*.yaml and map this repository's CI/CD pipeline from push to deploy: which events trigger it, which jobs build, test and deploy, what each job caches, which environment and approval gate each deploy, and how each deploy authenticates. Then bring each stage up to a fast, safe default, ordered by how much time or risk each change removes: run build and test as parallel jobs, cache dependencies keyed on the lockfile, shard the longest test job with a matrix, add path filters to workflows that run on changes they cannot affect (gate required checks with a job-level if: condition), add a concurrency group that cancels superseded pull request runs, set timeout-minutes on every job, set least-privilege permissions, pin third-party actions to full-length commit SHAs, and move long-lived cloud keys to OIDC with id-token: write on the deploy job alone. Keep deploy targets, required checks and environment protection rules as they are unless you explain the change. Open one small PR with before/after reasoning for each change.

<a id="pipeline-stages"></a>

## The GitHub Actions CI/CD pipeline, stage by stage

Every stage has a fast default, linked to the guide that teaches it in depth. Start with the defaults, then tune whichever stage owns the most wall-clock time once the pipeline runs.

| Stage | What it does | Fast by default |
| --- | --- | --- |
| Trigger | Starts the pipeline on a push, a pull request or a manual run. | [Path filters](/best-practices/github-actions/path-filter-workflows) skip docs-only commits to `main`, and a concurrency group [cancels superseded pull request runs](/best-practices/github-actions/cancel-superseded-runs). |
| Checkout | Fetches the commit under test. | A [shallow checkout](/best-practices/github-actions/shallow-checkout) of one commit, which is the `actions/checkout` default. |
| Install | Restores the project's dependencies. | A [dependency cache](/best-practices/github-actions/cache-dependencies) keyed on the lockfile, through the setup action's `cache` input. |
| Build | Compiles and packages the app. | Build once and upload the output as an artifact, and the deploy job ships those exact files ([avoid duplicate compilation](/github-actions/optimizations/avoid-duplicate-compilation)). |
| Test | Runs the suite against the commit. | Runs beside the build job, with the suite [sharded across a matrix](/best-practices/github-actions/shard-tests) when it is the long pole. |
| Guardrails | Bounds every job and every action it runs. | [Bounded job timeouts](/best-practices/github-actions/bound-job-timeouts) with `timeout-minutes`, and third-party actions [pinned to full-length commit SHAs](/best-practices/github-actions/pin-action-shas). |
| Approve | Holds production until a person signs off. | A `production` environment with required reviewers and a deployment branch rule, set up as in [environments and approvals](#environments-and-approvals). |
| Deploy | Ships the build to its target. | Short-lived OIDC credentials, with `id-token: write` [granted to the deploy job alone](/best-practices/github-actions/scope-id-token-per-job). |

<a id="triggers"></a>

## Triggers: run on the events that matter

A workflow starts on [events](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows). For CI/CD the usual set is three: `push` to the default branch builds, tests and deploys; `pull_request` builds and tests every proposed change; and `workflow_dispatch` adds a Run workflow button for a manual redeploy.

Two settings keep the pipeline fast from the first commit. A [path filter](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#onpushpull_requestpull_request_targetpathspaths-ignore) such as `paths-ignore` on `push` skips the build and deploy for commits to `main` that touch only Markdown or docs, and when a trigger sets both branch and path filters, GitHub runs the workflow only when both match. A [concurrency group](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency) keyed on the branch, with `cancel-in-progress` set for pull requests, cancels the run a newer push supersedes, so runners spend their minutes on the latest commit.

The `pull_request` trigger runs on every change, so build and test report on every pull request and work as required status checks. To skip expensive jobs on docs-only pull requests too, the path filter guide gates them with a job-level `if:` that keeps required checks reporting.

- [Path-filter workflows](https://starsling.dev/best-practices/github-actions/path-filter-workflows)
- [Cancel superseded runs](https://starsling.dev/best-practices/github-actions/cancel-superseded-runs)

<a id="build-and-test"></a>

## Build and test as parallel jobs

Jobs in a workflow [run in parallel by default](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobs), so a build job and a test job finish in the time of the longer one. Declare [job dependencies](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idneeds) with `needs` only where they are real: the deploy job needs both, and nothing else waits.

The build job uploads its output as an [artifact](https://docs.github.com/en/actions/tutorials/store-and-share-data), and the deploy job downloads it, so production receives the exact files built from the tested commit.

When the test job is the long pole, shard it. A [matrix](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/run-job-variations) runs one copy of the job per shard, and each copy hands its index to the test runner's `--shard` flag, which Vitest, Jest and Playwright all accept.

Give every job a [timeout](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idtimeout-minutes) with `timeout-minutes`. GitHub's default is 360 minutes; a 15 or 20 minute bound ends a hung job quickly and frees its runner for the next one.

- [Shard tests](https://starsling.dev/best-practices/github-actions/shard-tests)
- [Parallelize independent jobs](https://starsling.dev/github-actions/optimizations/parallelize-independent-jobs)
- [Bound job timeouts](https://starsling.dev/best-practices/github-actions/bound-job-timeouts)

<a id="caching"></a>

## Caching dependencies

Dependency installs are the step a cache speeds up most. The setup actions for Node.js, Python, Java, Go, Ruby and .NET carry [built-in caching](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching): `cache: pnpm` on `actions/setup-node` restores the pnpm store keyed on `pnpm-lock.yaml`, so the cache refreshes when dependencies change and restores on every other run.

For other paths, such as build output or a compiler cache, `actions/cache` takes a path and a key you build with `hashFiles()`. Each repository gets 10 GB of cache storage by default, and GitHub removes entries nobody has read in seven days.

- [Cache dependencies](https://starsling.dev/best-practices/github-actions/cache-dependencies)
- [Prevent cache poisoning](https://starsling.dev/best-practices/github-actions/prevent-github-actions-cache-poisoning)

<a id="environments-and-approvals"></a>

## Environments and approvals

An [environment](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments) is where deployment rules live. Add `environment: production` to the deploy job, then configure the environment under the repository's settings.

Required reviewers pause the job until one of up to six listed people or teams approves it. A wait timer holds it for a set number of minutes. Deployment branch rules let `main`, or branches matching a pattern you choose, deploy to the environment.

Environment secrets reach only jobs that reference the environment, and only after its protection rules pass, so the production credential stays with the approved deploy job.

- [GitHub - Managing environments for deployment](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments)

<a id="secrets-and-oidc"></a>

## Secrets and OIDC credentials for deploys

Store credentials as [secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets): repository secrets for the whole pipeline, environment secrets for one environment. Secrets reach the runs triggered from your own repository, and a pull request run from a fork receives the `GITHUB_TOKEN` with [read-only permissions](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#workflows-in-forked-repositories).

For cloud deploys, [OpenID Connect](https://docs.github.com/en/actions/concepts/security/openid-connect) replaces the stored key. Grant the deploy job `id-token: write`, configure your cloud account to trust GitHub's OIDC provider, and the job exchanges a token GitHub issues for each run for a short-lived cloud credential scoped to that run.

Keep the workflow's default `permissions` at `contents: read` and grant `id-token: write` on the deploy job alone. That permission lets a job request and use an OIDC token, and the job's repository access stays at `contents: read`.

Pin every third-party action to a full-length commit SHA, which [GitHub's security guidance](https://docs.github.com/en/actions/reference/security/secure-use) calls the only way to use an action as an immutable release, and keep the version tag as a trailing comment.

To check a repository's workflows against these settings, run the free [/ci-secure skill](/skills/ci-secure): it reports the attack paths an outsider can walk end to end, including fork code run with privileges and cache poisoning.

- [Scope id-token per job](https://starsling.dev/best-practices/github-actions/scope-id-token-per-job)
- [Limit GitHub Actions permissions](https://starsling.dev/best-practices/github-actions/limit-github-actions-permissions)
- [Protect GitHub Actions secrets](https://starsling.dev/best-practices/github-actions/protect-github-actions-secrets)
- [Pin action SHAs](https://starsling.dev/best-practices/github-actions/pin-action-shas)

<a id="deploy-targets"></a>

## Deploy steps for common targets

The deploy step changes with the target and the job around it stays the same. Each row signs in with a short-lived credential: an OIDC token for the clouds and Pages, `GITHUB_TOKEN` for GitHub's container registry.

| Target | Sign in and deploy with | GitHub's walkthrough |
| --- | --- | --- |
| AWS | `aws-actions/configure-aws-credentials` with `role-to-assume` and `aws-region`, then the AWS CLI or your deploy tool. | [OpenID Connect in Amazon Web Services](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws) |
| Azure | `azure/login` with `client-id`, `tenant-id` and `subscription-id`, then the Azure CLI. | [OpenID Connect in Azure](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-azure) |
| Google Cloud | `google-github-actions/auth` with `workload_identity_provider` and `service_account`, then `gcloud`. | [OpenID Connect in Google Cloud Platform](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-google-cloud-platform) |
| Container image on GitHub Packages | `docker/login-action` with registry `ghcr.io` and `GITHUB_TOKEN`, on a job granted `packages: write`. | [Publishing Docker images](https://docs.github.com/en/actions/tutorials/publish-packages/publish-docker-images) |
| GitHub Pages | `actions/deploy-pages` on a job granted `pages: write` and `id-token: write`. | [Using custom workflows with GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages) |

<a id="complete-workflow"></a>

## The complete GitHub Actions CI/CD workflow

One file covers the whole pipeline. It builds and tests every pull request in parallel, shards the test suite four ways, and on a push to `main` or a manual run deploys the build to an S3 bucket through an approved `production` environment with OIDC credentials. Every action is pinned to a full-length commit SHA.

`.github/workflows/ci-cd.yml`:

```yaml
name: CI/CD

on:
  push:
    branches: [main]
    paths-ignore: ["**.md", "docs/**"]
  pull_request:
  workflow_dispatch:

# A new push to a pull request cancels the run it supersedes.
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
      - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: dist
          path: dist

  test:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
      - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm test --shard=${{ matrix.shard }}/${{ strategy.job-total }}

  deploy:
    if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
    needs: [build, test]
    runs-on: ubuntu-latest
    timeout-minutes: 15
    environment: production
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
        with:
          name: dist
          path: dist
      - uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
        with:
          role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}
      - run: aws s3 sync dist "s3://${{ vars.DEPLOY_BUCKET }}" --delete
```

- Create a `production` environment with required reviewers, and set `AWS_DEPLOY_ROLE_ARN`, `AWS_REGION` and `DEPLOY_BUCKET` as its variables.
- Set `packageManager` in `package.json`; `pnpm/action-setup` reads the pnpm version from it.
- Vitest and Jest both accept `--shard=<index>/<count>`; size the shard list to your suite. For one merged Vitest report, add `--reporter=blob` to each shard and merge with `vitest --merge-reports`, as the [shard tests guide](/best-practices/github-actions/shard-tests) shows.
- Deploying somewhere else? Swap the last two steps for a row from the deploy table above.

<a id="make-the-pipeline-fast"></a>

## Make the pipeline fast

Once the pipeline runs, speed comes from two places: less work per run, and faster hardware for the work that remains.

Less work: profile a run, find the stage that owns the most wall-clock time, and apply its fix from the [GitHub Actions CI best-practices catalog](/best-practices/github-actions). [Build only affected projects](/best-practices/github-actions/build-only-affected) in a monorepo, [cut queue time](/best-practices/github-actions/cut-queue-time) when jobs wait to start, and [right-size runners](/best-practices/github-actions/right-size-runners) job by job. The [fast GitHub Actions guide](/fast-github-actions) walks through the whole order of operations.

Faster hardware: StarSling runners are drop-in Ubuntu runners for GitHub Actions. Change `runs-on: ubuntu-latest` to a [StarSling runner label](https://docs.starsling.dev/runners/instance-types) such as `starsling-ubuntu-24.04` on the build and test jobs, and the same workflow runs on faster machines with its triggers, environments and secrets as they are.

StarSling agents keep the pipeline improving after the swap. On paid plans they analyze your workflows, run logs and machine telemetry, then open pull requests with the fixes this guide teaches: caching, test sharding, parallel jobs and workflow structure. You review and merge each one.

- [Fast GitHub Actions](https://starsling.dev/fast-github-actions)
- [GitHub Actions CI best-practices catalog](https://starsling.dev/best-practices/github-actions)
- [StarSling Runners are now generally available](https://starsling.dev/blog/starsling-runners-are-now-generally-available)

<a id="github-docs-sources"></a>

## Sources: GitHub's documentation

Every GitHub Actions behavior on this page is from GitHub's own documentation:

- [GitHub Actions - events that trigger workflows](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows)
- [GitHub Actions - workflow syntax (path filters, needs, timeout-minutes, permissions)](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)
- [GitHub Actions - control the concurrency of workflows and jobs](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency)
- [GitHub Actions - running variations of jobs (matrix)](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/run-job-variations)
- [GitHub Actions - storing and sharing data with artifacts](https://docs.github.com/en/actions/tutorials/store-and-share-data)
- [GitHub Actions - dependency caching reference (10 GB default per repository, seven-day eviction)](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching)
- [GitHub Actions - deployments and environments (required reviewers, wait timer, branch rules, environment secrets)](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments)
- [GitHub Actions - using secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets)
- [GitHub Actions - OpenID Connect](https://docs.github.com/en/actions/concepts/security/openid-connect)
- [GitHub Actions - secure use reference (pin actions to a full-length commit SHA)](https://docs.github.com/en/actions/reference/security/secure-use)

<a id="faq"></a>

## FAQ

### What is GitHub Actions CI/CD?

CI is continuous integration: every change is built and tested automatically. CD is continuous delivery or continuous deployment: every passing change is made ready to release, or released. GitHub Actions CI/CD is a pipeline defined in a YAML workflow file in your repository's .github/workflows directory. Events such as a push or a pull request trigger it, jobs build and test the commit on runners, and a deploy job ships the result to an environment once the build and tests pass.

### What does CI/CD stand for?

CI/CD stands for continuous integration and continuous delivery or continuous deployment, and the first answer in this FAQ covers how a GitHub Actions pipeline runs both.

### Can one GitHub Actions workflow handle both CI and CD?

Yes. Build and test jobs run on every push and pull request, and a deploy job that needs both runs only on pushes to the default branch, through a protected environment. The complete workflow on this page has that shape.

### How do I deploy from GitHub Actions with OIDC?

Grant the deploy job the id-token: write permission, configure your cloud provider to trust GitHub's OIDC provider, and sign in with the provider's action: aws-actions/configure-aws-credentials, azure/login or google-github-actions/auth. The job exchanges the token GitHub issues for each run for a short-lived cloud credential.

### How do I require approval before a production deploy?

Reference an environment in the deploy job with environment: production, and add required reviewers to that environment in the repository settings. The job pauses until one reviewer approves it, and the environment's secrets reach the job only after approval.

### How do I make a GitHub Actions CI/CD pipeline faster?

Run build and test as parallel jobs, cache dependencies keyed on the lockfile, shard long test suites with a matrix, skip docs-only changes with path filters, and cancel superseded pull request runs with a concurrency group. Then move CPU-bound jobs to faster runners: StarSling runners take a one-line runs-on change.

### Should I pin GitHub Actions to a commit SHA?

Yes, for third-party actions. GitHub's security guidance calls a full-length commit SHA the only way to use an action as an immutable release. Keep the version tag in a trailing comment so readers can see which release the SHA points at.

<a id="related-resources"></a>

## Related resources

- [Make GitHub Actions faster](https://starsling.dev/fast-github-actions)
- [GitHub Actions CI best practices](https://starsling.dev/best-practices/github-actions)
- [Cache dependencies](https://starsling.dev/best-practices/github-actions/cache-dependencies)
- [Path-filter workflows](https://starsling.dev/best-practices/github-actions/path-filter-workflows)
- [Shard tests](https://starsling.dev/best-practices/github-actions/shard-tests)
- [Cancel superseded runs](https://starsling.dev/best-practices/github-actions/cancel-superseded-runs)
- [Bound job timeouts](https://starsling.dev/best-practices/github-actions/bound-job-timeouts)
- [Pin action SHAs](https://starsling.dev/best-practices/github-actions/pin-action-shas)
- [Scope id-token per job](https://starsling.dev/best-practices/github-actions/scope-id-token-per-job)
- [Docker builds in GitHub Actions](https://starsling.dev/ci/docker)
- [GitHub Actions pricing](https://starsling.dev/github-actions-pricing)
- [StarSling vs GitHub Actions](https://starsling.dev/compare/github-actions)
- [How Mastra got 6x faster GitHub Actions tests](https://starsling.dev/customers/mastra)
- [Install the free /ci-speedup skill to fix slow GitHub Actions](https://starsling.dev/skills/ci-speedup)
- [Install the /ci-secure skill to close critical attack vectors in GitHub Actions](https://starsling.dev/skills/ci-secure)
- [Install the free /ci-score skill to improve your GitHub Actions setup](https://starsling.dev/skills/ci-score)

<a id="get-started"></a>

## Get started

- [Run your CI/CD pipeline on faster runners](https://github.com/apps/starslingdev)
- [CI best-practices catalog](https://starsling.dev/best-practices/github-actions)
- [Install the free /ci-speedup skill to fix slow GitHub Actions](https://starsling.dev/skills/ci-speedup). Install with `npx skills add starslingdev/skills`.
