Development

Writing a Smoke Test Suite the Pipeline Runs on Every Deploy

Antonio Evans8 min read

A terminal window showing a smoke test run that finishes in 47 seconds.
Key points

Key points

  1. 1A post-deploy smoke suite should cover only the paths whose failure forces a rollback: health, login, one read per critical service, and one reversible write.
  2. 2The whole run should finish in under two minutes so the team never has a reason to skip or disable it.
  3. 3Keep the suite to roughly 10 to 20 checks and make every failure print the request, the status, and the expected result on a single line.
  4. 4Smoke testing is a gate, not a regression suite, and it has to run against the environment you just deployed to.

Smoke testing is a rollback gate, not a test suite

Smoke testing answers one question: should this build move forward at all? QA Wolf's checklist puts it plainly: if the system fails to start, respond or connect to required services, stop immediately instead of running deeper tests. That framing is the decision rule for every candidate check. If a failure would not, by itself, make you redeploy the previous artifact, it does not belong in the post-deploy gate.

The name is literal. Hardware engineers powered on a new board and watched for smoke; software does the same first power-on check. Smoke tests are broad and shallow rather than deep: they cover the critical surface quickly and skip feature behavior, business rules and edge cases, which Momentic's overview places under launch, login, navigation and checkout rather than under detailed validation. That shallow scope is what keeps the suite short enough to trust.

It also sits in a specific pipeline stage. Microsoft's pipeline guide lists five core stages, and the deploy stage explicitly includes "post-deployment smoke tests and health checks" to verify the application is functioning correctly after the artifact lands in a staging or production environment (Microsoft's CI/CD pipeline guide). Your suite runs there: after the new version is live and answering traffic, not before.

The distinction that matters operationally is rollback cost. A rollback reverts to the previous version of the application, which can mean redeploying the artifact or flipping traffic through a load balancer, and no rollback is risk-free because every change in the release goes back with it (Octopus on rollback strategies). A smoke suite exists to decide that question fast and with evidence. If it cannot answer "keep it or revert it," the check is in the wrong place.

What to assert: ten to twenty checks across four categories

The short answer from the intro holds up: health, auth, one read from each critical service, one reversible write. Here is how each category earns its slot.

Health and readiness. The main URL returns a success status, the health or readiness endpoint returns success, and the app can reach its database. QA Wolf's smoke checklist covers exactly these, plus dependent services such as auth, payments, queues and third-party APIs.

Authentication. One real login through the same path users take, including token issuance. Broken sign-in is the classic example of a failure that makes deeper testing pointless, and it is cheap to assert.

One read per critical service. One warm request per service you depend on, checking status and a couple of fields in the body. Gift the failure a schema check: status 200 with an error payload is still broken.

One write that rolls back. Create a record through the real API, assert it exists, delete it, assert it is gone. This catches wiring and permission failures that read-only checks miss, and it leaves no residue in production data.

A sensible inventory looks like this:

CategoryExample checkTypical budget
LivenessGET /health returns 200 with status ok2 to 5 seconds
ReadinessReadiness endpoint green, database reachable2 to 5 seconds
AuthLogin returns a token, token fetches /me10 to 20 seconds
Service readsOne request per critical service, body fields checked20 to 40 seconds
Reversible writeCreate, read back, delete, confirm deletion20 to 40 seconds
Static assets and routingMain page loads, one deep link resolves10 to 20 seconds
LogsNo fatal errors in startup logs5 to 10 seconds

That lands comfortably inside the 10 to 20 check range. TestMu AI describes a typical smoke suite as 20 to 50 cases finishing in 5 to 15 minutes (TestMu AI's smoke testing guide). That is fine as an upper bound, but a pipeline gate you want people to leave enabled should aim much lower, which the next section covers.

Keeping the run under two minutes

Every minute you shave off a build is a minute saved for each developer on every commit, and GitLab cites Martin Fowler's ten-minute build guideline for continuous integration (GitLab's CI best practices). Even ten minutes is too slow for a gate that blocks every deploy. The target for post-deploy smoke is one to two minutes end to end, which is what keeps it from being the step people disable.

