Zuplo
API Gateway

API Gateway for CI/CD and GitOps: Automating API Lifecycle with Infrastructure as Code

Nate TottenNate Totten
March 18, 2026
13 min read

Learn how to manage API gateway configuration with GitOps and CI/CD. Explore gateway-as-code patterns, automated testing, and deployment strategies for modern API lifecycle management.

Your API gateway controls authentication, rate limiting, routing, and request transformation for every API call your system handles. Yet on many teams, the gateway is the one piece of infrastructure still managed through a dashboard. Routes get added by clicking through a UI. Rate limits are tweaked in a web console. Policy changes happen outside version control, outside code review, and outside your CI/CD pipeline.

This creates real problems. Configuration drifts between environments. Nobody knows who changed the rate limit on the orders endpoint last Tuesday. Rolling back a broken policy means remembering what the settings were before someone edited them in the admin panel. And onboarding a new team member requires a walkthrough of dashboard menus instead of a git clone.

GitOps and CI/CD solve these problems for application code. There is no reason your API gateway should be the exception. Whether you are a platform engineer building deployment pipelines, a DevOps team standardizing infrastructure workflows, or an API team shipping endpoints faster, this guide covers how to bring gateway-as-code, automated testing, and deployment pipelines to your API gateway — and why choosing a gateway built for this workflow matters more than bolting GitOps onto one that wasn’t.

What GitOps Means for API Gateways

GitOps applies the principles of infrastructure as code to operational workflows. For API gateways specifically, this means:

  • Gateway configuration lives in Git. Routes, policies, authentication rules, and custom logic are defined in files — JSON, YAML, TypeScript — stored in a Git repository. The repo is the single source of truth.
  • Changes go through pull requests. Adding a new route or modifying a rate limit gets the same code review treatment as any application change. A teammate reviews the diff, tests pass, and only then does the change merge.
  • Deployments are automated. Merging to the main branch triggers deployment. No one logs into a dashboard to click “publish.”
  • Rollback is a git revert. If a deployment breaks something, you revert the commit and push. The previous configuration deploys automatically.
  • Every change is auditable. Git history tells you who changed what, when they changed it, and why (through commit messages and PR descriptions).

This is the same workflow developers use for application code, Kubernetes manifests, and Terraform configurations. Bringing it to API gateways means one fewer system that operates outside your standard engineering practices.

Why API Gateways Need CI/CD

API gateways sit on the critical path of every request. A misconfigured route returns 404s to your customers. A broken authentication policy exposes protected endpoints. A rate limit set too low throttles legitimate traffic during a launch.

These are not hypothetical risks — they are the kinds of incidents that happen when gateway configuration changes are manual, unreviewed, and untested. CI/CD pipelines address each of these failure modes.

Catching Errors Before Deployment

A CI pipeline can validate your gateway configuration on every pull request. This includes linting your OpenAPI specification for structural correctness, running schema validation on route definitions, and starting a local gateway to verify that the configuration actually loads without errors. Problems surface in the PR, not in production.

Testing Against Live Preview Environments

The most powerful test for an API gateway change is sending real HTTP requests to a live instance running the new configuration. Preview environments — isolated deployments created for each pull request — let reviewers curl a new endpoint, verify that authentication policies reject unauthenticated requests, and confirm that response transformations produce the expected output.

Consistent Multi-Environment Promotion

A pipeline enforces the same deployment process for every environment. The configuration that ran in the preview environment is the same configuration that deploys to staging, and the same configuration that reaches production. No manual steps, no “I forgot to update the staging config.”

Audit Trail and Compliance

In regulated industries, you need to demonstrate who approved a change, when it was deployed, and what it contained. When your gateway configuration flows through Git commits, pull request reviews, and CI/CD pipeline runs, the audit trail is built in.

Approaches to API Gateway GitOps

Not all gateways support GitOps equally. The differences come down to whether Git is the native operating model or an integration bolted on after the fact.

Git-Native Gateways

A git-native API gateway treats the Git repository as the source of truth. There is no separate control plane database to sync. The files in your repo are the configuration, and every push triggers a build and deployment.

Zuplo is built around this model. A Zuplo project is a standard Git repository with a well-defined structure:

plaintext
my-api/
├── config/
│   ├── routes.oas.json    # Route definitions (OpenAPI format)
│   └── policies.json      # Policy configuration
├── modules/
│   └── my-handler.ts      # Custom TypeScript handlers
├── tests/
│   └── api.test.ts        # API tests
├── zuplo.jsonc            # Project metadata
└── package.json

