The Guard at the College Gate
At the main gate of most colleges there is a security guard who checks every visitor’s ID card. Nobody has to call the principal each time a student walks in. The rule is set once, and the guard applies it to every person, every day, without getting tired or forgetting.
GitHub Actions is a guard for your repository. You write the rule once, for example “every HTML page must be valid and have a title”, and GitHub checks it automatically every time someone pushes code or opens a pull request. Asha, Ravi and Imran no longer need to test the Utsav fest site by hand: if something is wrong, GitHub shows a red cross before the mistake reaches the live site.
What Is CI?
CI stands for Continuous Integration. “Integration” means joining everyone’s work together; “continuous” means doing it often, in small pieces. CI is the habit of checking every change automatically as soon as it is pushed. Its partner, CD (Continuous Delivery or Deployment), publishes the checked work automatically. GitHub Actions can do both.
Even a small student team needs this, because people forget. If Ravi pushes a broken page at midnight, a CI check tells him within minutes which line is wrong.
The Parts of a Workflow
A workflow is a YAML file inside the folder .github/workflows/ of your repository. The folder name must be exactly this, starting with a dot. A file can have any name ending in .yml or .yaml.
college-fest-site/
├── .github/
│ └── workflows/
│ └── check.yml <- your workflow file
├── index.html
├── schedule.html
├── register.html
└── README.md
Inside a workflow, the words fit together like this:
Workflow (one YAML file in .github/workflows/)
│
├── on: the EVENT that starts it (push, pull_request, ...)
│
└── jobs:
└── a JOB (runs on a RUNNER, a fresh virtual computer)
├── step 1: uses: an action someone already wrote
├── step 2: run: your own terminal command
└── step 3: run: another command
| Word | Meaning | At the college gate |
|---|---|---|
| Event | What starts the workflow, such as a push or a pull request | A visitor arrives |
| Job | A group of steps that run together on one machine | The ID check |
| Runner | The virtual computer GitHub starts for the job, for example ubuntu-latest (Linux) | The guard on duty |
| Step | One task in the job: an action or a command | Look at the card, compare the photo |
| Action | A ready-made, reusable step, used with uses: | A card scanner bought from a company |
Each job gets a fresh, empty runner, so the first step almost always downloads your code.
YAML in One Minute
YAML is a simple format for settings. key: value sets a value, indentation with spaces shows what belongs inside what, and a line starting with - is an item in a list. Tabs are not allowed, and wrong indentation is the most common reason a workflow breaks.
Your First Workflow: check.yml
Asha creates .github/workflows/check.yml with this content:
name: Check HTML
on:
push:
branches: [main]
pull_request:
jobs:
html-check:
runs-on: ubuntu-latest
steps:
- name: Get the code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Validate every HTML page
run: npx --yes html-validate "*.html"
- name: Make sure every page has a title
run: |
for page in *.html; do
if ! grep -qi "<title>" "$page"; then
echo "$page has no <title> tag"
exit 1
fi
done
echo "All pages have a title."
Here is every line explained:
| Line | What it does |
|---|---|
name: Check HTML | The name shown in the Actions tab and next to commits. |
on: | Starts the list of events that run this workflow. |
push: with branches: [main] | Run when someone pushes to main. |
pull_request: | Run when a pull request is opened, or new commits are pushed to it. |
jobs: | Starts the list of jobs. |
html-check: | The job’s ID. You choose it: small letters and hyphens. |
runs-on: ubuntu-latest | Run on GitHub’s newest standard Linux runner. Windows and macOS runners also exist. |
steps: | Starts the list of steps, run from top to bottom. |
- name: Get the code | A new step with a readable name for the logs. |
uses: actions/checkout@v4 | The official checkout action copies your repository onto the runner. @v4 is its major version. |
uses: actions/setup-node@v4 | Installs Node.js, which gives us the npx command. |
with: and node-version: lts/* | Settings for that action: use the current long-term support version of Node.js. |
run: npx --yes html-validate "*.html" | Runs a command: npx downloads the free html-validate tool, which checks every .html file in the top folder. --yes skips the “install it?” question. |
run: | | The | means “several lines of commands follow”. |
The for loop | A small shell script: for each page, grep looks for <title>. If one is missing, it prints the page name and runs exit 1. |
The key rule: a step fails when its command ends with a non-zero exit code (like exit 1), and then the job stops and is marked as failed.
uses or run?
uses: runs an action written by someone else, named as owner/repository@version. Actions from the actions/ owner are made by GitHub. run: runs your own command in the runner’s terminal, as you would type it in Git Bash. Only use actions you trust, because they run with access to your code.
Add the workflow to the repository
# From the top folder of college-fest-site
mkdir -p .github/workflows
code .github/workflows/check.yml # or create the file in the VS Code Explorer
# After pasting the workflow and saving:
git add .github/workflows/check.yml
git commit -m "Add HTML check workflow"
git push
Windows users: these commands work in Git Bash. You can also create the folders and the file in the VS Code Explorer.
Reading the Actions Tab and the Logs
Open the repository on GitHub and click the Actions tab. Each push creates a new run of the workflow. Click a run, then the job html-check, then a step to open its log, the text the runner printed. A yellow dot means the run is still working.
A week later, Imran adds the registration page and opens a pull request. The check fails. In the log he sees something like:
Run npx --yes html-validate "*.html"
/home/runner/work/college-fest-site/college-fest-site/register.html
14:6 error <img> is missing required "alt" attribute wcag/h37
✖ 1 problem (1 error, 0 warnings)
More information:
https://html-validate.org/rules/wcag/h37.html
Error: Process completed with exit code 1.
The log names the file, the line and column (14:6) and the rule:
<!-- register.html, line 14: the check fails here -->
<img src="images/utsav-logo.png">
He adds the missing alt text, commits and pushes again to the same branch:
<!-- Fixed: every image needs alt text for screen readers -->
<img src="images/utsav-logo.png" alt="Utsav 2026 fest logo">
The workflow runs again by itself and passes.
Green tick, red cross
GitHub shows a green tick for success or a red cross for failure next to each commit and in the pull request, where the list of checks has a Details link to the log. Owners can also add a branch protection rule or ruleset (under Settings) so that a pull request cannot be merged until the checks pass: the guard does not open the gate without a valid ID.
Real-Life Example: Deploying the Fest Site with Actions
In the GitHub Pages Deploy lesson, the team published the site from a branch. Pages can also be published by a workflow, which is useful when you want checks or build steps first.
Part 1, on GitHub: open Settings, then Pages. Under Build and deployment, set Source to GitHub Actions.
Part 2, the workflow: she adds .github/workflows/deploy.yml:
name: Deploy site to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Get the code
uses: actions/checkout@v4
- name: Set up Pages
uses: actions/configure-pages@v5
- name: Upload the site files
uses: actions/upload-pages-artifact@v3
with:
path: .
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
| Part | What it does |
|---|---|
workflow_dispatch: | Adds a Run workflow button in the Actions tab, so you can deploy by hand. |
permissions: | What the workflow may do: read the code, write to Pages, and get a short-lived token that proves the deployment comes from this repository. |
concurrency: | Only one deployment runs at a time. A newer one waits instead of clashing. |
environment: | Records the deployment and shows the site link on the run page. |
actions/configure-pages | Reads your Pages settings and prepares the deployment. |
actions/upload-pages-artifact with path: . | Packs the files to publish (here, the top folder) into an artifact, a bundle of files stored with the run. |
actions/deploy-pages | Publishes that artifact. Its id: deployment lets environment read the page address. |
After the next push to main, the run shows the link to https://asha-utsav-demo.github.io/college-fest-site/. The versions after @ change over time, so check each action’s page for the newest major version.
Try these changes:
- Remove
branches: [main]fromcheck.ymlso the check runs on pushes to every branch. - Add one more step to
check.ymlwithrun: test -f index.html, which fails if the home page is ever deleted. - Add
workflow_dispatch:tocheck.ymland run it by hand from the Actions tab.
Secrets: Never Hard-Code Keys
Some workflows need a password or API key, for example to send a message to the team when the site is deployed. Never write a key in any file in your repository: others can copy it, and it stays in the Git history even after you delete it. Instead, open Settings, then Secrets and variables, then Actions, and add a repository secret. The workflow reads it like this:
- name: Send launch message
run: ./scripts/notify.sh
env:
NOTIFY_TOKEN: ${{ secrets.NOTIFY_TOKEN }}
GitHub hides secret values in the logs, and does not give secrets to workflows started by pull requests from forks.
Is It Free?
For public repositories, GitHub Actions on GitHub’s standard runners is free. For private repositories, each account gets a monthly amount of free minutes and storage, and the amount depends on your plan. Check the current numbers in GitHub’s billing documentation.
Common Mistakes
- Wrong folder name.
.github/workflow/(missing “s”) orgithub/workflows/(missing dot) is ignored silently. Fix: the path must be.github/workflows/. - Bad indentation. A step two spaces off breaks the file. Fix: use spaces only and line up items under their parent.
- Forgetting the checkout step. The runner starts empty, so later steps find no files. Fix: start with
uses: actions/checkout@v4. - A check that never fails. If your script prints an error but does not
exit 1, the step stays green. Fix: always end a failed check with a non-zero exit code. - Pasting keys into YAML. Fix: delete the key, create a new one with the service, and store it as a secret.
Practice Task: Let the Guard Catch a Mistake
- Add
check.ymlfrom this lesson to yourcollege-fest-siterepository and push it. Check that the run onmainis green. - On a new branch
gallery, add the page below. It has a deliberate mistake. - Push the branch and open a pull request. Watch the check fail and read the log.
- Fix the mistake, push again, and watch the pull request turn green.
<!-- gallery.html: add this page, then push it on a branch -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Utsav Gallery</title>
</head>
<body>
<h1>Photos from Utsav 2025</h1>
<img src="https://picsum.photos/400/250">
</body>
</html>
Show one possible answer (try it yourself first!)
# 1. Create the workflow file (paste check.yml from the lesson) and push it
mkdir -p .github/workflows
# (create .github/workflows/check.yml and save)
git add .github/workflows/check.yml
git commit -m "Add HTML check workflow"
git push
# 2. Open the Actions tab on GitHub: the run on main should be green
# 3. Add the gallery page on a new branch
git switch -c gallery
# (create gallery.html with the starter code)
git add gallery.html
git commit -m "Add gallery page"
git push -u origin gallery
# 4. Open a pull request. The "Check HTML" check fails with:
# <img> is missing required "alt" attribute
# 5. Fix the image line in gallery.html:
# <img src="https://picsum.photos/400/250" alt="Students dancing on the main stage">
git commit -am "Add alt text to gallery image"
git push
# The check runs again on the pull request and turns green.
Key Takeaways
- CI checks every change automatically. GitHub Actions runs it for you.
- A workflow lives in
.github/workflows/; it has events (on:), jobs, a runner (runs-on:) and steps. uses:runs a ready-made action;run:runs your own command. A non-zero exit code fails the step.- Read results in the Actions tab and as green ticks or red crosses on commits and pull requests.
- GitHub Pages can be deployed with
actions/upload-pages-artifactandactions/deploy-pagesafter setting the Pages source to GitHub Actions. - Keep keys in secrets, never in files.