Back to blog

GitHub Actions

Understanding GitHub Actions: How I Use It to Check and Publish This Website

See how I use GitHub Actions to check every change, build this Astro website, publish the finished output and trigger IndexNow after release.

GitHub Actions became useful to me when I stopped thinking about it as another page of YAML syntax and started treating it as a repeatable route through a job. On this website, that job is simple: check every change in a clean environment, build the finished site from the main branch and prepare that output for hosting.

01

The problem is not usually the build command

Most projects already have a command that checks or builds the application. The unreliable part is remembering to run it at the right time and in the right environment.

A change can work on my machine because the dependencies are already installed, an old file is still hanging around or my local setup is more forgiving than the production build. The code reaches the main branch, somebody expects it to be ready, and that is when the forgotten check decides to introduce itself.

GitHub Actions removes that memory test. The repository describes the steps once, then GitHub runs them when a chosen event happens. The value is not that the commands become clever. The value is that they become consistent.

02

What GitHub Actions means in plain English

A workflow is an automated process stored as a YAML file inside .github/workflows. An event starts it. A job groups work that runs on one runner. Steps perform the individual commands or reusable actions.

For this site, a push or pull request is the event. The build is the job. Checking out the code, preparing Node.js, installing dependencies, checking the Astro project and building the site are the steps. GitHub supplies a fresh Ubuntu virtual machine as the runner.

That vocabulary sounds more complicated than the small job it describes. I find it easier to read the file as: when this happens, use this machine and complete these steps in this order.

03

The first workflow checks every change

The repository has a workflow named CI, which stands for continuous integration. It runs for every pull request and whenever code is pushed to main.

The pull request run is the useful early warning. A proposed change has to survive the same clean install, project check and production build before it is merged. The run on main confirms that the final combined branch still passes after the merge.

This does not decide whether the feature is sensible or whether the page looks good. It answers a narrower question: can this exact version of the repository be installed, checked and built from scratch?

Narrow checks are useful when their meaning is clear. A green tick should tell me something specific, not create a warm feeling that every possible problem has somehow disappeared.

04

A fresh runner catches assumptions from my laptop

Each CI run starts on a fresh Ubuntu runner. The workflow checks out the repository, sets up Node.js 24 and enables npm's dependency cache. It then uses npm ci rather than an ordinary install.

The locked install matters. npm ci installs from the lock file and is intended for clean, repeatable environments. If the package files disagree, the workflow should fail instead of quietly changing the dependency tree while it builds.

The cache improves speed by reusing downloaded package data between runs. It does not turn the runner into my laptop or preserve a mysterious old node_modules directory. The actual project dependencies are still installed cleanly for the current lock file.

That clean starting point is one of the strongest reasons to use CI. If a build only succeeds because my computer remembers something the repository does not, the repository is incomplete.

05

I check the project before building it

After installing dependencies, the CI workflow runs the project's check command and then the production build.

For this Astro site, the check step catches type and project errors before the build is treated as valid. The build step then generates the static site into the dist directory.

The order is deliberate. A build command can sometimes produce output while warnings or type problems still deserve attention. Running the explicit check first keeps the signal clearer. If either step fails, the workflow stops and the change is not presented as a healthy build.

A larger application might add unit tests, browser tests, code formatting or security checks. I have kept this workflow matched to the project that exists. Adding five empty ceremonies would not make the site safer. Adding the next check should happen when it protects a real behaviour or known risk.

06

Checking and publishing are separate jobs

This repository uses a second workflow for publishing the website. I prefer that separation because checking code and changing the deployment output carry different responsibilities.

The CI workflow only needs to read the repository and run commands. The publishing workflow must create and push the finished build branch, so it has write access to repository contents. Keeping that permission out of the ordinary check workflow makes the boundary easier to understand.

Separate files also make a failed run easier to read. A CI failure means the source did not pass its checks. A publishing failure means the release route had a problem after or around those checks. Those are different conversations, even when some setup steps are shared.

07

A push to main creates the finished site

The publishing workflow starts when code is pushed to main. It can also be run manually, which is useful when I need to repeat the publishing process without inventing another code change.

It checks out the source, prepares the same Node.js version, installs the locked dependencies, runs the project check and builds the site again. Repeating the validation is intentional. The deployment should be based on the workflow's own clean result, not on an assumption that another run completed earlier with identical inputs.

