GitHub Actions workflow syntax reference — triggers, jobs, secrets, matrix builds, caching, artifacts, and reusable workflows.
Control when a workflow runs.
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "0 2 * * *" ## daily at 2 AM UTC
workflow_dispatch: ## manual trigger from the Actions tab
inputs:
environment:
description: "Target environment"
required: true
default: "staging"
Parameter Breakdown:
push / pull_request: Restrict with branches: or paths: to avoid running on every commitschedule: Cron syntax, always evaluated in UTC regardless of repo settingsworkflow_dispatch: Adds a manual "Run workflow" button, optionally with input fieldsThe core structure of a workflow file.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- run: npm test
- name: Build production bundle
run: npm run build
Parameter Breakdown:
uses: Runs a reusable action from the marketplace or another reporun: Executes a raw shell command on the runnerwith: Passes input parameters into an actionInject configuration without hardcoding values.
jobs:
deploy:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- run: echo "Deploying to ${{ env.NODE_ENV }}"
- run: ./deploy.sh
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Parameter Breakdown:
secrets.<NAME>: Pulled from repo or org-level Settings → Secrets, never logged in plaintextenv at job level applies to every step; env at step level overrides for that step only${{ }}: The expression syntax used to reference contexts like secrets, env, and githubRun the same job across multiple versions or platforms.
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
node-version: [18, 20, 22]
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test
Parameter Breakdown:
strategy.matrix: Generates one job per combination — 2 OS × 3 versions = 6 parallel jobs herefail-fast: false: Lets every matrix combination finish even if one fails — useful for full test visibilitymatrix.<key> anywhere in the jobSpeed up repeat runs by avoiding redundant downloads.
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
- run: npm ci
Parameter Breakdown:
key: A cache miss triggers a fresh install and a new cache save — tie it to the lockfile hashrestore-keys: Fallback prefixes if the exact key isn't found, partial cache is still faster than nonesetup-node@v4 also supports cache: npm built in, simpler for basic casesPass files between jobs or save build output.
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- run: ./deploy.sh dist/
Parameter Breakdown:
needs: build: Forces the deploy job to wait for build to finish successfullyupload-artifact / download-artifact: The standard way to share files between separate jobs (separate runners)Run steps only when specific conditions are met.
steps:
- run: echo "Only runs on main"
if: github.ref == 'refs/heads/main'
- run: echo "Only on pull requests"
if: github.event_name == 'pull_request'
- run: echo "Runs even if a previous step failed"
if: always()
- run: ./notify-failure.sh
if: failure()
Command Parameter Table:
| Function | Description |
|---|---|
always() |
Runs regardless of prior step outcome |
failure() |
Runs only if a previous step in the job failed |
success() |
Default behavior — runs only if everything before it passed |
Share workflow logic across repos.
## .github/workflows/deploy.yml — the reusable workflow
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
DEPLOY_KEY:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh ${{ inputs.environment }}
## calling-workflow.yml — in another workflow
jobs:
call-deploy:
uses: razorpay/shared-workflows/.github/workflows/deploy.yml@main
with:
environment: staging
secrets:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
Parameter Breakdown:
workflow_call: Makes a workflow callable from other workflows, like a functionBuild and publish container images from a workflow.
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: razorpay/orders-api:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Parameter Breakdown:
cache-from/cache-to: type=gha: Uses GitHub Actions' own cache backend for Docker layer cachinggithub.sha: Tags the image with the exact commit, far better than relying on latestdocker/login-action: Authenticates before push — must run before build-push-actionGet more information out of a failing workflow.
## Enable step debug logging — set as a repo secret
## ACTIONS_STEP_DEBUG = true
steps:
- name: Dump GitHub context
run: echo '${{ toJSON(github) }}'
- name: SSH into the runner for live debugging
uses: mxschmitt/action-tmate@v3
if: failure()
Notes:
ACTIONS_STEP_DEBUG=true as a repo secret unlocks verbose step-by-step logs on every runtoJSON(github) dumps the full event payload — useful when a trigger isn't behaving as expectedaction-tmate opens an SSH session into the actual runner, gated to only fire if: failure()