Skip to main content

Overview

This guide demonstrates how to set up a CI/CD infrastructure in which every change pushed to the main branch of an API project is automatically tested, produces a Docker image, and is sequentially deployed to four Kubernetes environments (dev → test → UAT → prod). Each deployment step is synchronized with the corresponding API proxy on Apinizer.

Technologies and Versions Used

Pipeline Flow

In addition to this pipeline, there is also a standalone Targeted Deploy job that deploys a specific version directly to any chosen environment. This job is used for single-environment updates or rollback scenarios without running the full pipeline.

Project Structure


1. GitHub Actions — CI Pipeline

The CI pipeline is triggered on every push to the main branch. It contains two jobs: tests run first, and the build does not start until the tests pass.

Workflow File

Create the following content as .github/workflows/ci.yaml and push it to your repository.
If the Jenkins job name contains spaces, it must be URL-encoded using urllib.parse.quote(). Otherwise, the curl request will be sent incorrectly.

GitHub Secrets Configuration

Define the following secrets under the repository’s Settings > Secrets and Variables > Actions screen:

2. Kubernetes Infrastructure

Base Manifests

The k8s/base/ directory contains the Deployment and Service manifests shared by all environments. The ENVIRONMENT value is passed to the application through a separate ConfigMap in each environment.

Kustomize Overlays

Each environment has its own k8s/overlays/<env>/kustomization.yaml file. This file defines the environment-specific namespace, ConfigMap values, and the active image tag. Jenkins automatically updates the newTag value after every successful deployment.
The NodePort value for each environment is defined via a service patch:
Repeat the same structure for the test, uat, and prod overlays; use a different namespace and NodePort value for each.

3. Jenkins — Main CD Pipeline

Trigger: Invoked from GitHub Actions via a buildWithParameters call, with the VERSION_TAG parameter.

Jenkins Job Setup

1

Create a new Pipeline job

In Jenkins, select New Item > Pipeline. Enter a name that represents your CD pipeline as the job name.
2

SCM configuration

Under the Pipeline tab, select Pipeline script from SCM. Choose Git as the SCM, enter your repository URL, and specify the branch as */main. In the Script Path field, enter jenkins/Jenkinsfile.
3

Define the VERSION_TAG parameter

Check the This project is parameterized box. Add a String Parameter: Name: VERSION_TAG, leave Default Value empty.

Shared Library Configuration

This pipeline uses shared library functions (retryWithDelay, apinizerProxySync, apinizerPromoteProd, smokeTest). To register the library with Jenkins:
1

Open the Global Pipeline Libraries screen

Go to the Global Pipeline Libraries section under Manage Jenkins > System.
2

Add the library

Click the Add button. Enter the library name in the Name field (e.g., shared-lib). Set the Default version to main. Select Modern SCM as the retrieval method and point it to your project repository.
3

Mark as implicit

Enable the Load implicitly option on the library definition screen. This removes the need to add an @Library(...) directive to the Jenkinsfile; the functions become directly available in all pipeline jobs.

Jenkinsfile

Each Deploy stage performs the following three operations sequentially within a single sh block: update the image tag with kustomize → apply to the cluster → after the rollout completes, push to Git with a [skip ci] commit. The withCredentials block wraps both the kubeconfig and the Git credentials at the same time. Because cd k8s/overlays/<env> is used, git add uses a relative path.

Pipeline Stage Details

Validate Parameters: Verifies that the VERSION_TAG parameter is not empty. This safeguard is critical in case Jenkins is triggered manually (bypassing GitHub Actions) with an empty parameter. Checkout: The repository containing the Jenkinsfile is checked out. This step is mandatory so that subsequent stages can operate on the Kustomize files under k8s/overlays/. Deploy (for each environment): The withCredentials block wraps both the kubeconfig and Git credentials simultaneously. Within a single sh block, the following happens in order: kustomize edit set image updates the newTag in the overlay, kubectl apply -k . applies it to the cluster, and after the rollout completes, git add kustomization.yaml → commit → push is performed. Thanks to the [skip ci] tag, GitHub Actions is not re-triggered. Apinizer Sync (dev / test / uat): The apinizerProxySync shared library function creates or updates the proxy in Apinizer and deploys it to the corresponding environment. The retryWithDelay wrapper applies 3 attempts × 15-second wait for transient errors. Apinizer Promote Prod: For production, the proxy is not deployed from scratch. The apinizerPromoteProd function moves the configuration approved in UAT to production via Apinizer’s promotion API. Approve (before Test / UAT / Prod): At each environment transition, the Jenkins pipeline pauses; an authorized user approves through the Jenkins UI to continue. Smoke Test: After deployment, kubectl rollout status and a /health endpoint check are performed. For details, see the Shared Library — smokeTest section.

4. Jenkins — Targeted Deploy Pipeline

Used to deploy a specific version to a single environment. Preferred for hotfix or rollback scenarios without running the full CI/CD flow.

Jenkins Job Setup

1

Install the required plugin

In Jenkins, install the Active Choices plugin from Manage Jenkins > Plugins. Dynamic parameter dropdowns will not work without this plugin.
2

Create a new Pipeline job

Select New Item > Pipeline. Enter a job name such as targeted-deploy or any name you prefer.
3

SCM configuration

Select Pipeline script from SCM, use the same repository and the */main branch. Script Path: jenkins/Jenkinsfile.targeted.
4

Define the parameters

Three dynamic parameters will be added: ENVIRONMENT, VERSION_TAG, and CURRENT_VERSION. Details follow below.

