A broken change reaches customers when nothing runs between the commit and the deploy. At least 17 of the 21 third-party apps I audited in June and July 2026 had no deploy gate: every push ships straight to production with nothing checking it first. CI CD best practices for a small app start with one workflow on every pull request and a merge that waits for green.

CI CD best practices: four checks on every pull request, in the order that fails fastest

CI CD best practices for one repo come down to 4 checks on every pull request, run cheapest first: lint, typecheck, build, tests. Each is a named step that blocks the merge when it fails, runs the same command a developer runs locally, and leaves a result you can point to.

The 21 apps behind that count come from two groups I audited in June and July 2026: 11 public third-party apps audited exhaustively and 10 held-out third-party apps audited blind. That is a selected set of apps, not a random sample, so the count describes those apps and is no rate for AI-built apps in general. The wider release path this gate sits in, from one repository to a team, is DevOps for startups.

Here are the four checks in the order they run. The time budget column is my working rule for a small app, not a vendor figure.

CheckWhat it catchesThe usual commandTime budget (my rule)Blocks merge
LintStyle errors and the forbidden patterns your rules namenpm run lint (ESLint or Biome)SecondsYes, once the check is required
TypecheckType errors, including the ones a framework build was told to skiptsc --noEmit on a plain TypeScript project; tsc -b where the root tsconfig.json only lists referencesA minute or twoYes, once the check is required
BuildThe production build the host will run, so a build that would break on the host breaks here firstnpm run buildA minute or twoYes, once the check is required
TestsRegressions in the smallest suite that touches sign-in and paymentnpm testA few minutes for the first suiteYes, once the check is required

The typecheck command is whatever the repo’s own typecheck script runs. On a plain project that is tsc --noEmit, which uses the compiler only as a type-checker and writes no files, as TypeScript’s noEmit option describes. If package.json has no typecheck script yet, add one that runs the right command for the project. A project-references setup works differently: Vite’s React TypeScript template ships a root tsconfig.json with an empty files list and two references, and its build script runs tsc -b, because plain tsc does not build referenced projects unless it runs in build mode.

Cheapest first means a typo fails in seconds, not after a long test run, and the red step’s name tells the author where to look. For a beginner, the whole CI CD checklist is that table plus seven lines. The CI CD pipeline best practices below are for one repo’s software, not for the infrastructure a platform team runs:

  1. Trigger the workflow on pull_request and on pushes to main.
  2. Keep one workflow file in the repository, under .github/workflows/.
  3. Run the same package scripts a developer runs locally, with no logic hidden in the YAML.
  4. Cancel a run when a newer push to the same pull request replaces it.
  5. Allow no continue-on-error and no skipped checks, because a step allowed to fail is a step nobody reads.
  6. Make the checks required before merge, where the GitHub plan allows it.
  7. Keep the result: the run link, its status and the date.

In a wider software development pipeline this is the first gate, and it is the cheapest piece of SDLC automation a small team can add. Building once and promoting the artifact, environment parity and progressive deployment matter too, but they all happen after merge, so they belong to the release readiness checklist. Shifting security checks left into this same pipeline is pipeline security, linked from the workflow section below.

What is CI/CD in DevOps: CI, CD and continuous integration testing, word by word

CI/CD in DevOps means continuous integration, where every change is merged into a shared codebase at least daily and checked by an automated build that includes tests, plus continuous delivery or deployment. DevOps is the wider practice both sit inside. On a host that deploys on push, the deployment half already exists and the checking half is missing.

The definition is close to the wording of Martin Fowler’s article on continuous integration, revised on 18 January 2024, where each integration “is verified by an automated build (including test) to detect integration errors as quickly as possible.” The five terms, side by side:

TermWhat it meansWhat it looks like on a one-repo app
Continuous integrationEach developer merges into the shared codebase at least daily, and an automated build with tests verifies every mergeThe workflow that runs on every pull request
Continuous integration testingThe automated tests that run inside that verification, nothing more exoticThe npm test step in the same workflow
Continuous deliveryThe product is always in a state where the latest build can be released, so releasing is a business decisionMain stays green, and a release is a button
Continuous deploymentEvery change that passes all the automated tests in the pipeline goes to production automaticallyEvery green merge to main goes live with no button
DevOpsDevelopers owning how their code is built, released and run; CI/CD is one part of itOne engineer or the founder owns the pipeline, the host and the alerts

