Set up a GitHub Actions workflow that installs dependencies, runs checks, and fails fast on pull requests.
What you will learn
- Workflow file structure (events, jobs, steps)
- Caching dependency installs
- Separating CI (verify) from CD (deploy)
- How to read logs when a job fails
Prerequisites
- A GitHub repository you can push to
- A Node (or similar) project with
npm test/npm run lint
Mental model
- Event starts a workflow (
pull_request,push) - Job runs on a fresh VM (
ubuntu-latest) - Step is either an action (
uses:) or a shell command (run:)
Docs: Understanding GitHub Actions.
First workflow
Create .github/workflows/ci.yml:
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm testOpen the Actions tab after you push. Click the failed step to read stdout—fix the log, not a blog post about a different stack.
Harden the basics
- Prefer
npm ciso installs match the lockfile - Keep secrets in repository/environment secrets, never in YAML
- Use
permissions:to grant the least privilege the job needs - Protect
mainso merges require a greenverifycheck
Useful next steps
- Matrix test across Node 20/22
- Upload build artifacts with
actions/upload-artifact - Split deploy into a second workflow that runs only on
main - Add
concurrencyto cancel outdated PR runs
Practice checklist
- Push a branch that fails lint and confirm CI goes red
- Fix the lint issue and confirm CI goes green
- Require the workflow status in branch protection
- Add one secret and reference it as
${{ secrets.NAME }}in a non-echoing step
Common pitfalls
- Using
npm installin CI instead ofnpm ci - Storing long-lived cloud keys without rotation
- Deploying from forks without careful permission design
- Debugging locally with different Node versions than CI
Sources and further reading
Who this guide is for
- Developers automating an existing repository
- Maintainers reducing manual release risk
- Teams reviewing supply-chain and deployment controls
Definition of done
A least-privilege GitHub Actions workflow that validates pull requests, pins important dependencies, protects secrets, publishes immutable artifacts, and requires explicit approval for production.
Finishing the commands is not the same as finishing the guide. Keep the result only when another person can reproduce it, inspect the evidence, and understand the remaining risks.
Professional workflow
Phase 1 — Define gates
Goal: Automate the same evidence trusted by reviewers.
- List install, lint, test, build, and artifact checks.
- Separate pull-request validation from deployment.
- Choose timeouts and concurrency behavior.
Verification: A deliberately broken change fails at the earliest relevant gate.
Phase 2 — Harden workflow trust
Goal: Limit what untrusted code and dependencies can do.
- Set explicit minimal token permissions.
- Pin third-party actions to reviewed versions or commit SHAs.
- Keep forked pull requests away from deployment secrets.
Verification: The permissions block and every secret consumer are visible in code review.
Phase 3 — Release with evidence
Goal: Make the deployed artifact traceable and recoverable.
- Build once and promote the same artifact.
- Attach commit and artifact identity to the release.
- Use protected environments and document rollback.
Verification: An operator can identify and redeploy the previous known-good artifact without rebuilding source.
Review questions before you continue
- Can untrusted pull-request code access any secret?
- Are deploy permissions broader than the job needs?
- Is the released artifact exactly the one tested?
- What prevents two releases racing each other?
If an answer is unknown, record it as an explicit follow-up instead of hiding the uncertainty behind a successful demo.
Troubleshooting decision guide
- A workflow works locally but fails remotely: compare runtime versions, paths, shells, and missing generated files.
- Jobs run twice or out of order: inspect event filters, dependencies, and concurrency groups.
- Caches create stale results: make cache keys depend on the lockfile and never treat cache as an artifact.
- Deployment cannot be traced: record commit SHA, artifact digest, environment, and deployment URL.
Change one variable at a time, preserve the first useful error, and write down the command or condition that fixed it. That turns a private debugging session into team knowledge.
Production-readiness checklist
- Use least-privilege permissions at workflow and job level.
- Protect environments and require review for production.
- Pin dependencies and enable automated update review.
- Set timeouts and cancellation for obsolete runs.
- Retain logs and provenance long enough for incident investigation.
These checks are intentionally stricter than a beginner demo. Use them to decide whether to continue learning, run a controlled pilot, or request specialist review.
Your next 30 minutes
- Create one failing pull request to prove the gate.
- Review every permission and secret reference.
- Record artifact and rollback identity in the release.
- Use GitHub's security-hardening documentation for the final review.
CTA: Pick the first unchecked step, complete it now, and save the evidence in your project README or decision log. Then open the official documentation linked below before adopting the result in production.