Once the build passes, the workflow creates a fresh orphan branch named hostinger. An orphan branch has no normal file history from main. The workflow copies only the built dist output into it, adds the small hosting files the deployment needs and force-pushes that branch.

The result is a useful separation. main holds the source code and project configuration. hostinger holds the static files that are ready for the hosting setup to serve. I do not have to commit generated HTML, CSS and JavaScript beside the source files after every edit.

08

Newer deployments should beat older deployments

Two quick pushes can start two publishing runs. Without a rule, the older run could finish after the newer one and publish stale output.

The workflow uses a concurrency group named hostinger-build and cancels an in-progress run when a newer one starts. For this static site, that is the behaviour I want. There is no value in carefully finishing a deployment that has already been replaced by a later commit.

Concurrency deserves a deliberate choice. A database migration or a long release process may need jobs to queue instead of being cancelled halfway through. The correct rule depends on what interruption means for that deployment.

09

The workflow gets only the permission it needs

GitHub creates a temporary repository token for each job. The publishing workflow grants that token permission to write repository contents because pushing the hostinger branch is part of its job.

That permission should be treated as part of the application design, not as decoration at the top of a file. A check workflow normally does not need write access. A deployment workflow may need a narrow write permission, a cloud identity or a secret, but each extra capability increases what a broken or compromised step could do.

The repository currently uses GitHub's own checkout and Node setup actions through version tags. GitHub's current security guidance says a full commit SHA is the immutable option for pinning an action. Version tags are convenient and widely used, but the choice should be understood and reviewed rather than copied forever without thought.

10

Publishing also tells search engines what changed

After the site has been built and the deployment branch has been pushed, the workflow runs the repository's IndexNow script.

The script reads the sitemap from the finished build and compares the commit before the push with the commit that was just published. This lets it submit the relevant URLs after the release rather than making search submission a separate manual job.

The timing matters. Sending a URL before the build succeeds would announce a page that may not exist yet. Keeping the submission after the publishing step means it only runs when the release output has been prepared.

This is a good example of using Actions for a small piece of delivery work around the application. The workflow is not limited to compiling code. It can coordinate the repeatable tasks that belong to a successful release, provided each one has a clear reason to be there.

11

What GitHub Actions does not replace

A green workflow does not prove that a page is easy to use, a feature meets the requirement or a production system can recover from failure.

Actions runs the checks I define. If there is no useful test for a critical behaviour, the workflow cannot invent one. If the hosting platform fails after accepting the build, CI is not monitoring. If a deployment needs a rollback, the team still needs a tested route back. Backups, access reviews and operational ownership remain separate responsibilities.

It is also possible to automate a poor process. A complicated workflow with broad permissions and nobody willing to change it can become another legacy system, only this time written in YAML.

The useful boundary is simple: GitHub Actions should make known, repeatable work happen consistently. Human judgement still decides what should be checked, what a failure means and whether a release is safe enough to continue.

12

Keep the workflow boring enough to trust

I use GitHub Actions on this site to remove variation from the path between a code change and a publishable build. Pull requests get checked in a clean environment. The main branch gets checked again, built into a separate deployment output and followed by the small search submission task that belongs after publishing.

None of those steps is especially dramatic. That is a strength. A deployment workflow should be easy for another developer to read, easy to run again and clear about what it can change.

Start with the command somebody keeps forgetting. Put it on a fresh runner. Make failure visible. Then add another step only when it removes a real risk or manual handover. The best automation is often the boring route everybody can trust.

Useful questions

GitHub Actions workflow review checklist

  • Which repository event starts the workflow?
  • What useful fact does a successful run prove?
  • Does the runner install the project from a clean, locked dependency state?
  • Are checks and tests run before a deployment can continue?
  • Can two deployment runs race or publish in the wrong order?
  • Does each workflow have only the permissions it needs?
  • Are reusable actions trusted and versioned deliberately?
  • Can the deployment be repeated manually when the source has not changed?
  • What monitoring, backups and rollback work still sits outside GitHub Actions?
Daniel Mills

Written by Daniel Mills

Business understanding and hands-on software delivery.

I help owners and teams improve the software they rely on, replace fragile processes and turn new ideas into practical systems people can actually use.