The meaning of CI/CD in software development is that pair of habits, not a product. A CI/CD platform is the product that runs the jobs, and the CI/CD systems a small team meets are covered in the tools section below. In cloud computing terms, a hosted CI/CD service is someone else’s machines running your checks. The difference between CI/CD and DevOps is scope: CI/CD is part of DevOps, and the DevOps vocabulary at large sits in the DevOps for startups guide linked above.

For CI/CD deployment on a one-repo app hosted on Vercel, the delivery half already exists: Vercel creates a preview deployment for every push and a production deployment each time you merge to the production branch, according to Vercel’s Git integration docs. What is missing is the CI part, since nothing checks the push first. That is why CI/CD is important for a small app: a broken change gets caught before a customer sees it. To set up a DevOps environment at this size, you need this workflow plus separate environments, which is what a staging environment is for.

What goes wrong without it

What happenedWhy nothing caught itThe check that would have
Broken code merged because nobody ran testsThe tests exist but run only on a laptop, when someone remembersTests, on every pull request
A type error shipped to productionThe build was configured to ignore type and lint errorsTypecheck, run as its own step
The build passed locally and failed on the host, so the change the author thought had shipped never didNothing built the change the way the host does before mergeBuild
An AI assistant rewrote a working file and removed a guardNobody read that part of the diff, and no rule looks for the guardLint rules and tests on the pull request

The cause in row two is one the same audits recorded: several apps disable their own type and lint checks at build time. Take a founder’s Next.js app, built with an AI builder, that deploys from main on every push and carries ignoreBuildErrors: true in its config, the flag that lets a production build finish with TypeScript errors. A change renames a field the pages read, the build completes because type checking is skipped, the host deploys it, and the pages show empty values where the field was. A build that succeeds is not a typecheck that passed, and a typecheck step on the pull request fails where that build did not.

Tests were missing too. In that same selected set, at least 18 of the 21 third-party apps had no working test anywhere: 17 with literally none, plus a retail POS whose checkout “test suite” never executed the actual checkout code.

A green run proves one thing: the checks passed. It does not prove the app works, and my tests pass but the app is still broken covers what a passing suite cannot see.

CI CD in GitHub: how to set up the pipeline on GitHub Actions

CI CD in GitHub is one YAML file in the repository’s workflows folder, triggered by pull requests and by pushes to main, that installs dependencies from the lockfile and runs four package scripts. The file is short; clearing what it finds is the real work.

GitHub Actions is the natural choice when the repository already lives on GitHub. Usage is free for public repositories on standard GitHub-hosted runners, and a private repository draws on a monthly quota set by the account’s plan: 2,000 minutes on GitHub Free and 3,000 on GitHub Pro or GitHub Team, per GitHub’s Actions billing page read on 28 September 2026. The workflow on this page is built from GitHub’s documentation, its workflow syntax reference and its Node.js guide, not from a run on a client repository.

The CI/CD pipeline setup starts in the browser. To implement CI/CD without a terminal, the beginner CI/CD setup follows GitHub’s own click path for its Node.js workflow template:

  1. On the repository’s main page, click Actions under the repository name, then New workflow if the repository already has one.
  2. Search for “Node.js”, filter by Continuous integration, and click Configure on the “Node.js” workflow.
  3. Replace the template’s contents with the file in the next section, click Commit changes, and choose to open a pull request instead of committing to the default branch.
  4. Open that pull request and watch the run start on it.

The template lands as node.js.yml in .github/workflows; renaming it ci.yml changes nothing but the name. That is how to set up a CI/CD pipeline the first time; the sections below make it strict.

The CI lint and typecheck workflow, with build and tests

The CI workflow is a single job with 4 named steps after install: lint, typecheck, build, test. Named steps show which check failed in the pull request. A separate typecheck step matters most where the framework build is set to ignore type errors.

# .github/workflows/ci.yml
name: CI
on:
  pull_request:
  push:
    branches: [main]
concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true
permissions:
  contents: read
jobs:
  checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<40-character-commit-SHA>    # v7
      - uses: actions/setup-node@<40-character-commit-SHA>  # v7
        with:
          node-version-file: .nvmrc
          cache: npm
      - run: npm ci
      - name: Lint
        run: npm run lint
      - name: Typecheck
        run: npm run typecheck
      - name: Build
        run: npm run build
      - name: Test
        run: npm test

Every workflow key comes from GitHub’s workflow syntax reference, and the two with inputs from the setup-node README. The concurrency group joins the workflow name to the pull request’s branch, so a new push cancels the run it replaces; github.head_ref is only defined on pull request events, so a push to main falls back to its own run ID and never cancels another. Setting contents: read leaves every permission you do not list at none, and why that matters is covered in CI CD security best practices. Replace each <40-character-commit-SHA> with the full commit SHA of the v7 release in that action’s own repository, and keep the version as the comment; why the pin matters is in the CI CD security best practices guide linked earlier in this paragraph. node-version-file reads the Node version from .nvmrc, so commit one if the repository has none, and npm ci installs what the lockfile says and exits with an error when package.json and the lockfile disagree.

Lint runs first in the GitHub Action because it is the cheapest check, and the test step then runs the suite in CI on every push to a pull request and on every push to main. The workflow has no paths filter on purpose: when path filtering skips a workflow, GitHub leaves its checks “Pending”, and a pull request that requires them is blocked from merging.

If next.config sets typescript.ignoreBuildErrors, next build skips its type checking step entirely, and the Next.js TypeScript config reference warns that without type checks elsewhere “this can be very dangerous.” Lint is simpler on the current major: the Next.js 16 upgrade guide says next build no longer runs linting and the eslint option in the config file is removed, so the lint step is the only lint the project gets. Turn the type flag off once the backlog is clear, with how to enable TypeScript strict mode as the next step, and keep the rule set itself in the config file that linters read. The build step also needs whatever public environment variables the framework reads at build time: give CI dummy values, never production secrets.

How to run tests on every pull request when the repo has no tests

A repo with no tests still gets CI on day one: lint, typecheck and build catch a lot of broken merges on their own. Add the test step as a passing script, then write the sign-in smoke test and the payment test before anything else.

Ship the workflow with three real checks and a test script that exists and passes, so the step is wired before there is anything in it. Then grow it: a smoke test for sign-in and one for the payment path first, written the way how to write end-to-end smoke tests describes, and after that the order in how to test a vibe-coded app. Tests in CI run against a disposable database or a separate test project, never production data; the staging article above explains how to get one. In testing terms, CI/CD means the suite runs on every change without anyone choosing to run it. For automation testing, CI/CD adds the one thing a local runner cannot: nobody can forget to run it.

A CI check that blocks a forbidden code pattern

A lint rule, or a small search step next to the lint step, can fail the run when a named pattern appears. Four patterns worth blocking in an AI-built app are the service-role key’s variable name in client code, dangerouslySetInnerHTML without the sanitizer, a statement that disables row level security in a migration, and console.log of a request body. A name search does not find a key someone pasted as a value; that job belongs to secret scanning, a separate topic. The full pattern list, and the rules file that tells the coding assistant about it, are in guardrails for AI coding agents. Put the check in the lint step so it fails first.

Catch a breaking API change in CI

When the frontend and backend share types, the typecheck catches a breaking API change on its own: rename a field on the server and every client use of it stops compiling. With a schema between them, an OpenAPI file or a generated client, a CI step regenerates the client and fails when the result differs from what is committed. The contract design behind that is a separate problem: the frontend broke after a backend change. A database schema change rides the same pull request and gets its own checks from the migrations page.

Make the checks required, and keep update pull requests honest

A check that does not block merge is a suggestion. Mark the checks job as a required status check on main, which GitHub branch protection walks through; after that, “all required status checks must pass before collaborators can merge changes into the protected branch”, per GitHub’s protected branches page. There is a plan condition: protected branches work on public repositories with GitHub Free, and on private repositories only with GitHub Pro, GitHub Team, GitHub Enterprise Cloud or GitHub Enterprise Server. On a free private repository the check still runs and reports, and the owner treats red as a stop. By default a rule does not bind people with admin permissions, which on a one-founder repository means the founder, until “Do not allow bypassing the above settings” is turned on. GitHub also asks for job names that are unique across all workflows when specific checks are required.

Dependency-update pull requests run the same workflow, so a Renovate or Dependabot update passes the same four checks before it merges; what Renovate bot is, and how it is set up, is a separate question. Preview deployments sit beside CI, never instead of it, which is part of what preview deployments are. Jobs that are not per pull request go in a second workflow on an on.schedule trigger, and the usual first one repeats the certificate expiry and HTTPS redirect checks every week, which add HTTPS to a website defines. Keeping the whole run inside its time budget is how to speed up CI build times.

The checks worth running, and the tools that run them: actionlint, act, Biome, Danger

The 6 helper tools in this section do narrow jobs: actionlint checks workflow files, act runs workflows locally, Biome lints and formats in one command, Danger applies rules to the pull request itself, and two small actions manage bot comments and artifacts. None is needed on day one.

The “what it does” column is each project’s own description. The two “when” columns are my working rule.