Routes are defined in an OpenAPI-based JSON file. Policies like rate limiting and authentication attach to routes declaratively. Custom logic is TypeScript that runs on the edge. Connect the project to GitHub, and every push deploys automatically — merge to main updates production, push to a feature branch creates an isolated preview environment with its own URL.

There is no CLI sync step, no Terraform provider, and no export/import workflow. The standard Git workflow is the deployment workflow.

Declarative CLI Gateways

Gateways like Kong and Tyk offer declarative configuration through CLI tools. You define your gateway state in YAML or JSON files, store those files in Git, and use a sync command in your CI/CD pipeline to push changes to the running gateway.

Kong’s decK CLI uses a deck gateway sync command to apply YAML configuration. Tyk’s tyk-sync tool works similarly. The workflow looks like:

  1. Developer edits config files and opens a pull request
  2. CI runs deck gateway diff to show what will change
  3. After merge, CI runs deck gateway sync to apply the configuration

This works, but there is an important distinction: Git is not the deployment mechanism — it is the storage mechanism. A separate sync tool bridges the gap between what is in your repo and what is running on the gateway. If the sync fails or is skipped, configuration can drift.

Branch preview environments, automatic deployment on merge, and environment cleanup are all features you must build yourself with this approach.

Infrastructure-as-Code (IaC) Gateways

Cloud-managed gateways like AWS API Gateway and Azure API Management integrate with their respective IaC ecosystems. You define API resources in CloudFormation, CDK, Terraform, ARM templates, or Bicep files.

These tools manage gateway configuration as part of a broader infrastructure stack. The configuration lives in Git and deploys through CI/CD, but:

  • The IaC tool manages state separately from Git (e.g., CloudFormation stack state, Terraform state files)
  • Rollback is an IaC operation (stack rollback), not a simple git revert
  • Configuration is tightly coupled to cloud-provider-specific resource types
  • There are no branch preview environments for API changes

For teams already managing infrastructure with Terraform or CDK, this approach fits into the existing workflow. The tradeoff is complexity and a lack of API-gateway-specific developer experience.

Implementing Gateway-as-Code

The gateway-as-code pattern means your API gateway’s behavior is entirely defined by files in a repository. Here is what that looks like in practice with Zuplo.

Defining Routes with OpenAPI

Zuplo uses the OpenAPI specification as its native routing format. A route in config/routes.oas.json looks like this:

JSONjson
{
  "paths": {
    "/v1/orders": {
      "get": {
        "summary": "List orders",
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "urlForwardHandler",
            "module": "$import(@zuplo/runtime)",
            "options": {
              "baseUrl": "https://api.example.com"
            }
          },
          "policies": {
            "inbound": ["api-key-inbound", "rate-limit-inbound"]
          }
        }
      }
    }
  }
}

This is simultaneously your gateway routing configuration and a valid OpenAPI document. The x-zuplo-route extension attaches gateway-specific behavior — the request handler and inbound policies — while the rest of the document serves as the API specification for documentation and code generation.

Configuring Policies Declaratively

Policies like rate limiting, authentication, and request validation are configured in config/policies.json:

JSONjson
{
  "policies": [
    {
      "name": "rate-limit-inbound",
      "policyType": "rate-limit-inbound",
      "handler": {
        "export": "RateLimitInboundPolicy",
        "module": "$import(@zuplo/runtime)",
        "options": {
          "rateLimitBy": "user",
          "requestsAllowed": 100,
          "timeWindowMinutes": 1
        }
      }
    },
    {
      "name": "api-key-inbound",
      "policyType": "api-key-inbound",
      "handler": {
        "export": "ApiKeyInboundPolicy",
        "module": "$import(@zuplo/runtime)",
        "options": {
          "allowUnauthenticatedRequests": false
        }
      }
    }
  ]
}

Adding a new policy means editing a JSON file, committing, and pushing. Removing a policy is deleting a reference. Every change is diffable, reviewable, and reversible.

Writing Custom Logic in TypeScript

When declarative configuration is not enough, you write TypeScript handlers and custom policies in the modules/ directory:

TypeScripttypescript
// modules/order-validator.ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  const body = await request.json();

  if (!body.items || body.items.length === 0) {
    return new Response(
      JSON.stringify({ error: "Order must contain at least one item" }),
      { status: 400, headers: { "content-type": "application/json" } },
    );
  }

  return request;
}

This code lives in the same repository as your routes and policies. It gets the same code review, the same version control, and the same deployment pipeline.

CI/CD Pipeline Architecture for API Gateways

A well-structured API gateway pipeline follows a flow that balances speed with safety:

plaintext
Commit → PR Opens → Validate & Preview Deploy → Review & Test
                                                       |
                                                  Merge to main
                                                       |
                                              Production Deploy → Post-Deploy Tests

Each stage catches a different category of issues:

  1. Validation catches structural problems — invalid OpenAPI specs, malformed policy configuration, TypeScript compilation errors.
  2. Preview deployment catches runtime problems — routes that 404, authentication policies that reject valid tokens, transformations that drop required headers.
  3. Review catches design problems — a teammate spots that the rate limit is too aggressive or the route path does not follow your API’s naming convention.
  4. Post-deploy tests catch integration problems — the production deployment works end-to-end with real upstream services and real authentication.

Zuplo with GitHub: Built-In Pipeline

With Zuplo’s GitHub integration, much of this pipeline is built in. Connect your repository, and you get:

  • Automatic deployment on every push — no GitHub Actions workflow required for deployments
  • Branch-based preview environments — every branch gets its own isolated environment with a unique URL
  • Deployment status checks — GitHub shows whether the deployment succeeded directly on commits and PRs

You can extend this with a test workflow that runs after each deployment:

YAMLyaml
name: Test Deployment
on:
  deployment_status:

jobs:
  test:
    if: |
      github.event.deployment_status.state == 'success' &&
      github.event.deployment_status.environment_url != ''
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install
      - name: Run tests
        run: npx zuplo test --endpoint ${{
          github.event.deployment_status.environment_url }}

This workflow triggers when Zuplo finishes deploying, runs your test suite against the deployed environment, and reports the results as a GitHub check on the PR.

Custom CI/CD Pipelines

For teams that need more control — approval gates, multi-stage deployments, or pre-deployment tests — Zuplo’s CLI supports custom CI/CD pipelines with GitHub Actions, GitLab CI/CD, Azure Pipelines, Bitbucket Pipelines, and CircleCI.

A custom GitHub Actions workflow with pre-deploy testing and staged deployment looks like:

YAMLyaml
name: API Gateway CI/CD

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

env:
  ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }}

jobs:
  test-local:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install
      - name: Start local server and run tests
        run: |
          npx zuplo dev &
          sleep 10
          npx zuplo test --endpoint http://localhost:9000
          kill %1

  deploy-preview:
    needs: test-local
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install
      - name: Deploy preview
        id: deploy
        shell: bash
        run: |
          OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" 2>&1)
          echo "$OUTPUT"
          DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oP 'Deployed to \K(https://[^ ]+)')
          echo "url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
      - name: Run tests against preview
        run: npx zuplo test --endpoint "${{ steps.deploy.outputs.url }}"

  deploy-production:
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install
      - name: Deploy to production
        run: npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment production

For detailed pipeline templates including GitLab CI, multi-stage deployments with approval gates, and tag-based releases, see our CI/CD for API Gateways guide.

Testing API Gateway Configuration in CI

Testing is what makes a CI/CD pipeline trustworthy. Without tests, a pipeline just deploys changes faster — including broken changes. Here are the testing layers to include.

Configuration Validation

Validate that your gateway configuration is structurally correct before deploying anything:

Terminalbash
# Start a local Zuplo dev server to validate configuration
npx zuplo dev &
sleep 10

# Run tests against the local server
npx zuplo test --endpoint http://localhost:9000

# Stop the dev server
kill %1

If the dev server starts successfully, your configuration is structurally valid. Running tests against it catches syntax errors in your OpenAPI file, invalid policy references, and TypeScript compilation failures. Running this in CI means broken configuration never reaches a deployed environment.

API Contract Tests

The Zuplo CLI includes a built-in test runner. Test files live in the tests/ directory alongside your gateway configuration:

TypeScripttypescript
// tests/orders.test.ts
import { describe, it, TestHelper } from "@zuplo/test";
import { expect } from "chai";

describe("Orders API", () => {
  it("returns 401 without authentication", async () => {
    const response = await fetch(`${TestHelper.TEST_URL}/v1/orders`);
    expect(response.status).to.equal(401);
  });

  it("returns 200 with valid API key", async () => {
    const response = await fetch(`${TestHelper.TEST_URL}/v1/orders`, {
      headers: {
        Authorization: `Bearer ${process.env.TEST_API_KEY}`,
      },
    });
    expect(response.status).to.equal(200);
  });
});

These tests run against the deployed preview environment in CI, validating that your routes, policies, and handlers work correctly with real HTTP requests.

OpenAPI Spec Linting

If you maintain a separate upstream OpenAPI specification, lint it in CI to catch breaking changes:

Terminalbash
npx @redocly/cli lint config/routes.oas.json

This validates that your OpenAPI document is structurally correct and follows your organization’s API design standards.

Multi-Environment Management

API gateways typically need to run across development, staging, and production environments with different upstream URLs, secrets, and operational parameters.

Branch-Based Environments

Zuplo uses a branch-based deployment model: one branch equals one environment. The default branch (usually main) maps to the production environment. Every other branch gets a preview environment with its own URL.

This means environment promotion follows your Git branching strategy:

  1. Create a feature branch → preview environment spins up automatically
  2. Open a pull request → reviewers test against the preview URL
  3. Merge to main → production environment updates
  4. Delete the branch → preview environment can be cleaned up

Environment-Specific Configuration

Gateway logic stays the same across environments. What changes is operational configuration — upstream URLs, rate limit thresholds, and secrets. Zuplo handles this through environment variables scoped to each environment:

TypeScripttypescript
import { environment } from "@zuplo/runtime";

export function getUpstreamUrl(): string {
  return environment.UPSTREAM_URL;
}

Set UPSTREAM_URL to https://staging-api.example.com in the staging environment and https://api.example.com in production. The gateway configuration files remain identical — only the environment variables change.

Rollback and Recovery

When a deployment introduces a problem, rollback speed directly impacts how long your users are affected.

Git Revert as Rollback

With a git-native gateway, rollback is a standard Git operation:

Terminalbash
# Find the problematic commit
git log --oneline -5

# Revert it
git revert abc1234

# Push to trigger redeployment
git push origin main

The platform treats this like any other commit to main and deploys the reverted state. There is no special rollback procedure, no separate CLI command, and no dashboard interaction. The same deployment path used for forward changes handles rollbacks.

Since Zuplo deploys to 300+ edge locations worldwide in under 20 seconds, a git revert propagates globally almost immediately.

Redeploying a Previous Tag

For teams using tag-based releases, rolling back means deploying the previous tag:

Terminalbash
# Check out the last known-good tag
git checkout v1.2.3

# Deploy it
npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment production

Both approaches use Git as the source of truth for what “the previous good state” means, which is fundamentally simpler than remembering dashboard settings or tracking down the right IaC state file.

Best Practices for API Gateway GitOps

Require PR Reviews for All Gateway Changes

API gateway configuration affects every request to your system. Treat it with at least the same rigor as application code. Enable branch protection rules on your main branch and require at least one approving review before merging.

Automate Policy Validation

Use your CI pipeline to verify that security policies are present on routes that need them. A test that checks “every route in routes.oas.json has an authentication policy” catches accidental misconfigurations before they reach any environment.

Keep Secrets Out of Git

Never commit API keys, tokens, or connection strings to your repository. Use your CI/CD platform’s secret management (GitHub Secrets, GitLab CI Variables) to inject sensitive values at deployment time. In Zuplo, use environment variables scoped to each environment.

Use Preview Environments for Every PR

Preview environments turn API gateway reviews from “does this config look right?” into “does this actually work?” Require that tests pass against the preview environment before a PR can merge.

Monitor After Deployment

A successful CI/CD pipeline does not end at deployment. Set up monitoring and alerting for your gateway’s key metrics — error rates, latency, and throughput — so you catch issues that tests did not cover.

Start Managing Your API Gateway with GitOps

If your API gateway configuration still lives in a dashboard, start by moving it to Git. Define your routes and policies in files, commit them to a repository, and set up a CI/CD pipeline to validate and deploy changes automatically.

If you want a gateway where this workflow is native rather than bolted on, Zuplo is built for it. Your configuration lives in Git, deploys on push, creates preview environments for every branch, and rolls back with git revert. No sync tools, no IaC providers, no dashboard drift.

Sign up for Zuplo and deploy your first git-native API gateway in minutes.

Related reading: