
Mastering GitHub Actions: From Basics to Advanced Workflows
Shamal Iroshan
2025-05-19 | 12 min read
With GitHub Actions, developers can automate their processes directly within the GitHub environment. It is GitHub's native CI/CD platform. GitHub Actions facilitates development and increases efficiency by automating testing, deployments, and repetitive processes.
We'll go over everything you need to know in this blog article so you can start using GitHub Actions and take use of all of its great features.
What is GitHub Actions?
GitHub Actions allows you to automate your software workflows directly from your GitHub repository. You can trigger actions on events like code pushes, pull requests, issue comments, and even on a schedule. It integrates seamlessly with GitHub, making automation an integral part of your development life-cycle.
Key Concepts of GitHub Actions
To effectively use GitHub Actions, it’s essential to understand its core components. Each piece plays a role in building flexible and automated workflows.
1. Workflow
A workflow is the top-level configuration for your automation. It defines what should happen and when. Workflows are written in YAML and stored in the .github/workflows/
directory of your repository.
Example
name: CI Pipeline
on: [push, pull_request]
jobs:
...
You can have multiple workflows in a single repository, each triggered by different events.
2. Events
An event is what triggers a workflow. Common events include
push
: When code is pushed to a branch.pull_request
: When a PR is opened or updated.schedule
: A cron-like scheduled job.workflow_dispatch
: A manual trigger from the GitHub UI.release
,issue_comment
, etc.
Example
on:
push:
branches: [main]
3. Jobs
A job is a collection of steps that run on the same runner (a virtual environment). Jobs can run sequentially or in parallel, depending on how they’re configured.
Example
jobs:
build:
runs-on: ubuntu-latest
steps:
...
test:
needs: build
runs-on: ubuntu-latest
Here, the test
job waits for the build
job to complete.
4. Steps
Steps are individual tasks inside a job. They run sequentially and can either
- Execute shell commands.
- Use an existing action.
Example
steps:
- run: npm install
- run: npm test
5. Actions
An action is a reusable unit of code that performs a task. GitHub provides many official actions, and you can also create or use third-party ones from the GitHub Marketplace.
Example using a prebuilt action
- uses: actions/checkout@v4
6. Runners
A runner is the server that executes the jobs. GitHub offers
- GitHub-hosted runners: Comes pre-configured with common tools (Ubuntu, Windows, macOS).
- Self-hosted runners: Run on your own machines and give more control/customization.
Each job specifies a runner
runs-on: ubuntu-latest
7. Artifacts and Caching
- Artifacts: Used to share files between jobs or store output (e.g., logs, test reports).
- Caching: Used to speed up workflows by saving and reusing dependencies.
Example (caching npm modules)
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
8. Secrets and Environment Variables
- Secrets: Securely store sensitive data (API keys, tokens) in your repo's settings.
- Environment variables: Used to configure workflows or pass values between steps.
Example
env:
NODE_ENV: production
Secrets are accessed like this
- run: echo ${{ secrets.API_KEY }}
These building blocks—workflow, event, job, step, action, runner, artifact/cache, and secrets/env—work together to automate virtually any part of your development process within GitHub.
GitHub Actions Workflow Structure
┌──────────────────────────────┐
│ Repository │
└────────────┬─────────────────┘
│
▼
┌──────────────────────────────┐
│ Workflow │
│ (.github/workflows/*.yml) │
└────────────┬─────────────────┘
│
▼
┌──────────────────────────────┐
│ Event │
│ (push, pull_request, etc.) │
└────────────┬─────────────────┘
│
▼
┌──────────────────────────────┐
│ Job │
│ (runs-on: ubuntu-latest) │
└────────────┬─────────────────┘
│
▼
┌──────────────────────────────┐
│ Step │
│ (run: npm install) │
│ (uses: actions/checkout@v4) │
└────────────┬─────────────────┘
│
▼
┌──────────────────────────────┐
│ Action │
│ (Prebuilt or Custom) │
└──────────────────────────────┘
Explanation of Components
- Repository: The GitHub repository containing your code and workflow files.
- Workflow: Defined in YAML files within the
.github/workflows/
directory; specifies the automation process. - Event: Triggers that initiate the workflow, such as
push
,pull_request
, orschedule
. - Job: A set of steps executed on the same runner; jobs can run sequentially or in parallel.
- Step: Individual tasks within a job; can be shell commands or actions.
- Action: Reusable units of code that perform specific tasks; can be prebuilt (from the GitHub Marketplace) or custom-defined.
Using Pre-built Actions
One of the most powerful features of GitHub Actions is the ability to use pre-built actions—reusable components created by the GitHub community and maintained in public repositories. These save time and reduce the need to write custom scripts for common tasks.
What Is a Pre-built Action?
A pre-built action is a modular, shareable task packaged as a GitHub Action. You can plug it directly into your workflows with just a few lines of YAML. Pre-built actions can
- Set up programming environments
- Deploy apps
- Lint code
- Send notifications
- Integrate with external services
Where to Find Them
You can find thousands of pre-built actions in the GitHub Actions Marketplace. Each action comes with
- Usage instructions
- Inputs and outputs
- Supported platforms
Example: Using Pre-built Actions
Here’s a real example of a CI workflow using several pre-built actions
name: Node.js CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
Explanation
actions/checkout@v4
: Pulls the latest code from the repo.actions/setup-node@v4
: Installs Node.js version 18.- The rest are shell steps to install packages and run tests.
Customizing Pre-built Actions
You can pass inputs to actions to change their behavior
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
And access outputs like this
- id: example
uses: some/action@v1
- run: echo "The result is ${{ steps.example.outputs.result }}"
Security Considerations
- Use actions from trusted sources.
- Avoid using unpinned
@main
or@latest
tags in critical workflows. - Review the source code of unfamiliar actions before using them.
Workflow Triggers
Workflow triggers define when your GitHub Actions workflow should run. They are specified under the on:
key in your workflow file.
Common Triggers
push
: Runs when code is pushed to a branch.
on: push
pull_request
: Runs when a pull request is opened, updated, or reopened.
on: pull_request
schedule
: Runs on a cron schedule (e.g., nightly builds)
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch
: Allows manual triggering via the GitHub UI.
on: workflow_dispatch
release
, issues
, etc.: Event-based triggers for GitHub activities.
Matrix Build in GitHub Actions
A matrix build allows you to automatically run the same job multiple times with different combinations of inputs—like Node versions, OS types, or environment variables—in parallel. This is useful for testing your code across multiple platforms or configurations without duplicating your workflow code.
Why Use Matrix Builds?
- Ensure compatibility across multiple environments (e.g., different Node.js versions).
- Test different dependency versions, flags, or operating systems.
- Reduce manual effort and speed up CI by running jobs in parallel.
How It Works
You define a strategy.matrix
block in your job
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16, 18, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm test
GitHub will create three parallel jobs, each with a different node-version
: 16, 18, and 20.
Secrets and Environment Variables in GitHub Actions
GitHub Actions allows you to manage configuration data and sensitive values through environment variables and secrets. These help you keep your workflows clean, flexible, and secure.
Environment Variables
Environment variables are key-value pairs used to store non-sensitive configuration data.
Ways to Set Environment Variables
- Globally in a job
jobs:
build:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- run: echo $NODE_ENV
- In a single step
- name: Print version
run: echo $VERSION
env:
VERSION: '1.0.0'
Secrets
Secrets are encrypted environment variables used to store sensitive information, such as API keys, tokens, and credentials.
How to Use Secrets
- Add them in your repository or organization settings under Settings → Secrets and variables → Actions.
- Access them in workflows using
${{ secrets.SECRET_NAME }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Use secret
run: echo "The token is ${{ secrets.MY_TOKEN }}"
GitHub masks secrets in logs and restricts their usage to workflows with proper permissions.
Advanced Workflows in GitHub Actions
As your project grows, you may need more complex automation. GitHub Actions supports powerful features to create advanced workflows—enabling you to orchestrate builds, tests, and deployments across multiple conditions and environments.
1. Job Dependencies (needs
)
Control the order of job execution using needs
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building..."
test:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Testing..."
test
will only run after build
completes successfully.
2. Matrix Strategies with Exclusions and Includes
Use complex combinations of inputs and fine-tune with include
and exclude
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [16, 18]
exclude:
- os: windows-latest
node: 16
3. Reusable Workflows
You can call one workflow from another—great for sharing logic (e.g., linting, testing)
jobs:
call-shared:
uses: your-org/.github/.github/workflows/test.yml@main
with:
node-version: '18'
secrets: inherit
Define workflow_call
in the reusable workflow to expose inputs and secrets.
4. Manual Approvals with environments
Add manual approval steps before deploying to production
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://yourapp.com
Set up deployment protection rules via GitHub's Environment settings.
5. Conditionals and Expressions
Run steps or jobs conditionally using if
- name: Deploy
if: github.ref == 'refs/heads/main' && success()
run: ./deploy.sh
You can use
github.*
context (e.g. event, branch)env.*
variablessuccess()
,failure()
, etc.
6. Workflow Reuse and Dispatch Chaining
Trigger one workflow from another using workflow_dispatch
+ repository_dispatch
. This allows advanced chaining across repos or teams.
Debugging GitHub Actions
Debugging GitHub Actions is essential when workflows don't behave as expected. Fortunately, GitHub provides tools and techniques to help you identify and fix issues quickly.
1. View Logs in the Actions Tab
- Go to your repository’s "Actions" tab.
- Click on the failed workflow run.
- Expand each job and step to see real-time output logs, including environment variables, errors, and command outputs.
2. Add Debug Output
You can print custom logs using echo
- run: echo "Step completed"
Use environment variables for more insight
- run: echo "Branch: $GITHUB_REF"
3. Enable Step Debug Logging
You can enable extra debugging by adding secrets to your repository
- Go to Settings → Secrets and variables → Actions → Repository secrets.
- Add
ACTIONS_RUNNER_DEBUG
→true
(enables runner-level debug logs)ACTIONS_STEP_DEBUG
→true
(enables step-level logs)
These reveal internal details like step execution, caching behavior, and environment setup.
4. Use continue-on-error
for Isolation
Use this to allow steps to fail without stopping the entire job—useful for checking multiple things at once
- name: Run optional test
run: ./noncritical.sh
continue-on-error: true
5. Save Artifacts for Postmortem
You can upload logs, test reports, or config files as artifacts to inspect later
- uses: actions/upload-artifact@v4
with:
name: debug-logs
path: ./logs/
Best Practices for Using GitHub Actions
Following best practices ensures your workflows are secure, efficient, and maintainable over time.
1. Secure Secrets and Credentials
- Store sensitive data using GitHub Secrets (never hard-code).
- Use least privilege permissions via
permissions:
block. - Avoid logging secrets—even by mistake.
permissions:
contents: read
deployments: write
2. Use Specific Versions of Actions
Always pin actions to a version (@v3
) instead of using @latest
or @main
to avoid unexpected changes
uses: actions/checkout@v4
3. Re-use and Modularize Workflows
- Use composite actions or reusable workflows to reduce duplication.
- Keep logic DRY and easy to manage across projects or teams.
4. Use Matrix Builds for Compatibility Testing
Run tests across multiple environments (Node versions, OS, etc.) to ensure wider support.
5. Cache Dependencies Smartly
Use actions/cache
to speed up builds but ensure cache keys are precise to avoid stale dependencies
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('**/package-lock.json') }}
6. Keep Workflows in Version Control
Store and version all workflows inside .github/workflows/
so changes are tracked, reviewed, and tested.
7. Limit Workflow Scope
Avoid running workflows on every event unless needed. Use filters like
on:
push:
branches: [main]
paths: ['src/**', 'package.json']
8. Use Context and Expressions Thoughtfully
Use ${{ github.* }}
and ${{ secrets.* }}
to make workflows dynamic, but keep logic readable and clear.
9. Protect Production with Environments
Use environments with manual approvals and secret scoping to protect production deployments.
10. Enable Debugging When Needed
Set ACTIONS_STEP_DEBUG
and ACTIONS_RUNNER_DEBUG
to true for deeper insights during failures.
Conclusion
GitHub Actions is a powerful and flexible CI/CD tool that integrates deeply with your development workflow. Whether you're automating simple build and test pipelines or managing complex multi-environment deployments, it offers the features and extensibility you need—right within your GitHub repository.
By understanding key concepts like workflows, jobs, matrix builds, secrets, and advanced features like reusable workflows and environments, you can create reliable and maintainable automation pipelines. Following best practices around security, performance, and modularity will help you scale with confidence.
Start simple, iterate, and let GitHub Actions handle the heavy lifting so you can focus on building great software.