ToolWhat it doesWhen to add itWhen to skip it
actionlintA static checker for GitHub Actions workflow files: unexpected or missing keys, ${{ }} expression types, action inputs, and shellcheck on run: scriptsMore than one workflow, as a step or a pre-commit hookOne short workflow you rarely touch
actReads .github/workflows/ and runs the jobs locally in Docker containersDebugging a workflow without pushingThe workflow only calls package scripts, which already run locally
BiomeLint and format in one tool; biome ci runs the formatting, linting and assist checks without modifying filesA new repository with no linter yetAn ESLint setup that already works
DangerEvaluates a Dangerfile of your rules during CI and leaves messages on the pull request; the Python port’s README says it is work in progressReview comments keep repeating the same requestA small team that reviews every diff anyway
sticky-pull-request-commentCreates a pull request comment, or updates it if it already exists; needs pull-requests: writeA job posts a report on every runNothing posts comments yet
action-download-artifactCan find another workflow run by workflow, commit, branch or pull request and downloads its artifactsComparing a pull request with a baseline, such as a bundle-size report from mainNo job compares runs

actionlint catches a mistyped workflow key before a run is wasted on it. The act CLI tests GitHub Actions workflows on your machine with no push, and act’s user guide warns that its default images “do not contain all the tools that GitHub Actions offers by default in their runners”, so a green act GitHub Action run is a hint, not proof. biome ci is the pipeline form of biome check: the same checks, with no file changes, per Biome’s CLI reference.

The Danger tool is the one on this list that reads the pull request itself. Danger JS runs your rules, written in JavaScript or TypeScript, and leaves messages on the pull request, such as a request for a changelog entry or a warning on a big diff. Danger for Python exists as danger-python, the port in the table, and its only GitHub release is v0.1 from February 2020.

Two small actions finish the list. marocchino/sticky-pull-request-comment, or sticky-pull-request-comment, keeps one bot comment updated instead of adding a new one per run. dawidd6/action-download-artifact, or action-download-artifact, can find a run by branch or pull request, where the official download action can download from another run only when its exact run ID is known. Whichever you add is pinned to a commit SHA along with the core actions, as part of pipeline security. Among CI/CD automation tools, the honest default is none of these six until the problem each one solves shows up.

Best CI CD tools for a small team, and when a hosted runner stops being enough

The best CI CD tool for a small team is the one that ships with the code host: GitHub Actions on GitHub, GitLab CI on GitLab. Switching products rarely fixes a slow or flaky pipeline. My working rule: a self-hosted runner on the same product comes before any move.

The “hosted or self-hosted” and “free allowance” cells come from each vendor’s own page, read on 28 September 2026. The “fits when” and “does not fit when” cells are my reading.

ToolHosted or self-hostedFree allowanceFits whenDoes not fit when
GitHub ActionsHosted runners, or self-hosted runners you manageFree on public repositories with standard runners; private repositories get 2,000 minutes a month on GitHub Free, 3,000 on Pro or TeamThe code is on GitHubThe code lives on another host
GitLab CIHosted on GitLab.com, or GitLab Self-Managed on your own infrastructure400 compute minutes a month on the Free tierThe code is on GitLab, where GitLab continuous integration is built inThe code is on GitHub
CircleCIHosted; the Free plan also lists self-hosted runnersUp to 6,000 build minutes on a small Docker resource class (30,000 free credits a month), up to 5 active usersA team that wants CI apart from its code hostA one-repo team already on GitHub Actions
JenkinsSelf-hosted: WAR files, native packages, installers or Docker images you runOpen source, MIT license; you pay for the serverA team that already runs it or needs full controlA new one-repo app with no ops person
Woodpecker CISelf-hosted: your own Woodpecker instanceOpen source, Apache 2.0 licenseA light open source CI on a box you already runNobody wants to run a server

Prices checked 28 September 2026 on GitHub’s billing page and on GitLab’s and CircleCI’s pricing pages.

Top CI/CD tools for a one-repo team are the five in this table, and the list stops there on purpose. Every one of them has a free way in, so free CI/CD tools are the wrong thing to choose on; the code host is the right one. A CI/CD SaaS such as GitHub Actions, GitLab CI or CircleCI runs the machines for you, while self-hosted CI/CD means you own the machine and its patches.

Two open source CI/CD tools in the table need a server of your own. Jenkins’ download page offers a Stable (LTS) line whose baselines are chosen every 12 weeks, with fix releases every 4 weeks, and a weekly line. Jenkins calls itself an automation server that “can be used as a simple CI server or turned into the continuous delivery hub for any project”, so it does both CI and CD. Woodpecker CI’s docs describe a lightweight tool you deploy as your own instance.

