How to Use GitHub Actions: The Beginner's Guide (First Workflow, Real Tests, Secrets, and a Safe Deploy)

CodexFuse Team · 2026-10-31T03:57:27.257Z · 14 min read

Learn how to use GitHub Actions: write your first workflow, run real tests in CI, keep secrets safe with least-privilege permissions, and deploy only from main.

What is GitHub Actions, and why is it free for most beginners?

GitHub Actions is a CI/CD and automation platform built directly into GitHub. You define workflows in YAML files inside your repository, and GitHub runs them on its own virtual machines — called runners — every time something happens in the repo, like a push or a pull request. If you are learning how to use GitHub Actions as a beginner, the key idea is simple: instead of running your tests and deploys by hand, you describe them once in a file, and GitHub executes them for you automatically.

The model works because automation lives next to the code it automates. The workflow file is versioned in the same repo, reviewed in the same pull requests, and anyone who clones the project can see exactly what "CI passes" means. Before GitHub Actions (launched in 2019, per aquilax.ai's overview), most teams wired up external services like Travis CI or Jenkins with webhooks and separate accounts; GitHub Actions removed that extra service entirely.

Cost is rarely a blocker at the start. Public repositories get unlimited GitHub Actions minutes on standard GitHub-hosted runners, and private repositories get 2,000 free minutes per month on the GitHub Free plan (GitHub Docs, "Product usage included with each plan," fetched this run). If you are new to GitHub itself, start with our GitHub beginner guide — Actions lives inside the repository, so branches, commits, and pull requests are the context everything below assumes.

How do GitHub Actions workflows actually work?

A GitHub Actions workflow is a YAML file in your repository's .github/workflows directory. GitHub discovers any file there with a .yml or .yaml extension and registers it as a workflow (GitHub Docs quickstart, crawled today). Four concepts nest inside each other: a workflow contains jobs, a job contains steps, and each job runs on one runner.

A job is a set of steps that run on the same runner; jobs in one workflow run in parallel unless you declare a dependency with needs:. A step is a single task — either a shell command (run:) or a prebuilt Action (uses:). Steps inside a job run sequentially and share the runner's filesystem. A runner is the virtual machine itself — GitHub offers Ubuntu, Windows, and macOS runners, or you can host your own.

An Action is a reusable unit of workflow logic published to the GitHub Marketplace or referenced from a repository. The most common one is actions/checkout, which clones your repository onto the runner so the steps can actually see your code. Actions are pinned to versions like @v6 — and that version choice matters more in 2026 than it used to, as we will see below.

How do I create my first GitHub Actions workflow?

You create a workflow by adding a YAML file to .github/workflows and committing it. The commit itself triggers the run, and you watch it in the repo's Actions tab. Here is the complete path from zero to a passing first run:

  1. Create the file in your repo. On GitHub, go to your repository's main page, click Add file → Create new file, and name it .github/workflows/hello.yml. That single step creates the .github and workflows directories for you (GitHub Docs quickstart, fetched today).
  2. Paste a minimal workflow. The snippet block below is a valid, working first workflow — it checks out your code and prints a message. YAML is whitespace-sensitive: two-space indentation, no tabs.
  3. Commit the file. Click Commit changes; committing triggers the push event, which starts the workflow immediately.
  4. Open the Actions tab. Under your repository name, click Actions, then your workflow name in the left sidebar, then the run itself. Click the job name to see every step's log expand in order.
  5. Break it on purpose. Change the echo to a failing command (for example exit 1), commit, and watch the run turn red. Learning to read a failed run — before you ever add tests or deploys — is what separates a working pipeline from a decorative one.

The hello-world file you just committed is the anatomy every bigger workflow follows. Validate the YAML before you commit if you are unsure about indentation — our free YAML validator tool catches the structure, indentation, and duplicate-key errors that make GitHub silently ignore a workflow file.

# .github/workflows/hello.yml — your first GitHub Actions workflow
name: Hello Actions

on: [push]

jobs:
  hello:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v6

      - name: Say hello
        run: echo "Hello from GitHub Actions!"

Three details in that file are worth memorizing. on: [push] is the trigger — the event that starts the workflow. runs-on: ubuntu-latest picks the runner — a Linux VM managed by GitHub. And uses: actions/checkout@v6 pins the Action version to v6, the current major in GitHub's own docs as of this run (their quickstart and Node.js CI tutorial both use checkout@v6 and setup-node@v7).

What goes in a real test job?

A real test job replaces the echo step with your project's actual commands: check out the code, install the runtime, install dependencies, and run the test suite. The shape is identical across languages — only the setup action changes. Here is a complete Node.js CI workflow that runs on every push and pull request to main:

# .github/workflows/ci.yml — a real test job for a Node.js project
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v6

      - name: Set up Node.js
        uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

Every line earns its place. branches: [main] keeps feature-branch experimentation from burning minutes — the workflow still runs on pull requests targeting main, which is where quality gates matter. node-version: 24 pins the runtime (Node 24 is the current active LTS line per techearl.com's setup-node guide, fetched this run). cache: npm restores npm's package-data cache between runs; the action's cache input does not cache node_modules itself — npm ci still does a clean install from your lockfile, which GitHub's docs describe as faster and more reliable than npm install for CI.

The version pins deserve a paragraph of their own. In September 2026 GitHub removed Node 20 from the runner's JavaScript-action runtime; JavaScript actions now execute on Node 24, and the old opt-out is gone (devops-daily.com, fetched this run). That is why checkout@v4 and setup-node@v4 are now the "version-drifted" tags you see in older tutorials — they declare the old runtime. Use the current majors from GitHub's own docs (checkout@v6, setup-node@v7 as of this run) and check the docs again when something looks stale. For Python, swap the setup step for actions/setup-python@v5; for a Docker-based build, see our Docker beginner guide, which pairs naturally with container-job workflows.

How do I read a failed run?

You read a failed run inside-out: run page → failed job → first red step → the actual error lines. GitHub annotates the failing step in red on the run page; clicking it expands the full log for that step. In almost every beginner case, the answer is in the last 20 lines of the first failed step — not in the final "Error: Process completed with exit code 1" summary, which just reports that something above it broke.

Three failure patterns cover most red runs. "Unable to process file ... error parsing YAML" means your indentation is off or a tab slipped in — paste the file into a validator and re-commit. "Cannot find module" or a failing npm ci means the lockfile and package.json disagree, or a dependency needs a system package the runner lacks. A test that passes locally but fails in CI is usually environment: a different Node version (pin node-version explicitly), a missing environment variable, or a test that secretly depends on your machine.

Two habits make debugging cheap. First, re-run only failed jobs: on the run page, click Re-run jobs → Re-run failed jobs instead of re-running everything, so green jobs stay green. Second, add concurrency to cancel superseded runs — when you push three commits in five minutes, only the latest needs to finish:

# cancel older runs of the same workflow on the same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

This three-line addition directly eliminates minutes spent on runs you have already made obsolete (codetalenthub.io's free-tier guide, fetched this run). It is the single highest-value optimization in this entire guide.

How do secrets work in GitHub Actions?

GitHub Actions secrets are encrypted variables you store in your repository settings and reference as ${{ secrets.NAME }} in workflows. You create them under Settings → Secrets and variables → Actions → New repository secret. GitHub masks secret values in logs automatically — but only if the exact value matches a stored secret, so never echo secrets into derived strings.

The non-negotiable rules: secrets live only in the settings UI, never in the YAML file; a fork's pull request does not get your repository's secrets by default (GitHub withholds them from forked PRs precisely to prevent exfiltration); and you reference them only through the secrets context. If a tutorial ever shows a real token pasted into a workflow, close the tab — that token is compromised the moment it is committed, even in a private repo.

Pair secrets with least-privilege permissions. Workflows get a GITHUB_TOKEN automatically, and since 2023 GitHub recommends restricting what it can touch. Declare only what your job needs:

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read   # checkout needs this; nothing else does
    steps:
      # ... your steps here

A workflow that only runs tests needs contents: read and nothing more. When a deploy job later needs to push a package, you widen that job's permissions explicitly — one job at a time, never the whole workflow at once.

How do I deploy safely with GitHub Actions?

A safe deploy job is a guarded one: it runs only on the main branch, only after the test job passes, and only through a GitHub environment if it touches production. The guard is two lines — an if: condition plus a needs: dependency — and it is the difference between continuous delivery and continuous accidents.

# .github/workflows/deploy.yml — a guarded deploy (example: Vercel)
name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm test

  deploy:
    needs: test                      # only runs if the test job passed
    if: github.ref == 'refs/heads/main'   # only on the main branch
    runs-on: ubuntu-latest
    permissions:
      contents: read
    environment: production          # optional: approval rules live here
    steps:
      - uses: actions/checkout@v6
      - name: Deploy to hosting
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: npm run deploy

needs: test makes the deploy wait for — and require — a green test job. The if: condition is belt and braces: combined with the branches: [main] trigger, a push to any other branch can never reach the deploy step. The environment: production line is where you attach required reviewers in the repo settings, so a human approves production deploys even when everything is green.

The deploy command itself depends on your host — most static and Node hosts have a CLI or an official deploy action you call with a token stored as a secret. For the hosting side of the story, see our Vercel beginner guide; the workflow above is the pipeline that feeds it.

GitHub Actions vs Jenkins: which should a beginner pick?

Pick GitHub Actions if your code already lives on GitHub. Jenkins is a self-hosted automation server with decades of plugin history and total control — and a server to maintain, upgrade, and secure. GitHub Actions gives you hosted runners, zero server maintenance, and a workflow file that travels with the repo, at the cost of GitHub's pricing model and platform.

GitHub ActionsJenkins
HostingGitHub-managed runners; no server to maintainYou host the controller and agents yourself
ConfigurationYAML in the repo (.github/workflows)Web UI plus Jenkinsfiles; plugin configuration
Best forGitHub-hosted projects, beginners, teams without infra staffComplex on-prem pipelines, air-gapped networks, exotic agents
Cost shapeFree for public repos; minutes quota for private reposFree software; you pay for the machines and the maintenance time
SecretsRepo/org/environment secrets in GitHub settingsCredentials store on your controller

The honest middle ground: many teams run both — Actions for pull-request CI because it is where the code is, Jenkins for the heavy internal pipelines that predate it. If you are starting today with a GitHub repo and no infrastructure team, there is no contest; Actions is the shorter path to a green checkmark.

Is GitHub Actions free? How many free minutes do you get?

GitHub Actions is free with an asterisk. Public repositories get unlimited minutes on standard GitHub-hosted runners — no cap at all. Private repositories get a monthly minutes allowance based on the plan of the account that owns the repo, and usage beyond the allowance is billed per minute. Self-hosted runners cost no GitHub minutes today (GitHub postponed its planned platform charge, per avrea.com's pricing review fetched this run) — you just pay for your own machines. Our VPS hosting guide covers the kind of machine people run self-hosted runners on.

PlanIncluded minutes / month (private repos)Artifact storage
GitHub Free2,000500 MB
GitHub Pro3,0001 GB
GitHub Team3,0002 GB
GitHub Enterprise Cloud50,00050 GB

Source: GitHub Docs, "Product usage included with each plan," fetched this run. The fine print that matters: every job is rounded up to a whole minute (a 61-second job bills 2 minutes), the allowance resets monthly with no rollover, and if you have no payment method on file, jobs simply stop when the allowance is exhausted instead of charging you — the spending limit defaults to $0 (codetalenthub.io, fetched this run). Windows and macOS runners consume the allowance faster than Linux; larger runners are never free, even on public repositories.

How do I stop wasting minutes and keep workflows safe?

Minutes die in three places: superseded runs, oversized matrices, and sloppy triggers. Cancel superseded runs with the concurrency block from the debugging section. Keep matrices small — a 3×4 OS/Node matrix is 12 jobs per push, and each one pays the one-minute rounding tax. And scope triggers with branches: and paths: so a docs typo does not spin up the full test suite.

On the safety side, three habits cover nearly everything. Pin your actions to reviewed major tags (@v6, not @main) and let Dependabot's github-actions ecosystem propose updates as pull requests — that way a compromised or broken action release arrives as a reviewable PR, not a silent surprise. Keep permissions least-privilege per job, as shown above. Never run untrusted code with write permissions: pull requests from forks run with read-only tokens and without your secrets, which is exactly the behavior you want — do not "fix" it by granting more.

Frequently asked questions

Do I need to pay to learn GitHub Actions?

No. A public repository gets unlimited minutes on standard runners, so a personal learning project costs nothing. Even private repos include 2,000 free minutes a month on the GitHub Free plan — far more than a beginner's tutorial-level usage.

Where do workflow files go?

In the .github/workflows directory of your repository, with a .yml or .yaml extension. GitHub only discovers workflows in that exact directory; a workflow file anywhere else is ignored.

Why did my workflow not run after I pushed?

Check three things in order: the file is in .github/workflows with a YAML extension, the on: trigger matches what you did (a pull_request-only workflow will not run on a direct push), and the YAML parses (indentation errors silently prevent registration).

Can I use GitHub Actions for deployment, not just tests?

Yes — deploy jobs use the same workflow syntax, guarded by needs:, branch conditions, and environments with required reviewers. The rule is simple: tests run on every pull request, deploys run only on main after tests pass.

Should I use actions/checkout@v4 or v6?

Use v6. GitHub removed Node 20 from the runner's JavaScript-action runtime in September 2026, and the current majors in GitHub's own docs are checkout@v6 and setup-node@v7. Older tutorials showing v2/v3/v4 tags are version-drifted — update them before copying.

Sources

  • GitHub Docs — "Quickstart for GitHub Actions" (docs.github.com, crawled 2026-10-30): workflow file location (.github/workflows, .yml/.yaml), actions/checkout@v6, run-name contexts, reading run logs.
  • GitHub Docs — "Building and testing Node.js" (docs.github.com, crawled 2026-10-30): actions/setup-node@v7, matrix strategy, npm ci recommendation, dependency caching, cache: npm semantics.
  • GitHub Docs — "Product usage included with each plan" (docs.github.com, crawled 2026-10-30): minutes and storage allowances per plan (2,000 / 3,000 / 3,000 / 50,000; 500 MB / 1 GB / 2 GB / 50 GB).
  • avrea.com — "GitHub Actions pricing in 2026 and how to lower your bill" (checked 2026-10-02, fetched 2026-10-30): hosted-runner price cut effective 2026-01-01, postponed self-hosted runner charge, per-minute rates.
  • devops-daily.com / dev.to — "GitHub Actions Removed Node 20" (fetched 2026-10-30): Node 20 removed from runner runtime 2026-09-23; first majors on Node 24 (checkout v5, setup-node v5); pinned-SHA caveat.
  • techearl.com — "actions/setup-node: Version Matrix, Caching, Pinning" (fetched 2026-10-30): setup-node@v7 as current major, Node 24 active LTS, cache behavior.
  • codetalenthub.io — "GitHub Actions 'Free' Tier: The Minute Math That Hurts" (fetched 2026-10-30): $0 default spending limit, no rollover, concurrency cancel-in-progress, per-minute rounding.
  • anygen.io — "GitHub Actions Setup: First Workflow Guide" (crawled 2026-10-30): current checkout@v5/setup-node@v5 usage notes, runner version requirements.
  • aquilax.ai — "GitHub Actions: What It Is and How to Use It" (March 2026, fetched 2026-10-30): 2019 launch, key concepts (workflow/job/step/runner/action), free-tier summary.
  • cycle.io — "Introduction to GitHub Actions" (crawled 2026-10-30): beginner workflow walkthrough, event triggers.
Featured on Twelve Tools Launch Llama Tools Launch your MCP