CI/CD and self-service · 2025–26
Self-service integration environments for outside teams
Two partner teams needed to build against our GraphQL content API while our team kept shipping on main: the migration vendor for CNET's move to WordPress VIP, and later RetailMeNot. For the vendor, I set up a dedicated branch that auto-deployed on every push to a login-gated Cloud Run integration environment—giving them a live nonprod endpoint that scaled to zero when idle while main kept moving. When RetailMeNot onboarded onto our product catalog, I reused the same pattern while supporting their integration end to end from retailer feeds through search to the GraphQL API.
Context
The GraphQL content API serves articles, products and other content to CNET's sites. During the WordPress VIP migration, the vendor's WordPress code had to call that API, and some of what it needed was new: fields and behavior added just for the migration. Pointing the vendor at our shared nonprod environment would have tied their testing to whatever our team merged next, and holding main for them would have stalled everything else.
How it worked
- A branch for the vendor's work. Changes for the migration landed on their own branch. Every push ran a GitHub Actions workflow that started the environment build in Cloud Build, deployed the branch to its own Cloud Run service, and printed the environment's URL and GraphQL endpoint.
- Feature requests on the branch. When the vendor asked for something, I built it on the vendor branch, so they could test it in their environment before it reached main.
- Built on the team's tooling. The team already had an environment build and per-pull-request environments. I added the branch-triggered workflow on top, so a vendor endpoint reused the existing build instead of needing new infrastructure.
- Main kept shipping. The vendor branch merged back into main three times—in January, February and April 2026—as migration features were ready, while main took about 30 other merges from the team. I built the product-search work the vendor needed on Vertex AI Search, shipped it to the vendor branch, and tested it there first.
- Scale to zero. The environment runs on Cloud Run with zero minimum instances, so it ran no instances between the vendor's test runs and scaled up when they called it.
- Gated access. The environment has no public Cloud Run URL and is reachable only through the load balancer. The API itself requires a token: teammates granted the vendor nonprod Okta access so its workflow could request a token and call the environment against nonprod data only.
The pattern grew: RetailMeNot
In 2026 RetailMeNot, another brand in the company, started building an app on our product data. I supported it end to end, from the feeds through search to the GraphQL API, using the same approach: its team got a dedicated integration environment of the API behind the same nonprod login while they built.
- New feeds. With the team, I set up feeds from six new retailers, twelve feed configurations covering products and offers. I fixed what the larger runs exposed: a memory limit that broke price runs, and a rule to capture each offer's raw retailer link.
- New categories and deal data in search. The new retailers brought categories well beyond the tech catalog, such as beauty, apparel, footwear, toys and fragrance. I made deal summaries, coupon offers, list prices and other pricing fields searchable through the API.
- Merchant ID end to end. I carried a merchant ID through every layer: the search index schema and ingestion, Product Catalog Search's filters, the GraphQL API's filters and the admin UI, so callers can filter offers by merchant.
- Site support and stricter filters. I added RetailMeNot as a site with its own image service and search autocomplete, locale on product lookups, and strict validation of search filters so a malformed filter returns a validation error instead of wrong results.
- Per-PR environments and tests. These pull requests deployed their own preview environments before merge, and I added Bruno API tests and an environment for RetailMeNot. Each new endpoint deployed to its own sandbox and passed a Bruno suite before merge (how the tests work).
My part of this work is complete and live in production; RetailMeNot is still building its app on top of it.
My part: I built the early API work for the migration, including the new API pattern the vendor integrated against. I then set up the vendor branch and the workflow that deploys it to its own Cloud Run integration environment, built the product-search API work and the feature requests the vendor needed, and merged the branch back into main as features were ready. For RetailMeNot I built the search and API work above and set up their dedicated environment; teammates owned the project and its requirements, and handled image ingestion. The team's existing environment build and per-pull-request environments were a teammate's, and during the integration period teammates added migration-specific fields and set up the vendor's token access.
What I carry forward
Give an outside team a real endpoint instead of a mock or a turn on a shared environment. A branch that deploys itself costs little to set up, scale to zero keeps it idle between uses, and putting it behind the company login means access is granted and revoked through the same Okta login as everything else.