Get there by spending the budget deliberately:

  • Run checks in parallel. Independent HTTP checks finish in the time of the slowest one, not the sum. Start all checks at once, then fail fast on the first hard failure.
  • Force one serialized chain. Auth depends on login, and the write depends on auth, so keep that chain linear and make everything else concurrent.
  • Cap every request. A timeout of 10 seconds per check with two retries turns a 30 second hang into a 30 second worst case, and slow is a failure too.
  • Skip the browser unless the release touched the front end. HTTP-level checks cover most deploys in seconds; save a single headless page load for releases that change rendering.
  • Warm the target first. The first request after a cold start can take several seconds; fire one throwaway request to /health before the timer starts.

A worked example: login at 1.5 seconds, auth-only /me at 0.4, four service reads in parallel at 0.9 each, one write at 1.2 with delete at 0.3, and static assets in parallel at 1.1. Serial time inside the dependent chain is about 3.4 seconds plus the parallel block, so wall time is roughly 4 seconds plus runner setup. Even with a cold start and retries you stay well under a minute. If your suite runs past 120 seconds, something in it is a regression test.

Treat flakiness as a bug, not noise. Teams that ignore maintenance end up with unreliable smoke tests that fail for the wrong reasons (QA Wolf). When a check flakes twice in a week, either fix its setup or delete it; a gate that cries wolf gets bypassed.

What to skip, and why full regression does not belong here

Smoke testing exists to check that a build is worth testing further. It does not validate that every feature works. Duplicating pre-merge coverage post-deploy buys nothing and costs the trust you need.

Test typeQuestion it answersWhere it runsDuration
SmokeIs the deploy alive and revert-needed?After every deploySeconds to minutes
SanityDoes this specific change work?After a targeted fixShort
RegressionDid anything break?Before releaseHours

That comparison follows the standard distinction between the three: smoke is broad and shallow on a new or unstable build, sanity is narrow and deep on a specific change, and regression is full coverage before release (TestMu AI). Anything already covered in the merge pipeline does not need to rerun against production with production data.

Three categories in particular deserve to stay out:

  • Load and performance testing. It needs a separate environment and a sustained load plan; a smoke run that doubles as a load test will be flaky by construction and slow by definition.
  • Edge cases. Invalid inputs and validation logic are precisely the cases TestMu AI says a smoke suite deliberately skips. If an input is invalid, a passing smoke test would never have caught it anyway.
  • Anything requiring manual steps or seeded business data. If a check needs a human to log in and click through a flow, it belongs in exploratory or acceptance testing.

The dividing line is simple: if the failure would not justify a revert at 2 a.m., leave it out.

Making failures readable enough to act on

A smoke failure should read like an incident report, not a stack trace. Harness's smoke testing guidance stresses that strong smoke suites stay small and dependable, with clear assertions and rich failure artifacts, and that they verify the deployment using real health signals where possible (Harness on smoke testing in CI/CD). The cheapest way to get there is to make every check log one line on failure, in a fixed shape.

FAIL check=login_read GET https://api.example.com/me
  status=401 expected=200
  body={"error":"token expired"}
  target=smoke-7f3a deployed=e3b0c442
FAIL check=write_create POST https://api.example.com/items
  status=500 expected=201
  body={"error":"database is read-only"}
  target=smoke-7f3a deployed=e3b0c442

Four details do most of the work:

  • The request line. Method, URL and the check name, so a reader can reproduce it in one command.
  • The observed against the expected status. The pair is the assertion, and the expected value should be printed literally rather than implied by the check's name.
  • The response body, truncated. Twenty lines is enough to see "read-only database" or "expired token."
  • The deployed identifier. The commit SHA or version tag ties the failure to an artifact and a diff.