Dynamic Parameters

The targeted deploy job uses three dynamic parameters. The Active Choices plugin must be installed for these parameters to be defined. ENVIRONMENT (Active Choices Parameter): A dropdown from which the user selects the target environment.
VERSION_TAG (Reactive Choice Parameter — references ENVIRONMENT): The last 25 tags are fetched from the Docker Hub API and presented to the user as a list.
CURRENT_VERSION (Reactive Choice Parameter — references ENVIRONMENT): The newTag value is read from the selected environment’s kustomization.yaml file to show the “currently deployed version” info. A timestamp query parameter is added to bypass caching.
The Groovy scripts used in the VERSION_TAG and CURRENT_VERSION parameters run outside the sandbox (sandbox: false). These scripts must be approved in Jenkins under Manage Jenkins > In-process Script Approval.

Jenkinsfile.targeted

Unlike the main pipeline, the git operations in this pipeline are performed in a separate Update Git stage and run before the cluster changes. kustomize edit and git add/commit are in separate sh blocks; git push is placed in its own withCredentials block to narrow the credential scope. Because git add runs at the workspace root, it uses the full path. The Deploy stage contains no git operations; only kubectl apply and, on failure, an automatic rollback.

Pipeline Stage Details

Validate: In addition to checking parameters, it verifies that the selected tag actually exists by sending a request to the Docker Hub API. This prevents deploying a nonexistent tag at this stage. Update Git: Two separate sh blocks run: the first updates the overlay with kustomize edit set image; the second performs git add → commit. git add uses the full path (k8s/overlays/${ENVIRONMENT}/kustomization.yaml) because it runs at the workspace root. git push is placed in a separate withCredentials block to narrow the credential scope. The Git change is made before the cluster change; this way, even if the pipeline fails, Git reflects the deployment attempt. Deploy: Only kubectl apply -k . and kubectl rollout status run; there are no git operations in this stage. If the rollout fails, kubectl rollout undo automatically reverts to the previous Kubernetes deployment. When a rollback occurs, the Apinizer sync does not run; the proxy continues to point to the previous version. Apinizer Sync: Based on the target environment, the correct port and Apinizer project name are selected dynamically, and then apinizerProxySync is called.

5. Shared Library Functions

The Jenkins shared library contains four functions under jenkins/shared-library/vars/. Because the library is configured implicitly under Global Pipeline Libraries in Jenkins, there is no need to add an @Library(...) directive to the Jenkinsfile; the functions can be used directly in all pipeline jobs.

apinizerProxySync

Creates or updates the proxy in Apinizer, then deploys it to the relevant environment. Flow:
If the proxy exists, a ZIP is obtained from the export/ endpoint before the update and archived as a Jenkins artifact. This allows a manual restore through the Apinizer UI after a failed update. During the update, the existing relativePathList value is read from the API and preserved; it is not written manually. Proxy creation payload:
Proxy update payload:
With deploy: false, the proxy is not automatically deployed to the environment. Controlled deployment is performed via the separate POST .../environments/{env}/ request at the end of the flow.
For more details, see the Create API Proxy from URL and Update API Proxy references.

apinizerPromoteProd

Moves the proxy configuration from the UAT environment to production using Apinizer’s promotion mechanism. The proxy is not deployed from scratch; it is copied over a previously defined mapping.
Promotion mappings must be defined in advance in the Apinizer UI. The mappingNames parameter references these mappings. For more information, see the Creating API Mappings reference.

smokeTest

Performs a two-step verification: first, it checks that the deployment has completed with kubectl rollout status, waits 5 seconds, and then sends up to 3 requests to the /health endpoint. A 5-second wait is applied between attempts. A response of HTTP 200 is considered successful.

retryWithDelay

Retries any closure for the specified number of attempts and wait interval. If all attempts fail, the last error is thrown and the stage is marked as failure.

6. Jenkins Credentials Configuration

Define the following credentials under Manage Jenkins > Credentials > System > Global credentials:
For more information on generating an Apinizer API token, see the Token Retrieval Methods documentation.
The kubeconfig file provides full access to the cluster. Upload it to Jenkins as a Secret File; avoid copying it into the workspace.

7. Apinizer API Endpoint Reference

Apinizer Management API endpoints used in this pipeline:
For more information on the Apinizer Management API, see the API Overview documentation.

Adapting Your Own Pipeline

1

Docker Hub and image name

Update the IMAGE_BASE variable with your own Docker Hub username and image name. Edit the IMAGE_NAME value in the CI workflow the same way.
2

Kubernetes configuration

Replace the NODE_IP value with your cluster node IP, and the NodePort values with the ports you use for each environment. Adjust the deployment and namespace names to match your own naming convention.
3

Apinizer projects

Update the PROJECT_NAME_* variables with the project names you created in Apinizer. Replace PROXY_NAME with your API proxy name.
4

Apinizer promotion mapping

For production promotion, update the mappingNames list in the apinizerPromoteProd call with the mapping names you defined in the Apinizer UI.
5

Git and credentials

Update the GIT_REPO_URL value with your repository. Define the IDs apinizer-management-url, the Apinizer token credential, kubeconfig, and github-credentials under Jenkins credentials.

Conclusion

This guide has shown how to set up a multi-environment CI/CD pipeline that combines GitHub Actions, Jenkins, and Kubernetes. Every code change is automatically tested, versioned, and deployed to sequential environments — protected by approval gates — while the API proxy in Apinizer is kept in sync.