GitHub Actions CI/CD from push to deploy
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.
Run Actions fasterHow StarSling worksTopics 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.
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.
Real results
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 to start, or hand the prompt below to your coding agent to bring an existing pipeline up to this shape.
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.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 skip docs-only commits to main, and a concurrency group cancels superseded pull request runs. |
| Checkout | Fetches the commit under test. | A shallow checkout of one commit, which is the actions/checkout default. |
| Install | Restores the project's dependencies. | A dependency cache 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). |
| Test | Runs the suite against the commit. | Runs beside the build job, with the suite sharded across a matrix when it is the long pole. |
| Guardrails | Bounds every job and every action it runs. | Bounded job timeouts with timeout-minutes, and third-party actions pinned to full-length commit 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. |
| Deploy | Ships the build to its target. | Short-lived OIDC credentials, with id-token: write granted to the deploy job alone. |
Triggers: run on the events that matter
A workflow starts on events. 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 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 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.
Build and test as parallel jobs
Jobs in a workflow run in parallel by default, so a build job and a test job finish in the time of the longer one. Declare job dependencies 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, 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 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 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.
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: 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.
Environments and approvals
An environment 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.
Secrets and OIDC credentials for deploys
Store credentials as 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.
For cloud deploys, 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 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: it reports the attack paths an outsider can walk end to end, including fork code run with privileges and cache poisoning.
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 |
| Azure | azure/login with client-id, tenant-id and subscription-id, then the Azure CLI. | OpenID Connect in Azure |
| Google Cloud | google-github-actions/auth with workload_identity_provider and service_account, then gcloud. | OpenID Connect 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 |
| GitHub Pages | actions/deploy-pages on a job granted pages: write and id-token: write. | Using custom workflows with GitHub Pages |
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.
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
productionenvironment with required reviewers, and setAWS_DEPLOY_ROLE_ARN,AWS_REGIONandDEPLOY_BUCKETas its variables. - Set
packageManagerinpackage.json;pnpm/action-setupreads 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=blobto each shard and merge withvitest --merge-reports, as the shard tests guide shows. - Deploying somewhere else? Swap the last two steps for a row from the deploy table above.
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. Build only affected projects in a monorepo, cut queue time when jobs wait to start, and right-size runners job by job. The fast GitHub Actions guide 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 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.
Sources: GitHub's documentation
Every GitHub Actions behavior on this page is from GitHub's own documentation:
- GitHub Actions · events that trigger workflows (opens in new tab)
- GitHub Actions · workflow syntax (path filters, needs, timeout-minutes, permissions) (opens in new tab)
- GitHub Actions · control the concurrency of workflows and jobs (opens in new tab)
- GitHub Actions · running variations of jobs (matrix) (opens in new tab)
- GitHub Actions · storing and sharing data with artifacts (opens in new tab)
- GitHub Actions · dependency caching reference (10 GB default per repository, seven-day eviction) (opens in new tab)
- GitHub Actions · deployments and environments (required reviewers, wait timer, branch rules, environment secrets) (opens in new tab)
- GitHub Actions · using secrets (opens in new tab)
- GitHub Actions · OpenID Connect (opens in new tab)
- GitHub Actions · secure use reference (pin actions to a full-length commit SHA) (opens in new tab)
FAQ
What is GitHub Actions CI/CD?
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.
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.
Run your CI/CD pipeline on faster runners
Keep GitHub Actions workflows. Move supported Ubuntu jobs to faster runners, then let optimization PRs improve the workflow over time.
Last reviewed October 6, 2026