Print that identifier into the run itself, not just the log. In GitHub Actions, the run-name key lets the workflow title include expression values from the repository context (GitHub's workflow syntax reference), which means the deploy SHA can appear in the list of runs without anyone opening a log. Pair it with a required check so a failing smoke job blocks promotion, which is what harness calls turning smoke tests from slow manual steps into deployment gates (Harness).

One more rule: fail once, fail loud, and do not bury it. A smoke failure that needs a second human interpretation is a rollback you will debate for twenty minutes.

Wiring the job into the pipeline

The mechanics are boring, which is the point. Workflow files live in .github/workflows and run when an event such as a push or a deployment fires (GitHub Actions quickstart). Place the smoke job after the deploy step in the same workflow, and scope it to deployed environments.

name: deploy-and-smoke
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: ./scripts/deploy.sh
      - run: ./scripts/smoke.sh
        env:
          SMOKE_BASE_URL: ${{ vars.SMOKE_BASE_URL }}
          SMOKE_TOKEN: ${{ secrets.SMOKE_TOKEN }}
          DEPLOYED_SHA: ${{ github.sha }}

Two operational habits matter more than the YAML:

  1. Gate on the smoke job. If the step fails, the pipeline fails and the release does not proceed. That is the whole value of a gate; a check that only warns is documentation, not control.
  2. Never point smoke at production data you cannot afford to touch. The reversible write must use a dedicated smoke account and delete what it creates, and the check should be idempotent so a retry does not double-create.

Keep credentials in secrets, not in the script, and keep the suite's own runtime predictable: a smoke suite with wild timing is unreliable and slow. If the post-deploy check has become a job you outsource, a QA automation or release-engineering consultant can rewrite it in a day or two. Quanta Intelligence can put an AI expert for a line of business onto that kind of scoped work, or hand the smoke-suite refactor to a specialist through Quanta.

FAQ

FAQ

How many checks should a smoke suite have?

Roughly 10 to 20, grouped by critical path. That is enough to cover health, auth, one read per critical service and one reversible write without turning into a regression suite. TestMu AI's guide describes suites of 20 to 50 cases as typical, but a tighter set running in one to two minutes is easier to keep green.

Should smoke tests run against staging, production, or both?

Run them against the environment you just deployed to, and treat production as the default for a continuous deployment setup. Microsoft's guide places post-deployment smoke tests and health checks in the deploy stage, after the artifact reaches its target environment. A pre-production smoke run is useful as an early gate, but it does not prove the production deploy works.

What is the difference between smoke testing and sanity testing?

Smoke goes wide and shallow across the whole build, asking whether it is testable at all. Sanity goes narrow and deep into one specific change or fix, usually after the build has already passed the smoke gate, which matches the distinction drawn by TestMu AI's comparison. Neither replaces regression testing before a release.

Why does a smoke suite that takes twenty minutes get ignored?

Because it competes with every other pipeline step for developer attention, and GitLab's CI guidance ties fast feedback to fast builds, citing Fowler's ten-minute build guideline. A slow gate gets bypassed or disabled, which turns it into decoration. Keep the post-deploy suite under two minutes and make every failure actionable, and it stays on.

How do I stop a smoke suite from becoming flaky?

Pin the target environment, set request timeouts and retries, and use a dedicated smoke account with a reversible write. Treat flaky checks as defects: fix the setup or remove the check, because a gate that fails for the wrong reasons erodes trust in every other check in the run (QA Wolf).

Sources

Sources

  1. Smoke Testing: What It Is, Examples & Checklist
  2. Smoke Testing: What It Is, When to Run It, Best Practices - Momentic AI
  3. CI/CD Pipeline Guide for Developers | Software Development Companies
  4. Modern Rollback Strategies | Octopus blog
  5. Smoke Testing: Definition, Examples & Checklist 2026
  6. Continuous integration best practices - GitLab
  7. Integrating Smoke Testing into CI/CD Pipelines
  8. Workflow syntax for GitHub Actions
  9. Quickstart for GitHub Actions
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles