Master GitHub Actions from core concepts to production. Learn workflows, jobs, actions, and build complete CI/CD pipelines. Understand repository structure, best practices, and implement automated testing, linting, building, and deployment workflows for modern development teams.

Manual deployments are slow, error-prone, and don't scale. Every time code is pushed, developers manually run tests, build artifacts, and deploy to production. This process is tedious, inconsistent, and prone to human error.
GitHub Actions is a continuous integration and continuous deployment (CI/CD) platform that automates software workflows directly in your repository. Used by millions of developers and organizations, GitHub Actions enables you to build, test, and deploy code automatically whenever you push changes.
In this article, we'll explore GitHub Actions architecture, understand CI/CD fundamentals, and build production-ready pipelines that automate testing, linting, building, and deployment for modern development teams.
Traditional CI/CD approaches had significant limitations:
Separate Tools: Required external services like Jenkins, CircleCI, or Travis CI.
Complex Setup: Difficult to configure and maintain separate infrastructure.
Context Switching: Developers had to leave GitHub to manage CI/CD.
Limited Integration: Poor integration with GitHub features and workflows.
Cost: Expensive infrastructure and licensing fees.
Vendor Lock-in: Difficult to migrate between CI/CD platforms.
Slow Feedback: Delays between code push and test results.
GitHub Actions was built to solve these problems:
Native Integration: Built directly into GitHub, no external tools needed.
Easy Setup: Simple YAML configuration in your repository.
Powerful Automation: Automate any workflow, not just CI/CD.
Community Actions: Thousands of pre-built actions available.
Free for Public Repos: No cost for open-source projects.
Fast Feedback: Immediate test results and notifications.
Flexible: Works with any language, framework, or platform.
Workflow: Automated process defined in YAML, triggered by events.
Event: Trigger that starts a workflow (push, pull_request, schedule, etc.).
Job: Set of steps that execute on the same runner.
Step: Individual task within a job (run command, use action, etc.).
Action: Reusable unit of code that performs a specific task.
Runner: Machine that executes jobs (GitHub-hosted or self-hosted).
Artifact: Files produced by a job that can be used by other jobs.
Secret: Encrypted environment variable for sensitive data.
Event Triggered → Workflow Loaded → Jobs Queued → Runner Assigned → Steps Executed → Results Reported.github/workflows/GitHub Repository
↓
Event Triggered (push, PR, schedule)
↓
Workflow File Loaded (.github/workflows/*.yml)
↓
Jobs Created
↓
Runner Assigned (GitHub-hosted or self-hosted)
↓
Steps Executed
↓
Artifacts & Logs Stored
↓
Status Reported (Checks, Notifications)Workflows are triggered by events and define automation.
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 0 * * *' # Daily at midnight
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: npm testCommon Events:
on:
push: # Code pushed to repository
pull_request: # Pull request opened/updated
schedule: # Scheduled time
workflow_dispatch: # Manual trigger
release: # Release published
issues: # Issue opened/closed
pull_request_review: # PR review submitted
repository_dispatch: # External trigger via APIUse Cases:
Jobs contain steps that execute sequentially or in parallel.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm run lint
- run: npm test
build:
runs-on: ubuntu-latest
needs: test # Wait for test job to complete
steps:
- uses: actions/checkout@v3
- run: npm run build
- uses: actions/upload-artifact@v3
with:
name: build
path: dist/Use Cases:
Actions are reusable units of code.
steps:
# Official GitHub actions
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- uses: actions/upload-artifact@v3
with:
name: build
path: dist/
# Community actions
- uses: codecov/codecov-action@v3
- uses: aquasecurity/trivy-action@master
- uses: docker/build-push-action@v4
# Local actions
- uses: ./.github/actions/custom-actionPopular Actions:
Use Cases:
Runners execute jobs on specified operating systems.
jobs:
test-linux:
runs-on: ubuntu-latest
steps:
- run: echo "Running on Linux"
test-windows:
runs-on: windows-latest
steps:
- run: echo "Running on Windows"
test-macos:
runs-on: macos-latest
steps:
- run: echo "Running on macOS"
test-matrix:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [16, 18, 20]
steps:
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm testAvailable Runners:
Use Cases:
Manage sensitive data securely.
env:
NODE_ENV: production
LOG_LEVEL: info
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v3
- name: Deploy
run: |
echo "Deploying to ${{ secrets.DEPLOY_HOST }}"
curl -X POST ${{ secrets.WEBHOOK_URL }} \
-H "Authorization: Bearer ${{ secrets.API_TOKEN }}"
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}Secret Types:
Use Cases:
Store and retrieve files between jobs.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
# Cache dependencies
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- run: npm install
- run: npm run build
# Upload artifacts
- uses: actions/upload-artifact@v3
with:
name: build-output
path: dist/
retention-days: 5
deploy:
needs: build
runs-on: ubuntu-latest
steps:
# Download artifacts
- uses: actions/download-artifact@v3
with:
name: build-output
path: dist/
- name: Deploy
run: |
echo "Deploying files from dist/"
ls -la dist/Use Cases:
Run steps or jobs based on conditions.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Run only on main branch
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: npm run deploy:prod
# Run only on pull requests
- name: Comment on PR
if: github.event_name == 'pull_request'
run: echo "This is a pull request"
# Run only on failure
- name: Notify on failure
if: failure()
run: echo "Build failed!"
# Run only on success
- name: Notify on success
if: success()
run: echo "Build succeeded!"
# Run always
- name: Cleanup
if: always()
run: rm -rf temp/Condition Functions:
Use Cases:
Control access and deployment environments.
permissions:
contents: read
pull-requests: write
checks: write
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
run: npm run deploy:staging
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
needs: deploy-staging
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: npm run deploy:prodPermission Scopes:
Use Cases:
Report workflow status and results.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm test
# Create check run
- uses: actions/github-script@v6
if: always()
with:
script: |
github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'Test Results',
head_sha: context.sha,
status: 'completed',
conclusion: 'success',
output: {
title: 'All tests passed',
summary: 'No failures detected'
}
})Use Cases:
Share workflows across repositories.
# .github/workflows/test.yml
name: Test
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '18'
secrets:
npm-token:
required: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node-version }}
registry-url: 'https://registry.npmjs.org'
- run: npm install
- run: npm test
env:
NODE_AUTH_TOKEN: ${{ secrets.npm-token }}
# .github/workflows/ci.yml (calling reusable workflow)
name: CI
on: [push, pull_request]
jobs:
test:
uses: ./.github/workflows/test.yml
with:
node-version: '18'
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}Use Cases:
A well-organized repository includes GitHub-specific configuration files and workflows.
.
├── .github/
│ ├── CODEOWNERS # Code ownership rules
│ ├── FUNDING.yml # Sponsorship information
│ ├── SECURITY.md # Security policy
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml # Bug report template
│ │ ├── feature_request.yml # Feature request template
│ │ ├── config.yml # Issue template config
│ │ └── blank.md # Blank issue template
│ ├── PULL_REQUEST_TEMPLATE.md # PR template
│ ├── dependabot.yml # Dependabot configuration
│ ├── labeler.yml # Label automation
│ └── workflows/
│ ├── lint.yml # Linting workflow
│ ├── test.yml # Testing workflow
│ ├── build.yml # Build workflow
│ ├── pr-checks.yml # PR validation
│ ├── release.yml # Release workflow
│ ├── deploy.yml # Deployment workflow
│ └── security.yml # Security scanning
├── CONTRIBUTING.md # Contribution guidelines
├── LICENSE # License file
├── README.md # Project documentation
├── package.json
├── src/
└── tests/Define code ownership and require reviews.
# Global owners
* @maintainer1 @maintainer2
# Frontend code
/src/components/ @frontend-team
/src/pages/ @frontend-team
# Backend code
/src/api/ @backend-team
/src/services/ @backend-team
# DevOps
/docker/ @devops-team
/.github/workflows/ @devops-team
# Documentation
/docs/ @documentation-team
*.md @documentation-team
# Tests
/tests/ @qa-teamUse Cases:
Display sponsorship options.
github: [maintainer1, maintainer2]
patreon: username
ko_fi: username
custom: ['https://example.com/donate']Define security policy and reporting.
# Security Policy
## Reporting a Vulnerability
Please email security@example.com with:
- Description of vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
## Supported Versions
| Version | Supported |
|---------|-----------|
| 2.x | ✅ |
| 1.x | ❌ |
## Security Updates
Security updates are released as patch versions.Standardize issue reporting.
name: Bug Report
description: Report a bug
title: "[BUG] "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting a bug!
- type: textarea
id: description
attributes:
label: Description
description: Describe the bug
placeholder: What happened?
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: How to reproduce the bug
placeholder: |
1. Go to...
2. Click...
3. See error...
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What should happen
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: |
- OS: [e.g. macOS, Windows, Linux]
- Browser: [e.g. Chrome, Firefox]
- Version: [e.g. 1.0.0]
validations:
required: truename: Feature Request
description: Suggest a feature
title: "[FEATURE] "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: Thanks for suggesting a feature!
- type: textarea
id: problem
attributes:
label: Problem Statement
description: What problem does this solve?
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: How should this be implemented?
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Other approaches consideredStandardize PR descriptions.
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Related Issues
Closes #(issue number)
## Testing
- [ ] Unit tests added
- [ ] Integration tests added
- [ ] Manual testing completed
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Comments added for complex logic
- [ ] Documentation updated
- [ ] No new warnings generated
- [ ] Tests pass locally
## Screenshots (if applicable)
Add screenshots for UI changes
## Additional Context
Any additional informationAutomate dependency updates.
version: 2
updates:
# npm dependencies
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "03:00"
open-pull-requests-limit: 5
reviewers:
- "maintainer1"
labels:
- "dependencies"
commit-message:
prefix: "chore(deps):"
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "github-actions"
# Docker
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "docker"Automate label assignment.
# Label based on file changes
frontend:
- changed-files:
- any-glob-to-any-file: 'src/components/**'
backend:
- changed-files:
- any-glob-to-any-file: 'src/api/**'
devops:
- changed-files:
- any-glob-to-any-file: '.github/**'
- any-glob-to-any-file: 'docker/**'
documentation:
- changed-files:
- any-glob-to-any-file: '**/*.md'
tests:
- changed-files:
- any-glob-to-any-file: 'tests/**'name: Lint
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run Prettier
run: npm run format:check
- name: Run TypeScript
run: npm run type-checkname: Test
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- name: Run unit tests
run: npm run test:unit
- name: Run integration tests
run: npm run test:integration
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json
flags: unittests
name: codecov-umbrellaname: Build
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- name: Build application
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build
path: dist/
retention-days: 5
- name: Check bundle size
run: npm run build:analyzename: PR Checks
on:
pull_request:
branches: [main, develop]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Test
run: npm run test
- name: Build
run: npm run build
- name: Check for breaking changes
run: npm run check:breaking-changes
- name: Generate coverage report
run: npm run test:coverage
- name: Comment PR with results
if: always()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '✅ All checks passed!'
})name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- name: Build
run: npm run build
- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
Changes in this Release
- Feature 1
- Feature 2
draft: false
prerelease: false
- name: Upload artifacts
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/app.zip
asset_name: app.zip
asset_content_type: application/zipname: Deploy
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run build
- name: Deploy to staging
run: |
npm run deploy:staging
env:
DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}
DEPLOY_HOST: ${{ secrets.STAGING_HOST }}
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
needs: deploy-staging
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run build
- name: Deploy to production
run: |
npm run deploy:prod
env:
DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}
DEPLOY_HOST: ${{ secrets.PROD_HOST }}
- name: Notify deployment
if: success()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-H 'Content-Type: application/json' \
-d '{"text":"Deployment successful!"}'name: Security
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
- cron: '0 0 * * 0' # Weekly
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: Run npm audit
run: npm audit --audit-level=moderate
- name: SAST with CodeQL
uses: github/codeql-action/init@v2
with:
languages: 'javascript'
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2name: Labeler
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
label:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/labeler@v4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/labeler.yml# ❌ Wrong - secrets in workflow
- name: Deploy
run: |
curl -H "Authorization: Bearer abc123xyz" https://api.example.com
# ✅ Correct - use secrets
- name: Deploy
run: |
curl -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" https://api.example.com# ❌ Wrong - reinstalls dependencies every time
- run: npm install
- run: npm test
# ✅ Correct - cache dependencies
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
- run: npm test# ❌ Wrong - runs all combinations
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [14, 16, 18, 20]
# Results in 12 jobs
# ✅ Correct - only necessary combinations
strategy:
matrix:
include:
- os: ubuntu-latest
node-version: 18
- os: windows-latest
node-version: 18
- os: macos-latest
node-version: 18# ❌ Wrong - no error handling
- name: Deploy
run: npm run deploy
# ✅ Correct - handle errors
- name: Deploy
run: npm run deploy
continue-on-error: true
- name: Notify on failure
if: failure()
run: echo "Deployment failed"# ❌ Wrong - sequential jobs
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
lint:
needs: test
runs-on: ubuntu-latest
steps:
- run: npm run lint
# ✅ Correct - parallel jobs
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint# ❌ Wrong - uses latest
- uses: actions/checkout@latest
# ✅ Correct - uses specific version
- uses: actions/checkout@v3.github/workflows/
├── lint.yml # Code quality
├── test.yml # Testing
├── build.yml # Building
├── security.yml # Security scanning
├── deploy.yml # Deployment
└── release.yml # Release process# Share common logic across workflows
jobs:
test:
uses: ./.github/workflows/test.yml
with:
node-version: '18'# Require workflows to pass before merge
# Settings → Branches → Branch protection rules
# → Require status checks to pass before merging# Track execution time
- name: Report timing
run: |
echo "Workflow completed in ${{ job.duration }} seconds"environment:
name: production
url: https://example.com# Add comments explaining complex logic
# Use descriptive job and step names
- name: Run security scan with Trivy
run: trivy scan .# Use act to test workflows locally
act -j testGitHub Actions transforms CI/CD from complex infrastructure to simple, integrated automation. Understanding workflows, jobs, actions, and best practices enables you to build reliable, efficient pipelines.
Key takeaways:
Next steps:
GitHub Actions makes CI/CD accessible and powerful. Master it, and you'll build systems that are reliable, fast, and maintainable.