Continuous Integration only matters if it runs something real. Most CI tutorials either show massive pipelines that do too much, or toy examples that do nothing useful. The goal here is smaller and stricter: one workflow, one runner, two real commands, and a visible green check in GitHub. If those three things exist, you have working CI. Everything else is optional.

This post walks through adding a single GitHub Actions workflow to a JavaScript project. The workflow installs dependencies, runs lint, and runs build. No test scaffolding. No deployment. No claims beyond what actually happens. At the end, you will see the workflow run in GitHub and you will know exactly why it passed or failed.

Why CI is worth setting up

Without CI, the only person verifying builds is you, on your machine, in your environment. The moment another contributor, another OS, or another Node version enters the picture, assumptions start breaking. CI solves exactly one problem: it runs your project in a clean environment every time you push, using declared versions and declared commands. If the commands fail there, they will fail for someone else too. That feedback loop is the entire value.

GitHub Actions is simply the execution platform. It spins up a temporary machine, checks out your code, runs the commands you specify, and reports success or failure. Nothing more mysterious than that is happening.

What you will add

You will add a single YAML file under .github/workflows. That file describes when the workflow runs, what environment it uses, and which commands to execute. Once committed, GitHub reads it automatically and starts running it on every push to your main branch and every pull request targeting that branch.

By the end, three things will exist:

• A workflow file in your repo • A visible Actions run in GitHub • A green checkmark on successful pushes

If those exist, CI is working.

What you need before starting

You need a GitHub repository and a project that already defines lint and build scripts in package.json. If those scripts do not exist, this workflow has nothing real to run, and the setup is meaningless. The workflow will not guess commands for you.

You should also be able to run these commands locally. CI should never be the first place you discover that your own build is broken.

Start to finish

Step 1: Create the workflow file

GitHub Actions looks for workflow definitions inside .github/workflows. You will create a single file named ci.yml in that directory. The content below declares one job that runs on Ubuntu, installs Node, installs dependencies, then runs lint and build.

Create the file:

name: CI
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm install
- run: npm run lint
- run: npm run build

Nothing in this file is decorative. Each step corresponds to a real action.

actions/checkout pulls your repository onto the runner. actions/setup-node installs the Node version you specify. The three run lines execute exactly the same commands you run locally. If any of them exit with a non‑zero code, the workflow fails. That is the whole contract.

Step 2: Commit and push

Once the file exists in your repo, commit it and push to GitHub. There is no extra configuration step. GitHub scans the .github/workflows directory automatically.

As soon as the push completes, GitHub queues a workflow run. You can watch it in real time under the Actions tab in your repository. Each step logs its output, just like a terminal session.

Step 3: Verify the run

Open your repository in GitHub and click the Actions tab. You should see a workflow named "CI". Click into the latest run.

If everything passes, you will see each step marked as successful and the overall workflow marked with a green check. That green check is the only thing CI owes you: proof that a clean machine could install, lint, and build your project without manual intervention.

If the workflow fails, the log will show exactly which command failed. There is no guessing. You fix the failure locally, commit again, and CI re‑runs automatically.

Why each part exists

The on block defines when the workflow triggers. Running on pushes ensures your main branch is always verified. Running on pull requests ensures changes are checked before merging.

The runner environment (ubuntu-latest) gives you a consistent, clean OS every time. If your project only works on your laptop, CI will expose that quickly.

Pinning a Node version prevents surprises when GitHub updates default environments. You are declaring which runtime your project expects.

Running npm install inside CI ensures dependency resolution works from scratch. Running lint ensures code style or static checks remain enforced. Running build ensures the project can actually compile or bundle. Those three together represent the minimum viable health check for most JavaScript projects.

Common failure modes

CI failures are usually simple and mechanical.

A workflow that never runs is almost always a branch mismatch. If your default branch is not named main, the trigger will never fire.

A workflow that fails during install usually means a missing or outdated lockfile. Committing package-lock.json or equivalent makes installs deterministic.

A workflow that passes locally but fails in CI usually means your local environment hides an assumption. Different Node versions, missing environment variables, or OS‑specific paths are common causes.

In every case, the fix is to make the local and CI environments match more closely, not to weaken the CI checks.

Extending later

Once this baseline works, you can add tests, caching, artifact uploads, or deployments. Those are all additive. None of them are worth doing until this core loop is stable.

A workflow that runs real commands and gives reliable feedback is already useful. Fancy pipelines that do not run real checks are not.

Closing

A working CI setup is not measured by how complex it is. It is measured by whether it runs your real build in a clean environment and tells you when something breaks.

If you have a workflow file, a visible Actions run, and a green checkmark on successful pushes, CI is doing its job.