# Bankuet: Continuous integration and deployment (CI/CD)

> When we change Bankuet's donor app, Vercel turns the repository code into a version that could go live. I built checks around that path so broken behaviour or secrets are more likely to be caught before release.

Canonical HTML: https://laurence-peberdy.com/w/reliability

Project: [Bankuet](https://bankuet.co.uk)

Project date: 4 September 2026

## At a glance

### My role

I designed and implemented Bankuet.co.uk's CI/CD pipeline end to end.

### Decision

Use overlapping checks rather than trust a successful build alone.

### Outcomes

Source and build secret scans · normal releases held until required checks pass

<a id="summary"></a>

## Context

When we change Bankuet's donor application, we change the code in GitHub. Vercel then turns that code into the version that could go live — a build.

A build can finish successfully and still be wrong. A donation page can pass the wrong fund, generated files can accidentally contain credentials or other secret values, or the deployed app can behave differently from local tests. I used a **Swiss cheese approach**: several imperfect checks overlap, so one can catch what another misses.

<a id="problem"></a>

## Problems

Every change follows the same path: we commit code to GitHub, Vercel builds a version of the app, and that version can then be promoted to the live site. Each step can succeed while the result is still broken or unsafe.

- **The app can build and still behave incorrectly.** Discovery can fail, or a food bank page can load but pass the wrong fund identifier to its donation form.
- **The files that go live are not only the files we wrote.** The build generates pages and scripts, so reviewing source code alone cannot show everything that may be published.
- **What works locally can fail after deployment.** Vercel runs the candidate in a different environment, so the exact version that could go live also needs testing.

<a id="constraints"></a>

## Constraints

The checks cannot prove safety by repeating real production activity. They have to catch problems without leaking secrets, making real donations or bookings, or taking the current site offline:

- **Our post-build secret scan cannot inspect Vercel's build.** It can inspect the controlled local/CI build; Vercel makes a separate build with its real environment.
- **Tests cannot expose the secrets they are meant to protect.** The workflow cannot put credentials, bypass secrets or donor data in URLs, permanent headers, traces or uploaded artifacts.
- **Integration checks cannot submit real donations or bookings.** They have to test the handoffs without performing the real action or making releases depend on a third party being available.
- **Testing the next version cannot take the current site offline.** The existing deployment stays public until the candidate passes both main CI and the deployment check.

<a id="hypothesis"></a>

## Working hypothesis

If we check the same change at several points — the source code, the files the build generates, browser behaviour, and the exact Vercel version that could go live — one check can catch what another misses and reduce the chance that broken software or a secret reaches production.

<a id="solution"></a>

## Decisions and delivery

The pipeline follows the path of a release. It checks the source before the build, checks what the build generates, and then tests the exact Vercel deployment before that version can replace the live site.

### Run the same checks locally and in GitHub

One command runs both on a developer machine and in GitHub Actions. It checks environment-variable documentation, scans source for secrets, applies the dependency policy, checks TypeScript and lint warnings, and runs unit, integration, and browser tests. If any check fails, the sequence stops.

The repository therefore defines the acceptance process; nobody has to reconstruct a checklist before publishing. Known exceptions have an owner and an expiry date.

### Inspect what the build generates

Secretlint scans the source before the build. After the controlled local/CI build, the pipeline also scans the text that build creates: server output, client assets, and selected deployment manifests. This can catch credentials or other secret-like text that was not obvious in the authored source.

A finding stops the check and reports the file and rule while masking the suspected secret. An inert test fixture verifies both detection and masking. Vercel builds separately with its real environment; this scan does not inspect Vercel's output.

### Check dependencies and app behaviour

The dependency check combines npm's audit with first-party Next.js advisories. High or critical findings block the change unless an accepted exception covers them. If an advisory source is unavailable or unreadable, the check fails rather than silently passing.

Vitest runs unit and integration tests. Playwright then opens a fresh production build using synthetic data and local service fixtures. It checks discovery, public routes, and the fund identifier passed to donation forms. Live donation forms and analytics stay disabled.

### Test the exact version that could go live

Vercel builds a production candidate while the previous deployment stays public. A trusted workflow confirms that the candidate belongs to the right project and commit, then tests that exact deployment. Normal promotion requires both main CI and the candidate check to pass.

Those checks cover key pages and redirects, public discovery APIs, required security headers, the Beacon donation-form and Mapbox handoffs, and Calendly discovery. Selected third-party responses are controlled so we can test Bankuet's side of each integration without submitting donations or bookings, or making releases depend on provider availability.

### Keep test access safe and retain human control

Test access is limited to the validated production candidate. The browser gains access without putting the bypass secret in a URL or permanent request header. Protected traces are disabled; the workflow uploads no credentials or donor data as review evidence.

The release policy also includes a documented human override and rollback procedure. Automated checks support, rather than replace, operational responsibility.

<a id="pipeline"></a>

## Example pipeline run

A typical change moves through five stages. CI and production smoke do different jobs: CI checks the repository and a controlled build; production smoke checks the exact Vercel deployment that could become live.

### Open a pull request

A proposed change is opened in GitHub. Nothing has changed in production yet; this creates the reviewable version of the work that the rest of the pipeline follows.

- Reviewable code change
- No production change

### CI + preview

**GitHub Actions** runs the main acceptance checks against the repository and a controlled build. **Vercel** also builds a preview so the change can be inspected before merge.

- Lint + TypeScript
- Unit, integration + browser tests
- Source secret scan
- Generated-file secret scan
- Dependency security checks
- Vercel preview

### Merge into main

Once the change is approved, it is merged into the main branch. That starts the release path, but the new version is still not public.

- Approved change
- Release workflow starts
- Current production stays live

### Main CI + production smoke

**CI runs again** on the merged change. Vercel builds the production candidate, and **production smoke** checks that exact deployed version — not the controlled CI build — before it can replace the current site.

- CI — code + types
- CI — test suite
- CI — secret scans
- CI — dependency checks
- Smoke — key pages + redirects
- Smoke — APIs + security headers
- Smoke — Beacon, Mapbox + Calendly handoffs

### Promote to production

Normal promotion happens only when main CI and the production smoke check have both passed. Vercel then makes the tested candidate live; the previous deployment remains public until that point.

This is why the checks overlap rather than duplicate one another: CI looks for problems in code and a controlled build; production smoke asks whether the exact version about to go live behaves as expected.

- Both checks passed
- Tested candidate becomes live
- Human override + rollback remain available

<a id="outcomes"></a>

## Outcomes

The pipeline makes acceptance checks repeatable and holds normal releases until the required checks pass.

<a id="bankuet-reliability-shared-checks"></a>

- Local development and GitHub share **one acceptance command**, including source and build-output secret scans.

<a id="bankuet-reliability-held-release"></a>

- A controlled production proof on **4 September 2026** recorded the previous deployment staying public while checks were pending. The candidate went live only after the required checks passed.

The layers reduce reliance on any single check. They do not prove the application is free of vulnerabilities or that a live payment will complete.

Published: 5 September 2026

Updated: 7 September 2026

[JSON source and measurement metadata](https://laurence-peberdy.com/projects/reliability.json)
