Go to Portfolio
Blog I

GitHub Actions


GitHub Actions lets you automate tasks that normally happen after you push code.

Instead of manually running:

npm install
npm test
npm run build

you can make GitHub do it automatically whenever code is pushed or a pull request is opened.

This is one of the simplest ways to introduce CI/CD into a project.


What is CI?

Continuous Integration (CI) means automatically checking whether new code works before it is merged.

A typical flow looks like:

Push code

Install dependencies

Lint

Test

Build

Pass / Fail

The goal is simple:

Catch problems before they reach production.


Where GitHub Actions Lives

Workflows are stored inside:

.github/workflows/

For example:

my-project/
├── src/
├── package.json
└── .github/
    └── workflows/
        └── ci.yml

Each .yml file describes an automated workflow.


The Mental Model

GitHub Actions becomes much easier once you understand this structure:

Event

Workflow

Job

Runner

Steps

Event

Something happens in your repository.

For example:

push
pull request
manual trigger

Workflow

The automation that should run.

Job

A group of related tasks.

Examples:

test
build
deploy

Runner

The machine executing the job.

For example:

runs-on: ubuntu-latest

Steps

The individual commands inside the job.


Your First Workflow

Create:

.github/workflows/ci.yml

Then add:

name: CI

on:
  push:
    branches:
      - main

  pull_request:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup Node
        uses: actions/setup-node@v7
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

This is enough for a useful CI pipeline.


Understanding the Workflow

name

name: CI

This is simply the workflow name shown in GitHub.


on

on:
  push:
    branches:
      - main

  pull_request:
    branches:
      - main

This defines when the workflow should run.

In this case:

Push to main
       OR
Pull request into main

Run CI

jobs

jobs:
  test:

A workflow can contain one or more jobs.

Here we have one job called:

test

runs-on

runs-on: ubuntu-latest

This tells GitHub to run the job on an Ubuntu machine.

Think of it as GitHub temporarily creating a clean computer for your workflow.


uses vs run

You will see these constantly.

uses

- uses: actions/checkout@v6

This runs an existing reusable GitHub Action.

For example:

actions/checkout

copies your repository onto the runner.


run

- run: npm test

This executes a normal shell command.

So the difference is:

uses
→ run an existing action

run
→ execute a command

Why Checkout the Repository?

A runner starts as a fresh environment.

This:

- uses: actions/checkout@v6

makes your repository available inside it.

After that, commands like:

npm ci
npm test
npm run build

can work with your project files.


Why Use npm ci?

In CI, prefer:

npm ci

when your project has a lockfile.

It installs the exact dependency versions defined by your lockfile, which makes builds more predictable.

Your basic pipeline becomes:

Checkout

Setup Node

npm ci

npm test

npm run build

Multiple Jobs

As a project grows, you may separate responsibilities.

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - run: npm test

  build:
    runs-on: ubuntu-latest

    steps:
      - run: npm run build

By default, independent jobs can run separately.

If one job should wait for another, use:

needs:

Example:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - run: npm test

  deploy:
    needs: test
    runs-on: ubuntu-latest

    steps:
      - run: echo "Deploying..."

Now:

Test

Pass

Deploy

If the test fails, deployment does not continue.


Secrets

Never put credentials directly inside your workflow.

Bad:

env:
  API_KEY: "my-secret-key"

Instead, store the value in GitHub Secrets and access it like:

env:
  API_KEY: ${{ secrets.API_KEY }}

This is commonly used for:

deployment tokens
API keys
cloud credentials
database credentials

A Good Workflow for an Astro Project

For a simple Astro project, this is usually enough:

name: CI

on:
  push:
    branches:
      - main

  pull_request:
    branches:
      - main

jobs:
  check:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Setup Node
        uses: actions/setup-node@v7
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Check
        run: npm run check --if-present

      - name: Test
        run: npm test --if-present

      - name: Build
        run: npm run build

That gives you a practical CI pipeline without overengineering the project.


Common Mistakes

Forgetting checkout

Without:

uses: actions/checkout@v6

your repository files may not be available to later steps.


Hardcoding secrets

Never commit:

API_KEY: "secret"

Use GitHub Secrets instead.


Forgetting to build

Tests passing does not always mean the production build works.

For frontend projects, include:

- run: npm run build

in CI.


Making workflows too complicated

A small project does not need:

10 jobs
5 environments
multiple matrices
custom runners
complex deployment logic

Start with:

install
test
build

Add complexity only when the project actually needs it.


Final Mental Model

When reading any GitHub Actions file, ask:

What triggers it?

What jobs run?

What machine runs them?

What steps execute?

What happens if something fails?

And remember:

Event

Workflow

Job

Runner

Steps

GitHub Actions is not really about YAML.

It is about turning your development process into something repeatable and automatic.

For most projects, a good starting point is simply:

Push code

Install dependencies

Test

Build

That alone gives you most of the practical value of CI without unnecessary complexity.