A close-up portrait of a man with a salt-and-pepper beard wearing a white collared shirt against a textured beige background.
A close-up portrait of a man with a salt-and-pepper beard wearing a white collared shirt against a textured beige background.

The deploy pipeline

Jesse James Richard
|
Feb 11, 2026
|
7 min read
#CI/CD
#Testing

A few times a day I push code, and about twenty minutes later whatever I changed is running live, checked, and confirmed healthy, without me touching anything in between. Giant Context runs that entire crossing from one file, twenty-nine jobs, and one push goes through all of them.

0

jobs in the pipeline

0

phases

0

minutes from keyboard to live

What a bad deploy costs here

What ships here is not an internal tool. Customers' websites run on these services, publicly, indexed, with their phone numbers and their prices on them. A broken deploy is somebody's business offline while they are asleep and I am asleep.

Multitenancy makes that worse in a specific way. The services are shared, so there is no such thing as breaking one customer. A bad render path breaks every site at once.

And there is nobody else. No reviewer, no second pair of eyes on the diff, no colleague who notices the thing I stopped seeing three hours ago. The pipeline is the review step, and it is the only one that exists.

Before the push ever leaves my machine

The pipeline starts locally, with two small gates that git itself runs.

On every commit, Prettier rewrites each changed file into the house style. It costs milliseconds and it means no diff in this repository ever contains a formatting change nobody made on purpose.

On push, a second hook does more.

.husky/pre-push

# .husky/pre-push# …BRANCH=$(git rev-parse --abbrev-ref HEAD)if [ "$BRANCH" = "main" ]; then    echo "ERROR: Direct pushes to 'main' branch are not allowed."    exit 1fi# …echo "Running pre-push validation..."

Four checks run in order. The format check confirms Prettier has done its job. The typecheck runs the TypeScript compiler, which proves every function is called with the shapes it declared. The tests run. The build compiles everything into the artifacts that would actually deploy.

The order is by cost. Formatting takes a second, typecheck takes ten, the suite takes minutes, the build takes longer. A change that breaks all four fails on the first one, and I get the answer before the kettle boils rather than after a cloud runner has spent twenty minutes reaching the same conclusion.

One file, twenty-nine jobs

Everything from here on is defined in one file in the repository, .github/workflows/ci.yml, which GitHub reads and executes on its own servers every time a push lands on the dev branch, the shared branch where finished work integrates. The file's table of contents is just its job names in order:

The job names in ci.yml, in order

validate-secrets:detect-changes:test-ts-lint:test-ts-unit:test-ts-integration-api:test-ts-integration-core:test-ts-integration-apps:test-ts-rbac:test-python-logger:   # …and five more python suitesci-report:deploy-web:           # …and six more deploysweb-health:           # …and six more health checks

Twenty-nine jobs in four phases. Check that the run can proceed at all, work out what changed, prove the change is sound, ship it and confirm it came up.

1

Validate secrets

Check it can run

2

Detect changes

What was touched

3

Prove it

12 tests fan out

4

Ship + verify

Deploy, then health

1

Validate secrets

Check it can run

2

Detect changes

What was touched

3

Prove it

12 tests fan out

4

Ship + verify

Deploy, then health

Validate secrets, detect changes

Every service reads its configuration from environment variables, and those variables are the secrets: the database connection string, the cloud credentials, the model API keys. A service handed an incomplete environment does not run badly. It fails to start.

That is why the first job checks that every secret the deploy will need actually exists. Without it, a missing key surfaces at the very end, after every test has run and every container has been built and pushed, and the whole run is wasted on a condition that took two seconds to detect.

The second job detects changes. The platform is seven services, separately built and separately deployed: the console customers work in, the frontend that renders their public websites, the API, the router that steers each domain to the right site, the file service, the AI service, and the error-triage service.

Redeploying all seven for a one-line fix is not merely slow. Every service that redeploys is a service that restarts, and a restart is when a process is most likely to fail, on a bad environment variable or a migration that has not landed. This job diffs the push against what is live and decides which services the change actually touched, so a fix to the file service leaves the other six running untouched.

Twelve test jobs in parallel

Then twelve test jobs fan out at once, on parallel machines. Lint and format verification. Unit tests, the fast checks of individual functions. Three integration suites that open a live connection to a real Postgres and exercise the API the way the product does, because a test against a fake database proves nothing about the real one. An RBAC suite checking every declared permission against what the routes enforce. And six Python suites for the Python services and their shared libraries.

They run in parallel because they are independent, and running them in sequence would put the whole crossing well past an hour. A reporting job collects the twelve verdicts. Any red and nothing ships.

Ship it and verify

All green, and the deploy jobs start, one per service that detect-changes flagged. Each one builds its service into a container, an image that packages the program with everything it needs to run, and hands it to Google Cloud, where the new version replaces the old one.

Then seven health checks, one per service, and they exist because a successful deploy and a running service are different facts. The deploy step reports that the container was accepted. It does not report that the process inside it started, found its configuration, reached the database and began answering. A container that crashes on boot is a successful deploy by every measure the deploy step has.

So each health check asks its service to answer a request before the pipeline calls the push finished. If a service cannot answer, the run goes red and I know within the same twenty minutes, rather than finding out when a customer's site stops loading.

A screenshot of a software deployment pipeline visualization on a dark background.
One push, all twenty-nine jobs. Green the whole way across.

What twenty minutes buys

Twenty minutes, several times a day, and the bill grows every week as the test half does. I have fought with this file more than with any other part of the platform, and the fight is economics rather than correctness.

The obvious saving is to stop running everything on every push, since a change to the file service does not need the RBAC suite to pass. What stops me is that the moment a rule decides which tests a change needs, that rule can be wrong quietly, and a test that was skipped for a bad reason looks exactly like a test that passed. So it all runs, and the decision gets revisited every time the invoice arrives.

In exchange, no change reaches a customer's website without crossing twenty-nine jobs. A green run means the change is live and every check passed. A red run means the change stopped in the pipeline and production is still serving what it was serving before.

Moving a production stack from Azure to Google Cloud

#Architecture
#AI & Agents

Two years of production on Azure, then a platform built on Google Cloud from its first commit. The platform-specific layer takes weeks to learn and th...

Jesse James Richard

|

Feb 7, 2026
Read previous

Building something like this

I'm Jesse. I build platforms end to end, and I'm open to work. If this is the kind of engineering you need, get in touch.

Contact Jesse
Home
About
Contact
Sitemap
Privacy Policy
Terms of Service
Cookie Policy
The deploy pipeline | Jesse James Richard