Custom CI/CD

GitHub Actions: Deploy and test

Applies to
Gatewayruntime for API, AI, and MCP gateways
Hosting
Managed EdgeManaged DedicatedSelf-HostedDiffers
1 difference by hosting option
Self-Hosted

Add --self-hosted-endpoint with your management API hostname to each zuplo deploy command so it targets your cluster. See zuplo deploy.

Run your test suite against the deployed environment to validate changes before considering them complete.

YAML.github/workflows/deploy-and-test.yaml
name: Deploy and Test on: push: branches: - main pull_request: jobs: deploy-and-test: runs-on: ubuntu-latest env: ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 - name: Install dependencies run: npm install - name: Deploy to Zuplo id: deploy shell: bash env: # head_ref is the source branch on pull_request events (where the # checkout is the pull/<number>/merge ref, not the branch); # ref_name covers push events ENVIRONMENT: ${{ github.head_ref || github.ref_name }} run: | # Capture deployment output OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "$ENVIRONMENT" 2>&1) echo "$OUTPUT" # Extract the deployment URL DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oP 'Deployed to \K(https://[^ ]+)') echo "url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT - name: Run tests run: npx zuplo test --endpoint "${{ steps.deploy.outputs.url }}"

This workflow:

  1. Deploys to Zuplo and captures the deployment URL.
  2. Runs your test suite against the live deployment.
  3. Fails the workflow if any tests fail.

The deploy step passes --environment explicitly because this workflow runs on both push and pull_request events. The expression ${{ github.head_ref || github.ref_name }} resolves to the branch name on either trigger, so every run for a branch updates the same environment. Without it, pull_request runs create a second environment named after the PR merge ref instead of your branch. For details, see PR preview environments.

Write tests

describe, it, and TestHelper come from @zuplo/test, which a Zuplo project already has through the zuplo package. Assertions use chai, which a new project does not include. Install it first:

TerminalCode
npm install --save-dev chai @types/chai

Place test files in the tests folder with the .test.ts extension:

TypeScripttests/api.test.ts
import { describe, it, TestHelper } from "@zuplo/test"; import { expect } from "chai"; describe("API", () => { it("returns 200 for health check", async () => { const response = await fetch(`${TestHelper.TEST_URL}/health`); expect(response.status).to.equal(200); }); it("requires authentication", async () => { const response = await fetch(`${TestHelper.TEST_URL}/protected`); expect(response.status).to.equal(401); }); });

TestHelper.TEST_URL is the value passed to --endpoint, so the same files run against every environment. For the full setup, see Get started with zuplo test.

Next steps

Last modified on