DEV Community

Julie
Julie

Posted on Originally published at juliecodestack.github.io

The minimum you need to know about GitHub Actions

Real-world Hugo site automation example included

What is GitHub Actions?

Have you heard of GitHub Actions but assumed it was too complicated to learn? I had the same thought — until I used it on a real project (Build and deploy Hugo site with GitHub Actions, Schedule future-dated posts with Github Actions).

Think of GitHub Actions as a robot living on GitHub: you call it, and it does exactly what you've told it to do. More precisely, it's GitHub's built-in automation. You write a workflow, and GitHub runs it for you whenever one of the events you specify happens.

With GitHub Actions, I no longer build my Hugo site by hand: I push a new post, and the robot builds and deploys it.

In this post I'll show you the two sections every workflow file needs, then walk you through a six-line workflow that you can paste into your own repository today.

A real example: my Hugo site

Let me make this concrete with my own case of static site generation. With Hugo, the classic approach was to build the site locally and manually, then push the generated public/ folder to GitHub for deployment. With GitHub Actions, GitHub's servers take over the build. Now I just need to push a new post, and GitHub will build and deploy the site for me.

Even better, GitHub Actions can run on a schedule, so it runs at the time and frequency you choose.

The magic is in the workflow file

So how does it actually work?

The key is to define the workflow file: a .yml or .yaml file under .github/workflows/. It's the set of instructions we hand the "robot" in advance, so it knows what to do when it's triggered.

Note: a workflows/ folder can hold several workflow files.

Two sections are required: on and jobs.

The on section: when it runs

The on section specifies which events trigger the workflow. For example, a push to a branch, or a pull_request.

Let's use the Hugo site as our example. Its workflow lists three triggers: push, schedule, and workflow_dispatch.

on:
  push:
    branches:
      - main
  schedule:
    - cron: '26 1 * * *'
  workflow_dispatch:
Enter fullscreen mode Exit fullscreen mode
  • The push trigger means the workflow runs whenever someone pushes to the main branch.

  • The schedule trigger runs the workflow on a cron schedule — here, at 01:26 UTC every day. GitHub's cron uses UTC, so convert your local time. I walk through every cron field in this post).

  • The workflow_dispatch trigger lets you run the workflow manually. Normally a workflow only starts in response to an event, so testing it means making a throwaway commit. workflow_dispatch exists precisely so you don't have to.

Adding workflow_dispatch puts a Run workflow button at the top of the Actions tab. Here's how to use it:

(1) In your repository, click the Actions tab;

(2) In the left sidebar, click the workflow name;

(3) On the right side, click the Run workflow button. The button only exists when the file contains workflow_dispatch. You can pick the branch here.

(4) The new run appears at the top of the list.

Note: pay attention to the indentation before each trigger. I once made the mistake of putting schedule on the same indentation level as the branches under push, that cost me a lot of time debugging.

The jobs section: what it does

The jobs section specifies what the workflow does when it runs. In the Hugo example, the jobs build and deploy the site.

Each job needs two keys:

  1. runs-on: the machine the job runs on (ubuntu-latest, windows-latest, macos-latest, …);
  2. steps: the list of individual tasks to run, in order.

The code below shows the build job. The deploy job that follows it is omitted. You can read the full workflow here .

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      HUGO_VERSION: 0.166.0
    steps:
      - name: Install Hugo CLI
        run: wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb && sudo dpkg -i ${{ runner.temp }}/hugo.deb

      - name: Install Dart Sass
        run: sudo snap install dart-sass

      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive
          fetch-depth: 0

      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v4

      - name: Install Node.js dependencies
        run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"

      - name: Build with Hugo
        env:
          HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
          HUGO_ENV: production
        run: hugo --gc --minify

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./public
Enter fullscreen mode Exit fullscreen mode

Read the job top to bottom and you can follow the flow: install Hugo and Dart Sass; check out the repository onto the runner; build the site with Hugo; and pack the public/ folder as a Pages artifact.

Try it yourself

In fact, you've been using this kind of thing for years — an auto-reply rule in your email app runs the moment a message arrives. GitHub Actions is the same idea applied to your code.

GitHub is full of examples, too. Open your first pull request on a project, for instance, and you may receive a welcome message — that's GitHub Actions at work.

How do you write your first GitHub Action?

You don't need a site on GitHub Pages yet. Start small: set up a push-triggered workflow in any repository. Here's a complete, minimal one:

on: push
jobs:
  hello:
    runs-on: ubuntu-latest
    steps:
      - run: echo "hello"
Enter fullscreen mode Exit fullscreen mode

Save it as .github/workflows/hello.yml in your repository. Push, and it runs.

Keep in mind that GitHub Actions runs on a GitHub-hosted runner, so echo "hello" won't display on your local terminal. Where does it go?

The output is captured in the run's log. To view it, open your repository on GitHub and click the Actions tab. Then click the run and then the job (hello in this example).

 raw `hello` endraw  job

Expand the step, and you will see hello in the log.

run's log

That's it. Now you have a workflow that runs on every push. And that's the whole trick: you write a workflow file, GitHub does the work.

If you want to go further:

  • Add a schedule: reuse the schedule trigger from the on section so your site rebuilds itself every night, and please remember the cron runs in UTC.
  • Add a manual trigger: add workflow_dispatch and you get a Run workflow button, so you can test a workflow change without pushing a throwaway commit.
  • Read a real one: here's the full workflow file for my Hugo site. Now you should be able to follow it.

Have you set up GitHub Actions in your own repository yet? I'd love to hear what you're automating with it.

Top comments (13)

Collapse
 
francistrdev profile image
FrancisTRᴅᴇᴠ •

I was about to write an article on this but you beat me to it haha.

Love the article. Well written and straight to the point! I am trying to see if I can use GitHub Actions creatively as my CI/CD workflow. It's going well so far and a very convenient tool that everyone should use.

Well done Julie!

Collapse
 
julies1 profile image
Julie •

Thank you!😄 I'm glad you like it. Having tried to learn it twice, I might have a better sense of where beginners feel lost. 😄

I'd love to hear how you use GitHub Actions in your workflow.

Collapse
 
marcusykim profile image
Marcus Kim •

I'd make missing lockfiles optional without making a failed npm ci optional. The final || true makes both cases look successful to the runner, so a broken dependency install can reach the Hugo build. An explicit if around lockfile presence keeps the beginner-friendly skip, while allowing a real install error to stop the job. I'd test it once with a deliberately invalid lockfile to check that distinction in the Actions log.

Picked as gem
Collapse
 
julies1 profile image
Julie • • Edited

Thank you for pointing this out! 😄
To be honest, the AI agent wrote the .yml workflow file for me when I was setting up my Hugo site. To ensure the run stops when npm ci fails, the AI agent suggested the following change:

- name: Install Node.js dependencies
  run: |
    if [[ -f package-lock.json || -f npm-shrinkwrap.json ]]; then
      npm ci
    else
      echo "No lockfile found; skipping npm ci."
    fi
Enter fullscreen mode Exit fullscreen mode

But it seems that the starter template on GitHub provides the same code as in the article, so I'm wondering whether the change is necessary.

Collapse
 
marcusykim profile image
Marcus Kim •

Yes, I'd keep your explicit if. You're right that GitHub's Hugo starter currently uses the same || true form, so this isn't something your AI invented.

The difference is failure policy: that one-liner turns both "no lockfile" and "npm ci failed" into success. Your version skips only the first case. With Actions' standard Ubuntu shell behavior, a failed npm ci in that then branch stops the step; adding shell: bash makes the shell choice explicit.

The right policy depends on the site: if the theme needs Node dependencies, hiding an install error can produce an incomplete build. If the site needs no Node dependencies, skipping is fine. If they are required, even a missing lockfile should fail rather than skip. I'd check three cases: no lockfile, a valid install, and a deliberately broken install. Only the last should fail under your optional-install policy.

Thread Thread
 
julies1 profile image
Julie •

Thank you for the clear explanation!

Collapse
 
jabinpou profile image
Mahdi JabinPour •

The six-line hello workflow is a good starting point because you can see the trigger and the job without deployment details getting in the way. For the Hugo example, what would you add next to handle a scheduled build overlapping with a push-triggered deployment?

Collapse
 
julies1 profile image
Julie •

Thank you! As for adding scheduling, I wrote a post about it. Feel free to take a look.😄

The schedule and push events trigger the workflow independently: one at the scheduled time, the other on every push.

Collapse
 
jabinpou profile image
Mahdi JabinPour •

Thanks, Julie. That clears up the distinction: the schedule doesn't replace the push trigger. Showing both runs side by side would make a useful follow-up exercise for beginners. Appreciate the explanation and the link!

Thread Thread
 
julies1 profile image
Julie •

You're welcome! 😄 Yes, the schedule, push, and workflow_dispatch triggers don't replace one other. Each servers a different purpose.

Collapse
 
codingwithjiro profile image
Elmar Chavez •

Clear explanations! I am fairly recently using GitHub actions in my portfolio projects as well and I'm kinda liking it because of it's straightforward approach.

Collapse
 
julies1 profile image
Julie •

Thank you! 😄Yes, once you start using GitHub Actions, you'll find it convenient because it can automate many tasks.

What do you use GitHub Actions for in your portfolio projects?😄

Collapse
 
codingwithjiro profile image
Elmar Chavez •

Mainly for linting, building prod, and running tests for CI.