1.2 GitHub Actions Architecture & Core Mental Model
Key Takeaways
GitHub Actions operates on a four-tier hierarchical execution graph: Workflows contain one or more Jobs, Jobs run on specific Runners and contain sequential Steps, and Steps execute shell commands or reusable Actions.
Workflows are event-driven, triggered by repository webhook events, POSIX schedules, or manual/API dispatches, which inject rich JSON event payloads into the
${{ github.event }}context.Jobs execute in parallel by default on isolated, ephemeral runner virtual machines or containers, sharing no filesystem state unless explicitly coordinated via
needsdependencies and artifact transfers.Steps within the same job execute sequentially on a single runner instance, sharing the filesystem, working directory (
$GITHUB_WORKSPACE), and environment variables written to$GITHUB_ENV.Runners communicate with GitHub's backend strictly over outbound HTTPS (TCP port 443) via long polling or WebSockets, requiring zero inbound firewall ports to be exposed.
GitHub Actions Architecture & Core Mental Model
To pass the GH-200 exam and architect resilient CI/CD pipelines, you must understand the underlying execution engine of GitHub Actions. GitHub Actions is not simply a task runner; it is a distributed, event-driven orchestration system built on a strictly enforced component hierarchy and an asynchronous message-queue architecture.
1. The Four-Tier Component Hierarchy
Every automated pipeline in GitHub Actions conforms to a four-tier hierarchy: Workflow, Job, Step, and Action/Command.
+-----------------------------------------------------------------------------+
| GITHUB ACTIONS COMPONENT HIERARCHY |
| |
| +---------------------------------------------------------------------+ |
| | WORKFLOW (.github/workflows/*.yml) | |
| | Top-level automated process triggered by events | |
| | | |
| | +--------------------------+ +----------------------------+ | |
| | | JOB 1: 'build' | | JOB 2: 'test' (needs: build)| | |
| | | Runs on Runner VM A | | Runs on Runner VM B | | |
| | | | | | | |
| | | [Step 1: Checkout] | | [Step 1: Download Artifact| | |
| | | | | | | | | |
| | | [Step 2: Setup Node] | | [Step 2: Run Tests] | | |
| | | | | | | | | |
| | | [Step 3: Compile App] | | [Step 3: Post Summary] | | |
| | +--------------------------+ +----------------------------+ | |
| +---------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------+
1. Workflow (The Orchestration Root)
- A workflow is an automated, configurable process defined by a YAML file located strictly in the repository's
.github/workflows/directory. - A repository can contain multiple workflows (e.g.,
ci.yml,release.yml,security-scan.yml). - Each workflow defines its own triggers (
on:), global environment variables, concurrency limits, defaults, permissions, and set of jobs.
2. Job (The Unit of Distributed Execution)
- A job is a set of steps that execute on a single designated Runner (virtual machine or container).
- Parallelism by Default: By default, multiple jobs defined inside the same workflow execute concurrently in parallel as soon as runner capacity becomes available.
- Sequential Execution via
needs: To enforce sequential execution or construct a Directed Acyclic Graph (DAG), you must declare dependencies explicitly using theneeds:keyword (e.g.,needs: [build, lint]). - Isolated Environments: Each job runs on a separate, fresh runner instance with its own isolated virtual machine or container environment. Filesystem modifications in Job A are completely inaccessible to Job B unless shared via artifact uploads or external storage.
3. Step (The Sequential Task Unit)
- A step is an individual task that runs sequentially inside the job's assigned runner environment.
- A step can be either:
- An Action (reusable unit invoked with
uses: actions/checkout@v4). - A Shell Command (executable script run with
run: npm test).
- An Action (reusable unit invoked with
- Shared Context: All steps within a single job share the same runner filesystem, the same working directory (
$GITHUB_WORKSPACE), and environment variables exported to$GITHUB_ENV. - Failure Propagation: If a step fails (returns a non-zero exit code), GitHub Actions immediately halts execution of subsequent steps in that job by default, unless overridden by
continue-on-error: trueor conditional status functions likeif: always().
4. Action (The Smallest Reusable Building Block)
- An action is a reusable, standalone application component packaged for execution inside a workflow step.
- Actions can be authored as Composite Actions (combining multiple run steps), JavaScript Actions (Node.js runtime), or Docker Container Actions (Linux container image).
2. Event-Driven Architecture & Webhook Payloads
GitHub Actions is fundamentally event-driven. Execution is initiated when specific activities occur within GitHub, on a schedule, or via an external API call.
+-----------------------------------------------------------------------------+
| EVENT INGESTION & DISPATCH |
| |
| [REPOSITORY ACTIVITY] [TIME SCHEDULE] [EXTERNAL DISPATCH] |
| - Git Push - POSIX Cron - REST API Webhook |
| - Pull Request Opened - Daily / Hourly - repository_dispatch |
| - Issue Comment |
| \ | / |
| \ | / |
| v v v |
| +-------------------------------------------------------------+ |
| | GITHUB EVENT INGESTION SERVICE | |
| | - Validates webhook payload & filters (branch/tag/path) | |
| | - Injects payload into `${{ github.event.* }}` context | |
| | - Queues workflow run execution graph | |
| +-------------------------------------------------------------+ |
+-----------------------------------------------------------------------------+
When an event occurs:
- GitHub's internal webhook system creates a structured JSON payload describing the actor, repository, commit SHA, branch reference, pull request metadata, or issue details.
- GitHub parses all workflow files in
.github/workflows/whoseon:trigger declarations match the event and satisfy any configured branch, tag, or path filters. - The complete event JSON payload is injected into the workflow's
${{ github.event }}context, allowing steps and conditional expressions (if:) to inspect payload attributes (e.g.,${{ github.event.pull_request.base.ref }}).
3. Runner Architecture & Outbound Network Topology
A Runner is the compute application that checks out your code, processes the steps defined in a job, reports status updates, and streams execution logs back to GitHub.
+-----------------------------------------------------------------------------+
| RUNNER OUTBOUND COMMUNICATION MODEL |
| |
| [ENTERPRISE DATA CENTER / CLOUD VPC] [GITHUB ENTERPRISE] |
| +---------------------------------------+ +-------------------+ |
| | Self-Hosted Runner Machine | | GitHub Service | |
| | | | Backend | |
| | +-------------------------------+ | | | |
| | | `actions-runner` Daemon | | | | |
| | +-------------------------------+ | | | |
| | | | | | |
| | | Outbound HTTPS | | | |
| | | (TCP Port 443) | | | |
| | | Long-Poll/WSS | | | |
| | +---------------------------->| Job Message Queue | |
| | | | & Log Ingestion | |
| | [NO INBOUND PORTS OPENED (Port 22/80 X| | | |
| +---------------------------------------+ +-------------------+ |
+-----------------------------------------------------------------------------+
The Outbound-Only Communication Principle
A critical architectural concept frequently tested on the GH-200 exam is the runner networking model:
- Runners communicate with GitHub Enterprise Cloud or GitHub Enterprise Server exclusively via outbound HTTPS connections on TCP port 443.
- The runner agent utilizes HTTP long-polling and secure WebSockets (
wss://) to poll GitHub's job dispatch queue. - No Inbound Ingress Required: Network administrators never need to open inbound firewall ports, configure public IP addresses, or create inbound port-forwarding rules to enable self-hosted runners. This architecture allows runners to operate securely inside restricted private enterprise subnets and air-gapped VPCs.
4. Runner Filesystem & Workspace Topography
When a job executes on a runner, GitHub Actions establishes a standardized directory structure. Understanding these environment variables and directory paths is essential for debugging and workflow authoring.
| Environment Variable | Directory Purpose & Structure | Lifecycle & Scope |
|---|---|---|
$GITHUB_WORKSPACE | The default working directory where actions/checkout clones your repository (e.g., /home/runner/work/repo-name/repo-name). | Preserved across all steps within the same job; wiped between jobs on hosted runners. |
$RUNNER_TEMP | A temporary scratch directory for transient files, scripts, and downloaded packages (e.g., /home/runner/work/_temp). | Available to all steps in the job; automatically deleted during job post-cleanup. |
$RUNNER_TOOL_CACHE | Directory containing pre-installed software toolchains (Node.js, Python, Java, Go, Ruby). | Read-only cache on hosted runners; managed via setup actions (e.g., actions/setup-node). |
$GITHUB_ENV | Path to a local file used to export environment variables to subsequent steps (echo "VAR=value" >> $GITHUB_ENV). | Processed by the runner agent after each step completes to update the step environment. |
$GITHUB_OUTPUT | Path to a local file used to export step outputs (echo "output_key=value" >> $GITHUB_OUTPUT). | Persisted in the step execution context for consumption via ${{ steps.<id>.outputs.* }}. |
$GITHUB_PATH | Path to a local file used to prepend directories to the system $PATH (echo "/custom/bin" >> $GITHUB_PATH). | Automatically updates the search path for all subsequent steps in the job. |
5. State Management: Job Isolation vs. Step Shared State
The fundamental boundary rule in GitHub Actions is the distinction between Step Shared Context and Job Isolation.
+-----------------------------------------------------------------------------+
| SHARED STEP CONTEXT VS. JOB ISOLATION |
| |
| +=====================================================================+ |
| | JOB A (Runner 1 VM) | |
| | Step 1: Creates file -> `$GITHUB_WORKSPACE/dist/app.js` | |
| | Step 2: Appends to `$GITHUB_ENV` -> `BUILD_NUM=42` | |
| | Step 3: Reads `$BUILD_NUM` and accesses `./dist/app.js` (SUCCESS) | |
| +=====================================================================+ |
| | |
| ISOLATION BOUNDARY (Separate VMs) |
| (Cannot read memory, env, or disk directly) |
| | |
| v |
| +=====================================================================+ |
| | JOB B (Runner 2 VM - `needs: [Job A]`) | |
| | Step 1: Looks for `./dist/app.js` (FAILS unless uploaded/downloaded) |
| | Step 2: Looks for `$BUILD_NUM` (FAILS unless passed via job outputs) |
| +=====================================================================+ |
+-----------------------------------------------------------------------------+
Comparison of Workflow Components
| Dimension | Workflow | Job | Step | Action |
|---|---|---|---|---|
| Scope | Repository / Global | Single Runner Instance | Single Command / Action | Reusable Subtask |
| Execution Mode | Triggered by events | Parallel by default; sequential with needs | Strictly Sequential | Synchronous within step |
| Filesystem State | N/A (Config file) | Isolated per VM / Container | Shared across steps in job | Shared within job workspace |
| Environment State | Declared in env: block | Declared in job env: | Updated via $GITHUB_ENV | Scoped to action inputs/env |
| Syntax Tag | Top-level YAML root | jobs.<job_id> | jobs.<job_id>.steps[] | uses: <owner>/<repo>@<ref> |
6. Execution Lifecycle Diagram
The following sequence illustrates how a repository event transitions from an initial Git action to distributed runner execution and final status reporting.
A network security administrator is configuring egress filtering rules on an enterprise firewall protecting a subnet of self-hosted GitHub Actions runners. Which communication requirement represents the correct architectural model for runner connectivity to GitHub Enterprise Cloud?
The runner requires inbound TCP port 22 and inbound TCP port 443 to receive webhook payloads pushed from GitHub's IP range.
The runner requires bidirectional UDP communication on ports 8000–9000 to stream live step logs to the web interface.
The runner requires an inbound SSH tunnel established from GitHub Enterprise Cloud into the local runner daemon.
The runner requires only outbound HTTPS connectivity on TCP port 443 using long polling or WebSockets to poll for jobs and upload logs.
A workflow defines two jobs: build-app and publish-app. The build-app job compiles a binary at $GITHUB_WORKSPACE/bin/release. The publish-app job defines needs: build-app and immediately attempts to run ./bin/release --upload. What is the result when this workflow executes on standard GitHub-hosted runners?
The publish-app job fails because jobs execute on separate, ephemeral virtual machines that do not share filesystem state unless artifacts are explicitly transferred.
The publish-app job succeeds automatically because declaring needs: build-app copies the entire filesystem from the prerequisite runner VM.
The publish-app job succeeds because all jobs within the same workflow YAML file execute sequentially within the same shared directory container.
The publish-app job pauses indefinitely until the runner admin manually synchronizes the runner disk caches.
Within a single workflow job, Step 1 creates a local configuration file at $GITHUB_WORKSPACE/config.json and Step 2 executes echo "STAGE=production" >> $GITHUB_ENV. What is the state of the runner environment when Step 3 executes inside the same job?
The file $GITHUB_WORKSPACE/config.json exists, but the environment variable STAGE is unset because variables do not persist across step boundaries.
The environment variable STAGE is set to production, but $GITHUB_WORKSPACE/config.json is deleted because each step resets the filesystem.
Both the file $GITHUB_WORKSPACE/config.json and the environment variable STAGE are fully accessible to Step 3.
Step 3 fails immediately because writing to $GITHUB_ENV violates step isolation policies unless explicitly approved by a repository administrator.
Sections you finish are checked off in the contents.