The biggest surprise about migrating from CircleCI to GitHub Actions isn’t the syntax change, it’s how deeply the platform’s integrated nature fundamentally alters your CI/CD workflow’s cost, complexity, and speed.

Let’s see what a typical build looks like. Imagine you have a Python project. In CircleCI, you might have a .circleci/config.yml like this:

version: 2.1
jobs:
  build:
    docker:
      - image: cimg/python:3.9
    steps:
      - checkout
      - run: pip install -r requirements.txt
      - run: pytest
  test:
    docker:
      - image: cimg/python:3.9
    steps:
      - checkout
      - run: pip install -r requirements.txt
      - run: flake8
workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test:
          requires:
            - build

Now, let’s translate that to GitHub Actions. A .github/workflows/main.yml would look something like this:

name: CI

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python 3.9
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Test with pytest
        run: pytest

  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python 3.9
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Lint with flake8
        run: flake8

Notice the uses: actions/checkout@v3 and uses: actions/setup-python@v4. These are "actions," pre-built pieces of reusable workflow code. This is a core concept in GitHub Actions: leveraging community-contributed or first-party actions to abstract away common tasks. Instead of writing docker run ... commands to pull images and execute scripts, you’re using declarative references to pre-built functionality.

The problem this solves is the "reinventing the wheel" syndrome in CI. How do you set up Node.js? How do you cache dependencies? How do you deploy to AWS? Instead of writing custom scripts for each, you find an action. This dramatically reduces the boilerplate in your workflow files.

Internally, GitHub Actions runs these workflows on their hosted runners (or your self-hosted ones). Each runs-on directive specifies the environment. ubuntu-latest is a managed virtual machine. The steps within a job are executed sequentially. A step can be a run command (shell script) or a uses action. Actions themselves can be hosted on GitHub, Docker Hub, or even run from a Git repository.

The levers you control are primarily within the on: trigger, the jobs: definition, and the steps: within each job.

  • Triggers (on:): This is where you define when your workflow runs. push and pull_request are common. You can specify branches, tags, scheduled events (schedule: with cron syntax), or even webhook events (repository_dispatch:). This is far more granular than CircleCI’s workflows section.
  • Jobs (jobs:): These are independent units of work that can run in parallel or be dependent on each other. The runs-on: key is crucial for defining the execution environment. You can specify different runner types (e.g., windows-latest, macos-latest) and even different machine types for performance-critical tasks.
  • Steps (steps:): This is the granular instruction set for a job. Each step is an opportunity to run a command or use an action. The with: key within a uses step allows you to pass inputs to that action, configuring its behavior. env: allows you to set environment variables for a step or an entire job.

The true power comes from combining these. You can use actions/cache to speed up builds by caching node_modules or Python virtual environments. You can use actions/upload-artifact and actions/download-artifact to pass files between jobs, effectively creating a DAG of tasks.

The one thing most people don’t fully grasp is how the concept of "actions" extends beyond simple setup tasks. Actions can be complex tools that manage entire deployment pipelines, interact with cloud providers, or perform sophisticated security scans. They form a composable ecosystem where you’re not just writing scripts, you’re orchestrating a series of specialized services. This means you can build incredibly powerful and sophisticated CI/CD pipelines by chaining together existing, well-tested actions, often with minimal custom code.

The next step in mastering GitHub Actions is understanding the nuances of matrix builds for testing across multiple environments and versions.

Want structured learning?

Take the full Circleci course →