Continuous Integration (CI) is the practice of automatically building and testing your code every time you push a change. For beginners, GitHub Actions offers a free, built‑in solution that requires no extra servers. In this guide we’ll walk through every step needed to spin up a basic CI pipeline for a simple Node.js project, from repository preparation to troubleshooting common errors.
What You’ll Need
- A GitHub account (free tier works fine)
- Basic knowledge of Git and the command line
- A small project – we’ll use a Node.js app, but the concepts apply to any language
- Access to a code editor (VS Code, Sublime, etc.)
- Optional: A personal access token if you plan to use private repositories
Step 1: Prepare Your Repository
Start by creating a new repository on GitHub. Click the New button, give it a descriptive name (e.g., ci-demo), and initialize it with a README.md. Clone the repo to your local machine:
git clone https://github.com/your‑username/ci-demo.git
cd ci-demo If you don’t already have a Node.js project, create one quickly:
npm init -y
npm install --save-dev jest
echo "module.exports = { testEnvironment: 'node' };" > jest.config.js
cat >> package.json <<'EOF'
"scripts": {
"test": "jest"
}
EOF Commit the starter files:
git add .
git commit -m "Initial project with Jest test setup"
git push origin main Step 2: Create the Workflow File
GitHub Actions looks for workflow definitions in the .github/workflows directory. Create that path and add a file named ci.yml:
mkdir -p .github/workflows
cat > .github/workflows/ci.yml <<'EOF'
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
EOF This file tells GitHub to trigger the workflow on pushes and pull‑requests to the main branch, spin up an Ubuntu runner, check out the code, install Node, install dependencies, and finally run the test suite.
Step 3: Push the Workflow and Observe the Run
Commit the new workflow file and push it:
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push origin main Navigate to the Actions tab of your repository on GitHub. You should see a new workflow run titled “CI”. Click it to watch each step execute in real time. If everything is set up correctly, the job will finish with a green checkmark.
Step 4: Add a Bad Test to See Failure Handling
To understand how failures are reported, add a deliberately failing test:
cat > sum.test.js <<'EOF'
const sum = (a, b) => a + b;
test('adds 1 + 2 to equal 4', () => {
expect(sum(1, 2)).toBe(4);
});
EOF
git add sum.test.js
git commit -m "Add failing test"
git push origin main The next workflow run will stop at the “Run tests” step and display the Jest failure output. GitHub will automatically mark the run as failed, giving you instant feedback.
Step 5: Store Secrets Securely (Optional)
If your build needs API keys, database passwords, or other sensitive data, never hard‑code them. Instead, add them as repository secrets:
- Go to Settings → Secrets and variables → Actions in your repo.
- Click New repository secret, give it a name (e.g.,
API_TOKEN), and paste the value.
Reference the secret in your workflow using the ${{ secrets.API_TOKEN }} syntax. For example, to set an environment variable before running tests:
- name: Run tests with secret
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
run: npm test This keeps credentials out of the codebase and logs.
Step 6: Extend the Pipeline – Add a Build Artifact
Most real‑world projects also need to package the code. Let’s add a step that creates a ZIP file and uploads it as an artifact:
- name: Build project
run: |
npm run build || echo "No build script, skipping"
zip -r artifact.zip . -x "node_modules/*" "*.git*"
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: source‑archive
path: artifact.zip Now every successful run will produce a downloadable artifact.zip you can retrieve from the workflow summary.
Common Mistakes to Avoid
Even a simple pipeline can trip up beginners. Here are the most frequent errors and how to fix them:
- Forgetting the
.github/workflowsfolder: GitHub only scans that exact path. A typo in the directory name means the workflow never triggers. - Using
npm installinstead ofnpm ci:npm ciinstalls exact versions frompackage-lock.json, ensuring reproducible builds.npm installcan introduce version drift. - Mismatched Node version: If your local dev uses a newer Node version than the runner, you might see syntax errors. Always specify the version in
actions/setup-node. - Hard‑coding secrets: Committing API keys to the repo will expose them publicly. Use GitHub Secrets instead.
- Skipping the checkout step: The runner starts with a clean VM. Without
actions/checkout, subsequent steps won’t find your code.
Tips and Tricks
Level up your pipeline with these quick enhancements:
- Cache dependencies: Add a cache step to speed up builds.
- name: Cache node modules uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - Parallel jobs: Split linting, testing, and building into separate jobs that run concurrently, reducing total runtime.
- Matrix testing: Test against multiple Node versions or OSes with a matrix strategy.
strategy: matrix: os: [ubuntu-latest, windows-latest] node-version: [18, 20] - Badge for README: Show pipeline status on your project page.

Frequently Asked Questions
Can I use GitHub Actions for private repositories?
Yes. Private repos have the same Actions capabilities. The only limitation is that free accounts receive a limited number of minutes per month; you can purchase additional minutes or self‑host runners if needed.
Do I need to write YAML by hand?
You can start with the visual editor under the Actions tab, which offers starter templates for many languages. However, learning the basic YAML syntax gives you full control and helps when you need custom steps.
How do I debug a failing workflow?
Click the failed run, expand the step that failed, and view the raw logs. You can also add run: echo "debug info" statements or enable debug: true in the workflow’s env to get more verbose output.
Conclusion
Setting up a CI pipeline with GitHub Actions is straightforward: create a workflow file, define the steps you need, and let GitHub handle the rest. By following the steps above you’ll have automated builds, tests, and artifact generation in minutes, while also learning how to avoid common pitfalls and extend the pipeline as your project grows. Keep iterating, add caching, matrix builds, and secret management, and you’ll soon have a robust CI foundation that scales with your code.
Photo by Wolfgang Weiser on Unsplash