CircleCI vs Jenkins comes down to a hosted service that bills in credits against a server you run, patch and secure yourself. For a team without an ops person, I count that server as the real cost.

My rule is that a hosted runner stops being enough when builds need more memory or a GPU, when a job has to reach a private network, or when the minutes cost more than a small VM would. The next step then is a self-hosted runner on the same product, which GitHub’s self-hosted runners page describes as “a system that you deploy and manage to execute jobs from GitHub Actions on GitHub”, free to use while you pay to keep the machine running. GitHub’s guide to adding self-hosted runners recommends them only for private repositories, because forks of a public repository can potentially run dangerous code on your runner by creating a pull request that executes it in a workflow.

How to verify it

Pull-request CI is verified with 5 pull requests: a lint error, a type error, a broken build and a failing test, each failing at its own step, then a clean change that passes. Keep the five run links, the merge box and the date.

The first four pull requests verify that CI fails on a bad commit at the step meant to catch it, and the fifth that it passes a good one:

  1. 01 Lint: break a rule your lint config enforces; the run fails at the Lint step
  2. 02 Typecheck: assign a string to a variable typed as a number; the run fails at Typecheck
  3. 03 Build: add a change the typecheck accepts and the bundler rejects, such as a side-effect import of a stylesheet that does not exist; the run fails at Build
  4. 04 Tests: change one assertion so it fails; the run fails at Test
  5. 05 Clean: a change with none of the above; all four steps pass

Check item three locally before you push it: TypeScript’s current default reports a side-effect import it cannot find, and a wildcard declaration such as declare module "*.css" {}, which your project may already have, makes it recognize all CSS files as module imports, so if your typecheck catches the import, use any change only the bundler rejects. Where the repository’s plan allows a rule on main (the plan condition is in the required-checks section above), the merge box blocks pull requests one to four and allows five, and with bypassing turned off a direct push of an unchecked commit to main is refused. That setting belongs to branch protection. On a free private repository, record the red and green runs only.

Evidence to keep: the five run URLs with their status, a screenshot of the merge box where the rule exists, and the date. Re-run all five after any change to the workflow file.

This is how we verify deliverable 7.3, Pull-request CI, in the Production Hardening Sprint: submit failing and passing changes and retain the CI results. After merge, the production deployment checklist takes over, starting from green CI on the exact commit being deployed.

Where the sprint does this

In the Production Hardening Sprint, deliverable 7.3, Pull-request CI, is to run linting, type checks, builds, and tests on every pull request, because automated checks catch regressions before changes reach customers. Deliverable 7.2 protects the main branch with required checks and controlled merge permissions. Deliverable 7.9 configures Renovate, Dependabot, or an equivalent to propose dependency updates with checks. The production readiness report accounts for all 123 IDs, keeps failures visible until resolved and explains genuine non-applicable items. The app’s current framework and hosting setup are our starting point. The full list is all 123 deliverables.

Common questions about CI on a small app

What is CI CD for beginners?

CI means every change is checked automatically before it merges. CD means a green main line ships with a button, or on its own. For one repository, the first step is one workflow file that runs on every pull request; the DevOps picture around it is in the DevOps for startups guide.

Is CI CD difficult?

Not to start. The first workflow is one file and four package scripts, and GitHub’s Node.js template writes much of it. The work is fixing what it finds on the first run: lint errors, type errors and a build that never ran clean.

Is Jenkins still relevant in 2026?

Yes, where a team already runs it or needs full control of its build servers: the project still ships a Stable (LTS) line and a weekly line, and its LTS download on 28 September 2026 was 2.568.3. In my view, a new one-repo app has no reason to start a Jenkins server when its code host already includes CI.

Does CI CD require coding?

A little. It needs one YAML file and the package scripts the repository already has, and GitHub’s Node.js workflow template, reached from the Actions tab, writes the first version of the YAML. Every command the checks run is code the project already contains.

Is GitHub Actions still free?

For public repositories on standard GitHub-hosted runners, yes. Private repositories get a monthly quota by plan, 2,000 minutes on GitHub Free and 3,000 on GitHub Pro or GitHub Team as of 28 September 2026, and usage past it is billed, or blocked when no payment method is on file.

Is Jira a CI/CD pipeline?

No. Atlassian sells Jira as project management for planning and tracking work. Its CI/CD product is Bitbucket Pipelines, which can share build and deployment status via Jira.