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.pushandpull_requestare 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’sworkflowssection. - Jobs (
jobs:): These are independent units of work that can run in parallel or be dependent on each other. Theruns-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 toruna command orusean action. Thewith:key within ausesstep 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.