First GitHub Actions workflow
Write your first GitHub Actions workflow — CI/CD foundations. Learn the core concepts, follow a hands-on exercise, troubleshoot common issues, and connect to the next lesson.
Focus: write your first github actions workflow
You’ve pushed code a thousand times, but every merge still feels like a leap of faith. You run tests locally, they pass, and then… something breaks in production. The solution isn’t more discipline — it’s automation. In this lesson, you’ll learn to write your first GitHub Actions workflow, turning your repository into a machine that tests, builds, and validates your code on every push, so you never have to trust a green local check again.
The problem this lesson solves
Manual checks are slow, inconsistent, and forgettable. You might run tests before a push, but your teammate doesn’t. The linter isn’t installed on every machine. The build works on your laptop but fails on a clean checkout. GitHub Actions solves this by moving your checks into the cloud, triggered automatically by events like push or pull_request.
Before you dive in, you’ve likely faced one of these:
- Tests pass locally but fail in CI because of dependency version drift.
- You forget to run the linter, and code style issues slip into production.
- Your build process is a secret script only you know how to run.
GitHub Actions gives you a single, versioned, shareable definition of your pipeline — a workflow file that lives in your repository. Once you write your first GitHub Actions workflow, every contributor runs the same checks with zero setup. This is the foundation of CI/CD: catch problems early, automatically, and consistently.
Core concept / mental model
Think of a workflow as a recipe for your repository. The recipe sits in a file named workflow.yml inside the .github/workflows directory. GitHub reads that recipe and executes it on a runner — a virtual machine provided by GitHub (or self-hosted) that runs your code.
The recipe has three essential ingredients:
- Event: What triggers the workflow (e.g., a push, a pull request, a schedule).
- Job: A set of steps that run on the same runner. A workflow can have multiple jobs (e.g., test and deploy).
- Step: A single command or action. Steps run in order, and if one fails, the job stops.
Imagine a kitchen: the event is someone ringing the doorbell (a new commit), the job is the chef preparing a meal, and each step is chopping vegetables, boiling water, plating. The workflow defines the whole sequence.
Key terms you’ll encounter:
- YAML: The syntax of workflow files (indentation matters, spaces not tabs).
- Runner: The machine that executes your workflow.
- Action: A reusable unit of code (like
actions/checkoutto clone your repo). - Context: A variable like
github.repositoryorsecrets.MY_SECRETthat provides dynamic data.
How it works step by step
Now, let’s trace the life of a workflow — from creating the file to seeing the green checkmark.
- Create the file: In your repository, create a directory
.github/workflowsand a file, e.g.,ci.yml. - Define the name and trigger: Use
name:andon:to set the workflow name and the event(s) that start it. - Set up the job: Under
jobs:, define a job (e.g.,test) and specify the runner (e.g.,ubuntu-latest). - Add steps: Each step uses a
uses:key to call an action, or arun:key to execute a shell command. - Commit and push: GitHub automatically detects the workflow file and runs it on the next matching event.
- View results: Go to the Actions tab in your repository, click the workflow run, and inspect the logs.
The flow is: event → workflow → job → steps → result. If any step fails, the job fails, and the workflow appears red. You’ll get an email (if enabled) and can debug using the logs.
Hands-on walkthrough
Let’s create a simple workflow that runs your Python tests every time you push to the main branch. You’ll see the full anatomy in action.
Step 1: Create the workflow file
Open your terminal and navigate to your project root. Create the directory and file:
mkdir -p .github/workflows
nano .github/workflows/ci.yml
Step 2: Write the workflow
Paste the following YAML:
name: CI # The workflow name.
# Trigger on pushes to main and on pull requests into main.
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest # Use the latest Ubuntu runner.
steps:
- name: Checkout code
uses: actions/checkout@v4 # Pull your repo into the runner.
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12' # Use Python 3.12.
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: pytest
Step 3: Commit and push
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push origin main
Step 4: Watch it run
Go to your repository on GitHub, click the Actions tab. You’ll see a new run labeled <workflow name> / <job name> — e.g., CI / test. The logs show each step; a green checkmark means success.
Example output you might see in the logs:
Run pytest
============================= test session starts ==============================
platform linux -- Python 3.12.0, pytest-8.0.0, pluggy-1.4.0
collected 5 items
test_app.py ..... [100%]
============================== 1 passed in 0.12s ===============================
Pro tip: Use
actions/checkout@v4instead of cloning manually — it handles authentication and correct workspace paths for you.
Compare options / when to choose what
GitHub Actions is not your only CI/CD tool. Here’s a quick comparison to help you decide when to use it vs. alternatives.
| Feature | GitHub Actions | Jenkins | GitLab CI | CircleCI |
|---|---|---|---|---|
| Setup complexity | Low (file in repo) | High (server config) | Medium | Low |
| Hosting | Cloud runners (free for public) | Self-hosted | Cloud or self-hosted | Cloud (with free tier) |
| Language support | Any (via actions) | Any | Any | Any |
| Pricing | Free for public, paid for private | Free (but infrastructure cost) | Free tier | Free tier |
| Best for | GitHub-centric projects | Complex, on-premises needs | GitLab-centric teams | lightweight containerized builds |
When to choose GitHub Actions:
- Your code lives on GitHub (obvious, but the hook is deep).
- You want minimal setup and a large marketplace of pre-built actions.
- You need tight integration with GitHub features like PR checks and status badges.
When to consider alternatives:
- If you’re locked into GitLab or Bitbucket, use their native CI/CD.
- For complex, multi-environment pipelines, Jenkins in a container might be more flexible.
Troubleshooting & edge cases
Even simple workflows can hit snags. Here are real issues you’ll face and fixes.
YAML indentation errors
Error: “You have an error in your yaml syntax on line X” — usually a space/tab mixup or wrong indentation.
Fix: Use spaces (2 or 4) consistently. Validate locally with a linter: yamllint ci.yml or use the GitHub Action editor’s built-in validation.
Job fails at “Install dependencies”
Error: pip install -r requirements.txt fails because requirements.txt doesn’t exist.
Fix: Either create the file with your dependencies or use pip install . if you have a pyproject.toml. For a quick test, comment out the step.
Test step fails but passes locally
Cause: Version mismatch (Python or dependencies), or your tests rely on environment variables or database that aren’t available.
Fix: Check the log — the Run pytest step will show the traceback. Add a step to print versions:
- name: Show versions
run: |
python --version
pip list
Workflow doesn't trigger
If the Actions tab shows nothing, check:
- Is the file named
ci.ymland located in.github/workflows? (case matters) - Did you use
on:noton? (a common typo) - If you pushed to a branch listed in
branches:, it should trigger. For a PR, ensure you’ve opened one.
Pro tip: Trigger a manual run by adding
workflow_dispatchto theon:list — then you can click “Run workflow” from the UI.
What you learned & what's next
You now know how to write your first GitHub Actions workflow — from defining events and jobs to debugging common pitfalls. You can automatically run tests on every push, which is the heart of continuous integration.
In the next lesson, you’ll expand your workflow to build and package your application, and then explore artifacts — how to share build outputs between jobs and store them for later use. Your CI/CD foundation is getting stronger.
Keep that CI green, and you’ll ship with confidence.
Practice recap
Clone a small repository (or create a new one) and add a workflow that runs pytest on every push. Then intentionally introduce a failing test and watch the workflow turn red — this will cement the feedback loop. After that, try adding a second job that prints Hello, world using a run step, and observe how GitHub executes jobs in parallel.
Common mistakes
- Using
oninstead ofon:in the YAML — a classic typo that makes the file invalid. Always double-check your event keys. - Missing
actions/checkoutstep — your runner won’t have the source code, so anyrun:command that expects files will fail. - Assuming default Python version — the runner may have a different version than your local machine. Always explicitly set
python-versioninactions/setup-python. - Hardcoding secrets in the workflow file instead of using
secrets.X— commit history will expose them permanently. - Running the workflow on every push without filtering branches can cause unnecessary build load — use
on.push.branchesto limit triggers.
Variations
- Use
workflow_dispatchto manually trigger the workflow from the GitHub UI — handy for testing without a push. - Use a matrix strategy to test across multiple OS or Python versions in a single job — e.g.,
matrix.python-version: ['3.11', '3.12']. - Combine with strongly typed steps: use community actions like
actions/cacheto speed up dependency installation between runs.
Real-world use cases
- Automatically run unit tests on every pull request to prevent low-quality merges in a collaborative codebase.
- Deploy a static site to GitHub Pages when changes are pushed to main — trigger a workflow that builds and publishes.
- Run scheduled security scans (e.g., dependency audits) once a day using a cron schedule event in the workflow.
Key takeaways
- GitHub Actions workflows are defined in YAML files inside
.github/workflows/and are triggered by events likepushandpull_request. - A workflow consists of jobs (run on a runner) and steps (actions or shell commands) — they execute in order and stop on failure.
- Always use
actions/checkoutfirst to get your code, and pin action versions (e.g.,@v4) for reproducibility. - Troubleshoot by reading the step logs, checking YAML validity, and using
workflow_dispatchfor manual runs. - GitHub Actions integrates seamlessly with GitHub’s UI, making it the best choice for GitHub-hosted projects.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.