GitHub Actions FundamentalsIntroductionContinuous integration and continuous delivery (CI/CD) automate the path from a change in source code to a tested, approved, and deployable software release. GitHub Actions is GitHub’s event-driven automation platform. It enables teams to define workflows in YAML files stored in a repository and execute them on hosted or self-hosted runners. A GitHub Actions workflow can compile software, run tests, build container images, publish packages, deploy infrastructure, generate documentation, and notify stakeholders. Its value does not come merely from replacing manual commands. Properly designed workflows make software delivery repeatable, observable, auditable, and easier to govern. This chapter develops the foundational concepts needed to create professional workflows: events, YAML syntax, jobs, steps, actions, runners, contexts, expressions, matrices, artifacts, environments, secrets, permissions, and reusable workflows. Learning ObjectivesBy the end of this chapter, a learner should be able to: - explain the architecture and execution model of GitHub Actions;
- create event-driven workflows using valid YAML;
- distinguish workflows, jobs, steps, actions, and runners;
- use contexts and expressions safely;
- apply matrices for multi-version or multi-platform testing;
- preserve build outputs with artifacts;
- protect deployments with environments and approvals;
- manage secrets and the principle of least privilege;
- design reusable workflows for consistent organizational automation; and
- evaluate security, maintainability, and operational limitations.
1. The GitHub Actions Execution ModelA workflow is an automated process defined in a YAML file under .github/workflows/. A workflow starts when one or more specified events occur. It contains one or more jobs. Each job runs on a runner and contains ordered steps. A step may execute a shell command or invoke a reusable action. The relationship can be expressed as: event → workflow → job → runner → steps → commands or actions A job is normally isolated from other jobs. If two jobs must exchange files, the workflow should explicitly use artifacts, caches, or another storage mechanism. Jobs can run in parallel unless dependencies are declared with needs. A minimal workflow is: name: Continuous Integration
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
run: pytestThe name field gives the workflow a human-readable label. The on field defines triggers. permissions limits the workflow token. The jobs mapping defines executable units. runs-on selects the runner, while uses invokes an action and run executes a command. 2. Events and Event FiltersAn event is a change or request that can start a workflow. Common events include: | Event | Typical use |
|---|
push | Test changes committed to a branch | pull_request | Validate proposed changes before merging | workflow_dispatch | Start a workflow manually | schedule | Run periodic maintenance or scans | release | Publish or deploy a release | workflow_call | Invoke a reusable workflow | workflow_run | Start work after another workflow completes |
Filters reduce unnecessary executions. For example: on:
push:
branches: [main, develop]
paths:
- 'src/**'
- 'tests/**'
- 'requirements.txt'A manually started workflow may define typed inputs: on:
workflow_dispatch:
inputs:
environment:
description: Deployment target
required: true
type: choice
options: [staging, production]Event choice is an architectural decision. Pull-request workflows should provide rapid feedback and should not normally receive write permissions or production secrets. Production deployment is better associated with a protected branch, release, tag, or explicitly approved manual operation. 3. YAML Structure, Jobs, and StepsYAML uses indentation to represent structure. Spaces, not tabs, should be used. A malformed indentation level can prevent a workflow from loading, so validation should be part of development practice. Jobs can be sequential: jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./build.sh
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: ./deploy.shHere, deploy starts only after build succeeds. A job may depend on several jobs: needs: [build, security_scan] By default, a failed dependency prevents dependent jobs from running. Conditional expressions such as if: ${{ always() }} can be used for cleanup or reporting, but broad use can accidentally deploy after a failed build. Steps execute in order within a job. A step can have an identifier, environment variables, a condition, a timeout, and a working directory: - name: Run integration tests
working-directory: service
env:
TEST_MODE: integration
timeout-minutes: 15
run: ./run-integration-tests.shA step’s shell command executes on the selected operating system. Shell syntax therefore matters: Bash commands are not automatically portable to Windows PowerShell. 4. Actions and RunnersAn action is a packaged unit of automation. It may be maintained by GitHub, a vendor, a community, or the organization itself. Examples include actions/checkout, language setup actions, cloud-provider authentication actions, and artifact actions. Action references should be governed carefully. A major version such as actions/checkout@v4 receives compatible updates within that major line, while a full commit SHA gives stronger immutability and provenance. Organizations should review third-party actions, restrict allowed actions where appropriate, and monitor changes. A runner is the machine or execution environment that performs a job. GitHub-hosted runners are ephemeral virtual machines. Self-hosted runners are managed by the organization and may provide specialized hardware or private network access, but they introduce patching, isolation, credential, and lifecycle responsibilities. A runner does not necessarily preserve files between jobs. If a later job requires a file created by an earlier job, use an artifact or regenerate the file. Caches are intended primarily to accelerate dependency retrieval; they should not be treated as the authoritative storage of build outputs. 5. Contexts and ExpressionsContexts provide event and execution data. Important contexts include: | Context | Examples of information |
|---|
github | Repository, branch, event name, commit SHA | env | Workflow, job, or step environment variables | vars | Organization, repository, or environment variables | secrets | Encrypted secret values | steps | Outputs from earlier steps | needs | Outputs and results of dependent jobs | matrix | Current matrix combination | runner | Operating system and runner metadata | inputs | Manual or reusable-workflow inputs |
Expressions are enclosed in ${{ }}. For example: - name: Identify event
run: echo "Event is ${{ github.event_name }}"
- name: Deploy only from main
if: ${{ github.ref == 'refs/heads/main' }}
run: ./deploy.shSome fields automatically evaluate expressions, while explicit expression syntax improves clarity. Boolean, comparison, logical, and function operations are available. A common condition is: if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}Expressions should not be confused with shell interpolation. ${{ github.sha }} is evaluated by GitHub before the command runs; $HOME is interpreted by the shell on the runner. A step can publish an output through the environment file: - id: version
run: echo "value=1.4.0" >> "$GITHUB_OUTPUT"
- run: echo "Version is ${{ steps.version.outputs.value }}"Do not place untrusted issue titles, pull-request text, or commit messages directly into shell code. Treat event data as input and quote or validate it to reduce injection risk. 6. Matrix StrategiesA matrix runs a job for each combination of declared values. It is useful for testing supported language versions, operating systems, or database versions. jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node: ['20', '22']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm testThis configuration creates four job instances: two operating systems × two Node.js versions. In general, if a matrix has dimensions with cardinalities n₁, n₂, …, nₖ, the number of combinations is: N = ∏ᵢ₌₁ᵏ nᵢ Here, N is the number of generated job instances and nᵢ is the number of values in dimension i. The formula assumes no exclusions or additional combinations. Matrices improve coverage but increase cost and queue time. Use include to add special combinations and exclude to remove unsupported ones. 7. Artifacts, Caches, and Job OutputsAn artifact is a named file or directory retained from a workflow run. Typical artifacts include test reports, compiled packages, coverage reports, and deployment manifests. - name: Build package
run: npm run build
- name: Upload package
uses: actions/upload-artifact@v4
with:
name: web-package
path: dist/
retention-days: 14A later job can download it: - uses: actions/download-artifact@v4
with:
name: web-package
path: dist/Artifacts are suitable for transferring explicit outputs between jobs. Caches, commonly implemented with dependency-cache actions, are optimization mechanisms. A cache miss should not break a correct build. Job outputs are better for small values such as a version string, not large files. 8. Environments, Secrets, and PermissionsAn environment represents a deployment target such as staging or production. Environment rules can require reviewers, restrict branches, and expose environment-specific secrets only after protection requirements are satisfied. deploy:
environment:
name: production
url: https://example.org
runs-on: ubuntu-latest
steps:
- run: ./deploy.shSecrets are encrypted values supplied through repository, organization, or environment configuration: env:
CLOUD_TOKEN: ${{ secrets.CLOUD_TOKEN }}Secrets should not be printed, placed in source code, embedded in artifacts, or passed to untrusted pull-request code. Secret masking reduces accidental disclosure but is not a substitute for careful design. Forked pull requests generally do not receive ordinary repository secrets, which protects the repository but may require a separate trusted validation design. The automatically provided GITHUB_TOKEN should have the smallest necessary permission set: permissions:
contents: read
pull-requests: write Permissions can be set at workflow or job level. A read-only default is a sound baseline. Grant write access only to the job that requires it. Production authentication should preferably use short-lived, federated credentials such as OpenID Connect rather than long-lived cloud keys. 9. Reusable WorkflowsReusable workflows reduce duplication and standardize organizational practices. They are invoked with workflow_call: on:
workflow_call:
inputs:
language:
required: true
type: string
secrets:
registry_token:
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Building ${{ inputs.language }}"A caller uses: jobs:
standard-build:
uses: company/platform/.github/workflows/build.yml@v1
with:
language: Python
secrets: inheritReusable workflows should have clear contracts: typed inputs, declared secrets, documented outputs, stable version references, and explicit permissions. secrets: inherit is convenient but should be used only when the called workflow is trusted and its secret requirements are understood. 10. A Complete Applied ScenarioIllustrative case: Northstar Inventory ServiceBackground. Northstar maintains a Python inventory API. Developers submit pull requests, and the operations team deploys approved changes to staging and production. Problem. Tests were run inconsistently on personal machines. Deployment was manual, credentials were shared, and a failed release was difficult to trace. Analysis. The team identified four control points: dependency installation and unit tests, security scanning, creation of an immutable package, and protected deployment. It also determined that pull requests must not receive production credentials. Approach. Northstar implemented a workflow with separate validation and deployment jobs. Validation runs on pull requests and pushes to main. The build job uploads a package artifact. Deployment requires the build and scan jobs, uses the production environment, and obtains short-lived cloud credentials through an approved identity mechanism. Workflow-level permissions are read-only; deployment receives only the additional permission it needs. A simplified dependency structure is: jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements.txt
- run: pytest
package:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./package.sh
- uses: actions/upload-artifact@v4
with:
name: inventory-package
path: output/
deploy:
if: ${{ github.ref == 'refs/heads/main' }}
needs: package
environment: production
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: inventory-package
path: output/
- run: ./deploy.sh output/Outcome. Every main-branch deployment is traceable to a commit and artifact. Pull requests receive automated feedback without production access. Approval rules provide separation of duties, while job boundaries make failures easier to diagnose. Learning points. Automation is also governance. Correct sequencing, explicit permissions, protected environments, and artifact traceability are as important as the test command itself. 11. Limitations, Governance, and Best PracticesGitHub Actions introduces practical constraints. Hosted runners are temporary, network access can vary, concurrency can delay feedback, and third-party actions create supply-chain risk. YAML is expressive but can become difficult to maintain when workflows contain duplicated, implicit, or deeply nested logic. Recommended practices include: - Keep workflows small and purpose-oriented.
- Pin or centrally govern action versions.
- Use least-privilege
permissions. - Separate pull-request validation from privileged deployment.
- Protect production with environments and required reviewers.
- Use artifacts for explicit outputs and caches only for acceleration.
- Test workflows with linting, local simulation where appropriate, and controlled branches.
- Set timeouts and concurrency policies to prevent stale deployments.
- Record provenance: commit, workflow run, artifact, approver, and deployment target.
- Review logs for accidental disclosure and avoid printing environment values.
- Use self-hosted runners only with strong isolation, patching, and workload controls.
- Reuse stable workflow components, but version their interfaces deliberately.
Artificial intelligence can assist with generating YAML, explaining failures, or proposing test cases. However, generated workflows must be reviewed for excessive permissions, unsafe shell interpolation, leaked secrets, incorrect event assumptions, and unverified third-party actions. Human review remains essential because workflow code controls access to source code, credentials, and production systems. Chapter SummaryGitHub Actions is an event-driven CI/CD platform in which events start workflows, workflows contain jobs, jobs execute on runners, and jobs contain ordered steps. Actions package reusable automation, while shell steps execute project-specific commands. Contexts and expressions provide controlled access to event, matrix, job, and secret data. Matrices expand testing across supported combinations, artifacts transfer durable outputs, and caches accelerate repeatable dependency retrieval. Environments add deployment protection and approval, secrets protect sensitive values, and permissions enforce least privilege. Reusable workflows establish organizational standards. Professional implementations combine technical correctness with security, traceability, separation of duties, and careful treatment of untrusted input. Key TermsWorkflow: A YAML-defined automated process. Event: A condition or activity that triggers a workflow. Job: An isolated unit of work within a workflow. Step: An ordered operation within a job. Action: A reusable package of automation. Runner: The machine or environment executing a job. Context: Structured GitHub Actions data available to a workflow. Expression: An evaluated condition or value written using ${{ }} syntax. Matrix: A strategy that creates job variations from combinations of values. Artifact: A retained file or directory produced by a workflow. Cache: Stored data used to accelerate later executions. Environment: A deployment target with rules, variables, and secrets. Secret: An encrypted sensitive configuration value. Permission: An allowed capability granted to the workflow token. Reusable workflow: A workflow callable by another workflow. needs: A dependency declaration between jobs.
OpenID Connect: A mechanism for obtaining short-lived federated credentials. Least privilege: Granting only the access required for a task. Review QuestionsShort Answer Questions- What is the relationship between a workflow, job, step, and runner?
- Why should caches not be treated as authoritative build storage?
- What problem does a matrix strategy solve?
- How do environments improve production deployment governance?
- Why is least-privilege permission design important in GitHub Actions?
Long Answer Questions- Explain how events, filters, jobs, dependencies, and runners combine to execute a CI workflow.
- Compare artifacts, caches, and job outputs, including an appropriate use for each.
- Describe how contexts and expressions can control conditional execution, and identify risks when handling untrusted event data.
- Explain the design of a secure pull-request validation and production-deployment workflow.
- Discuss the advantages and limitations of reusable workflows in a large organization.
Analytical / Application Questions- Design a matrix strategy for testing a service on three operating systems and two supported language versions. State the number of generated combinations and discuss the cost implications.
- A deployment job currently has repository write permissions and receives production secrets on every pull request. Identify the risks and propose a safer event, permission, environment, and secret design.
- A build job creates a package needed by a later deployment job. Explain why relying on the runner’s filesystem is unreliable and design an artifact-based solution.
Multiple Choice Questions with AnswersQuestion 1Which GitHub Actions component defines when automation starts? A. A runner B. An event C. An artifact D. A matrix Correct Answer: B. An event Question 2What does the needs keyword primarily define? A. A job dependency B. A secret value C. A runner operating system D. An artifact retention period Correct Answer: A. A job dependency Question 3Which mechanism is most appropriate for transferring a package between separate jobs? A. A cache B. A shell alias C. An artifact D. A repository label Correct Answer: C. An artifact Question 4What is the principal purpose of a matrix strategy? A. To encrypt secrets B. To create job variations from value combinations C. To approve production deployments D. To change YAML into JSON Correct Answer: B. To create job variations from value combinations Question 5Which permission design follows least privilege? A. Grant write access to every job B. Use read-only permissions by default and add narrowly required access C. Disable all workflow logs D. Store credentials in source code Correct Answer: B. Use read-only permissions by default and add narrowly required access Question 6Which feature can require reviewer approval before production deployment? A. An environment B. A cache key C. A step identifier D. A runner label Correct Answer: A. An environment Question 7Where are reusable workflows designed to be invoked from? A. An on.workflow_call trigger B. A matrix.include list C. An artifact manifest D. A shell comment Correct Answer: A. An on.workflow_call trigger Question 8What is a major security risk of inserting untrusted pull-request text directly into a shell command? A. Matrix expansion B. Command injection C. Artifact compression D. Runner autoscaling Correct Answer: B. Command injection Question 9A matrix has 2 operating systems and 3 language versions, with no exclusions. How many job instances are generated? A. 2 B. 3 C. 5 D. 6 Correct Answer: D. 6 Question 10Which credential approach generally reduces the need for long-lived cloud keys? A. OpenID Connect with short-lived credentials B. Printing a token in the log C. Committing a password to YAML D. Sharing one permanent administrator key Correct Answer: A. OpenID Connect with short-lived credentials
|