Dev Encyclopedia
ArticlesTools

Get notified when new content drops

No spam. Just new articles, tools, and updates straight to your inbox.

Dev Encyclopedia

A reference for builders

Content

  • Articles
  • Tools
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

ยฉ 2026 Dev Encyclopedia

Privacy PolicyTermsDisclaimer
  1. Home
  2. /Blog
  3. /GitHub Actions Tutorial: CI/CD from Push to Deploy (2026)
devops13 min read

GitHub Actions Tutorial: CI/CD from Push to Deploy (2026)

Learn GitHub Actions: write your first workflow, run tests automatically, use secrets safely, deploy via SSH, cache dependencies, and run matrix builds.

By Dev EncyclopediaPublished June 12, 2026
On this page

On this page

  • What GitHub Actions Actually Does
  • Your First Workflow: Hello World
  • Running Tests on Every Push
  • Using Secrets Safely
  • Deploying to a Server via SSH
  • Caching Dependencies
  • Matrix Builds Across Node Versions
  • Common Errors and How to Fix Them
  • Frequently Asked Questions

GitHub Actions is how modern teams automate everything from running tests on every pull request to deploying code to production without touching a server. If you've never set one up before, this guide walks through the whole thing: how workflows work, your first working file, and a real deploy pipeline you can copy into your own repo.

By the end you'll have a workflow that runs your test suite on every push to main, deploys automatically when tests pass, and caches dependencies so CI runs finish in seconds instead of minutes.

  1. 1

    What GitHub Actions Actually Does

    When you push code, open a pull request, or trigger another event in your repo, GitHub Actions can automatically run a script in response. That script lives in a YAML file inside your repository at .github/workflows/. GitHub runs it on its own servers: no CI service to pay for, no external tool to configure.

    A workflow file is built from four pieces. Once you understand these, you can read and write any GitHub Actions workflow:

    • Workflow - the whole YAML file. One automation, one file.
    • Event (trigger) - what starts it: a push, a pull request, a release, or a schedule.
    • Job - a group of steps that run together on the same virtual machine.
    • Step - a single command or a reusable action, run in order inside a job.

    That's the entire mental model. Everything below is just different combinations of those four pieces.

  2. 2

    Your First Workflow: Hello World

    Create a file at .github/workflows/hello.yml in your repository:

    yaml โ€” .github/workflows/hello.yml
    name: Hello World
    
    on:
      push:
        branches: [main]
    
    jobs:
      say-hello:
        runs-on: ubuntu-latest
        steps:
          - name: Print hello
            run: echo "Hello, World!"

    Push this file, then open the Actions tab on GitHub. You'll see the workflow run and print "Hello, World!" to the log. That's a working GitHub Actions workflow. Here's what each line does:

    • `name` - a label shown in the Actions tab. Purely cosmetic.
    • `on.push.branches: [main]` - run this workflow on every push to main. This is the "run on push to main" trigger.
    • `runs-on: ubuntu-latest` - the virtual machine type. Ubuntu is the default and fastest option for most projects.
    • `steps` - a list of commands to run, in order. Here there's just one.

    ๐Ÿ’ก Tip

    Want it to run on every branch, not just main? Remove the branches key entirely, or set it to branches: ['**'].

  3. 3

    Running Tests on Every Push

    This is the most common use case: every time someone pushes code or opens a pull request, run the test suite automatically and block the merge if anything fails.

    yaml โ€” .github/workflows/tests.yml
    name: Tests
    
    on:
      push:
        branches: [main]
      pull_request:
    
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: '20'
          - run: npm ci
          - run: npm test

    uses: references a reusable action from the GitHub Marketplace instead of a raw shell command. actions/checkout@v4 clones your repository onto the runner (without it, none of your code is even there). actions/setup-node@v4 installs the Node.js version you specify. Then npm ci installs dependencies from your lockfile (faster and more reproducible than npm install in CI), and npm test runs your test suite.

    If any step fails, GitHub marks the whole run as failed and puts a red X on the commit or pull request. Combine this with a branch protection rule and your team can't merge a pull request until tests pass.

    โ„น Info

    If you already run lint and tests locally with Husky and lint-staged before every commit, this workflow is the safety net for the times someone bypasses that hook with --no-verify. CI is the check that can't be skipped.

  4. 4

    Using Secrets Safely

    Never put API keys, database URLs, or deploy keys directly in a YAML file: anyone with read access to the repository can see them, and they stay in git history forever. GitHub Actions has a secrets store built in for exactly this.

    1. Open your repo settings - go to Settings โ†’ Secrets and variables โ†’ Actions.
    2. Add a new repository secret - give it a name like DEPLOY_KEY or SSH_PRIVATE_KEY and paste the value.
    3. Reference it in your workflow - secrets are exposed through the secrets context, never as plain text.
    yaml
    env:
      DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

    Or scope it to a single step instead of the whole job:

    yaml
    - name: Deploy
      run: ./deploy.sh
      env:
        DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

    โš  Warning

    Secret names are case-sensitive. secrets.deploy_key and secrets.DEPLOY_KEY are different secrets, and a mismatch silently resolves to an empty string instead of throwing an error.

    โ„น Info

    GitHub automatically masks secret values in logs, replacing them with *** even if a step accidentally prints them. Secrets are scoped to the repository (or organization, if set at the org level) and are not passed to workflows triggered by pull requests from forks.

  5. 5

    Deploying to a Server via SSH

    This is the most-searched workflow pattern in this guide: push to main, run the tests, and if they pass, SSH into a server and deploy.

    yaml โ€” .github/workflows/deploy.yml
    name: Deploy
    
    on:
      push:
        branches: [main]
    
    jobs:
      test-and-deploy:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: '20'
          - run: npm ci
          - run: npm test
          - name: Deploy via SSH
            uses: appleboy/ssh-action@v1
            with:
              host: ${{ secrets.SERVER_HOST }}
              username: ${{ secrets.SERVER_USER }}
              key: ${{ secrets.SSH_PRIVATE_KEY }}
              script: |
                cd /var/www/myapp
                git pull
                npm ci --production
                pm2 restart myapp

    Add SERVER_HOST, SERVER_USER, and SSH_PRIVATE_KEY as repository secrets using the steps from the previous section. appleboy/ssh-action connects to your server over SSH, runs the script you give it, and reports success or failure back to the workflow.

    Steps run in order within a job, and each step only runs if the previous ones succeeded. That means Deploy via SSH only runs after npm test passes: a broken build never reaches production.

    ๐Ÿ’ก Tip

    Deploying to Vercel, Railway, or Fly.io instead of a VPS? Each platform publishes its own GitHub Action (for example amondnet/vercel-action) that does the same job without managing SSH keys at all.

  6. 6

    Caching Dependencies

    Without caching, every workflow run reinstalls node_modules from scratch. With caching, a typical install drops from around 45 seconds to under 5.

    yaml
    - uses: actions/cache@v4
      with:
        path: ~/.npm
        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-node-

    Add this step before npm ci. The key is built from a hash of package-lock.json: if your dependencies haven't changed, the cache hits and npm skips the download entirely. If package-lock.json changes, the key changes too, the cache misses, and npm reinstalls clean.

    The same idea applies outside the npm ecosystem. If you're on a Python project managed with uv instead of pip, see the GitHub Actions CI setup for uv projects for the equivalent caching pattern.

  7. 7

    Matrix Builds Across Node Versions

    A matrix build runs the same job multiple times with different inputs, in parallel. The most common use is testing against several language versions at once:

    yaml
    jobs:
      test:
        runs-on: ubuntu-latest
        strategy:
          matrix:
            node-version: [18, 20, 22]
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: ${{ matrix.node-version }}
          - run: npm ci
          - run: npm test

    GitHub runs three separate jobs in parallel, one per Node.js version, each substituting matrix.node-version into setup-node. If your code breaks on Node.js 18 but passes on 20 and 22, the matrix tells you exactly which version failed instead of leaving you to guess.

    ๐Ÿ’ก Tip

    Matrices aren't limited to language versions. Add os: [ubuntu-latest, windows-latest, macos-latest] to the matrix to test across operating systems too: every combination of the listed values runs as its own job.

  8. 8

    Common Errors and How to Fix Them

    Five mistakes account for almost every broken workflow. Here's what each one looks like and how to fix it.

    โš  YAML indentation errors

    GitHub's YAML parser is strict: no tabs, and indentation must be consistent. A workflow that fails immediately, before any step even starts, is almost always an indentation problem. Paste the file into a YAML validator to find the exact line.

    โš  Missing actions/checkout@v4

    Without this as the first step, your repository's code was never copied onto the runner. Every later step fails with "file not found" errors for files that clearly exist in your repo.

    โš  A secret resolves to an empty value

    The secret name in your YAML doesn't exactly match the name in repo settings. Secret names are case-sensitive, and typos resolve silently to an empty string instead of an error.

    โš  No on: trigger, or the wrong branch

    A workflow with no on: key, or one scoped to a branch that doesn't exist, simply never runs, and GitHub gives no warning. Double-check on.push.branches matches your default branch name (main vs master).

    โš  Job order assumptions

    Jobs in the same workflow run in parallel by default, not in sequence. If a deploy job needs the test job to finish first, add needs: test to the deploy job, otherwise they race and deploy can finish (or even succeed) before tests do.

    If you want to debug a workflow without pushing a dozen commits, tools like act run GitHub Actions workflows locally inside Docker, which catches most of these issues before they reach GitHub at all.

Quick reference: the YAML patterns covered in this guide.
PatternYAML
Trigger on push to mainon: { push: { branches: [main] } }
Trigger on pull requeston: pull_request
Set up Node.js 20uses: actions/setup-node@v4, with: { node-version: '20' }
Run testsrun: npm test
Cache node_modulesuses: actions/cache@v4
Use a secret${{ secrets.SECRET_NAME }}
Deploy via SSHuses: appleboy/ssh-action@v1

Frequently Asked Questions

What is GitHub Actions used for?

GitHub Actions is GitHub's built-in automation platform. It runs scripts in response to events in your repository: pushes, pull requests, releases, issue comments, or a schedule.

  • CI (continuous integration) - run tests and linters on every push or pull request.
  • CD (continuous deployment) - deploy to a server, container registry, or cloud platform when code merges.
  • Automation beyond code - label issues, close stale pull requests, post release notes, sync data on a schedule.
Is GitHub Actions free?

Yes, with limits. Public repositories get unlimited free minutes on standard runners. Private repositories get a monthly free quota and pay per minute after that, with Linux runners costing the least and Windows or macOS runners costing more per minute.

GitHub Actions vs Jenkins or CircleCI: which should I use?
GitHub ActionsJenkins / CircleCI
SetupBuilt into GitHub, no separate serverSeparate service or self-hosted server to configure
Config location.github/workflows/*.ymlJenkinsfile or .circleci/config.yml
Reusable stepsLarge marketplace of community actionsPlugins (Jenkins) or orbs (CircleCI)
Best forProjects already hosted on GitHubMulti-host repos or existing pipeline investment

If your code is on GitHub, GitHub Actions removes an entire piece of infrastructure since there's no separate CI server to maintain. Reach for Jenkins or CircleCI when you need a CI system that spans multiple source hosts or you already have pipelines built there.

How do I make a workflow run only on push to main?

Use the on.push.branches key with your branch name:

yaml
on:
  push:
    branches: [main]

โ„น Info

To run on multiple branches, list them: branches: [main, develop]. To exclude branches instead of listing them, use branches-ignore.

Why is my GitHub Actions secret showing as empty?

Almost always one of three causes:

  • Name mismatch - secret names are case-sensitive; secrets.API_KEY won't match a secret named Api_Key.
  • Wrong scope - the secret was added to a different environment or repository than the one the workflow runs in.
  • Fork pull requests - secrets aren't passed to workflows triggered by pull requests from forks, by design, to stop secrets leaking to untrusted code.
Can I test a GitHub Actions workflow locally before pushing?

Yes. The open-source act tool reads your .github/workflows/ files and runs them locally inside Docker containers, simulating GitHub's runners. It isn't a perfect match (some actions and contexts behave slightly differently), but it catches most YAML and logic errors without using up CI minutes or filling your history with debug commits.

Can GitHub Actions run AI code review or auto-fix failing pull requests?

Yes. A workflow can call any CLI tool as a step, including AI coding agents. If you're using Claude Code, the Claude Code cheatsheet covers the /autofix-pr workflow: when CI fails, Claude reads the failure logs, patches the issue, and pushes a follow-up commit to the same branch automatically.

Start small: a single workflow that runs your tests on every push to main already catches the majority of regressions before they reach production. Add secrets, caching, and a deploy step once that foundation is solid.

Every pattern in this guide, from the Hello World workflow to the SSH deploy job, is copy-paste ready. Save the quick reference table above and adapt the YAML to your project's package manager and deploy target.

Once your workflows are running, the next step is locking them down. The companion guide, GitHub Actions Security: 7 Misconfigurations to Avoid, covers the permission, secret, and caching mistakes that turn a working pipeline into an attack vector.

Related Articles

nextjs

Husky + Prettier + lint-staged Setup for Next.js

Set up Husky v9, Prettier, and lint-staged in your Next.js project. Step-by-step guide covering pre-commit hooks with the correct 2026 config.

May 30, 2026ยท8 min read
python

How to Switch to uv: Replace pip, virtualenv, and Poetry in Your Python Project

uv replaces pip, virtualenv, and Poetry with a single fast binary. Step-by-step guide to migrating your existing Python project and setting up GitHub Actions CI.

Jun 4, 2026ยท10 min read
ai tools

Claude Code Cheatsheet: Commands, Hooks & Subagents

The complete Claude Code reference: every slash command, keyboard shortcut, hook, subagent, and CLAUDE.md tip, with real examples for developers.

Jun 6, 2026ยท10 min read

On this page

  • What GitHub Actions Actually Does
  • Your First Workflow: Hello World
  • Running Tests on Every Push
  • Using Secrets Safely
  • Deploying to a Server via SSH
  • Caching Dependencies
  • Matrix Builds Across Node Versions
  • Common Errors and How to Fix Them
  • Frequently Asked Questions