Skip to main content
This lesson demonstrates a GitOps-style CI/CD flow: a Jenkins pipeline builds and publishes a container image, updates Kubernetes manifests stored in Git, and Argo CD applies those changes to the cluster. The example uses a simple Node.js app (highway-animation), a manifest repository tracked by Argo CD, and a self-hosted Gitea instance for repository hosting and PR automation.

Manifest: Deployment example

Below is the Deployment manifest used in this demo. It deploys 10 replicas of the highway-animation app and exposes the app container on port 3000. Argo CD will track this manifest in the manifest repository.

Create an Argo CD Application

Create an Argo CD Application that points to the manifest repository for the Jenkins demo. In the UI choose a name such as jenkins-demo, set the sync policy to manual initially (so you can control when changes are applied), and enable auto-creation of the target namespace.
The image shows an Argo CD application creation interface with settings for a "jenkins-demo" application. Options for sync policy, sync options, and project details are visible.
After creating the application the status will show as OutOfSync until you manually sync. Once you sync, Argo CD creates the jenkins-demo namespace and deploys the resources. In this demo the Deployment creates 10 Pods.
The image displays the Argo CD dashboard showing the status of a "jenkins-demo" application, with all components marked as healthy and synced. The graphical interface includes a flowchart of services and deployments with green health indicators.
Once deployed you can access the app (in the demo it was exposed on a NodePort) and confirm the UI shows 10 blue vehicles.

Developer workflow with GitOps

With GitOps the developer makes changes to application source, builds a new image, pushes it to a registry, and updates the image tag in the manifest repository. Argo CD monitors the manifest repo and applies changes once they land on the tracked branch (e.g., main). For this demo we migrated the app source from GitHub to a self-hosted Gitea instance and made small UI/server fixes. The migration UI looks like this:
The image shows a web interface for migrating a GitHub repository, with fields for URL input, access token, migration options, and repository details such as owner and visibility settings.
The highway-animation repository contains:
  • public/index.html (UI)
  • frontend JS files (ui.js, app.js)
  • package.json
  • Dockerfile
  • Jenkinsfile (CI/CD)
When browsing the manifest repository to update deployment.yaml you will see a structure similar to the image below:
The image shows a GitHub repository interface with various directories and files listed, such as "jenkins-demo" and "README.md," all presented under the branch "main" with commit details on updates made.

Application fixes and snippets

Representative fixes made while preparing the demo:
  • Ensure server listens on port 3000 (was 3001).
  • Serve static assets from public/.
  • Add a /health endpoint and a /api/pod-info endpoint that reads POD_COUNT from environment variables.
server.js (corrected):
index.html (style snippet):
ui.js — example snippets
  • Adjust vehicle color selection to use a red palette:
  • Example daytime sun drawing in the background:

Jenkins pipeline (full CI/CD automation)

After making source changes you build a new Docker image and push it to a registry. The Jenkins pipeline automates:
  • Build and tag the image (using BUILD_ID and Git commit).
  • Push the image to the registry using stored credentials.
  • Clone or pull the manifest repository.
  • Update the deployment.yaml with the new image tag (using sed).
  • Commit and push a feature branch and open a PR via the Gitea API.
Below is a cleaned-up, corrected Jenkins declarative pipeline that demonstrates the full flow.
Never hard-code tokens or registry credentials in pipelines or repository files. Use Jenkins Credentials (or a secret manager) and reference them via credentials() in the pipeline.
Store tokens and registry credentials in Jenkins Credentials (or another secret manager) and reference them via credentials() in the pipeline. Avoid hard-coding secrets in scripts or repository files.

Gitea: creating tokens and using the API

If you need to create a personal access token in Gitea (so Jenkins can push/raise PRs), go to your Gitea user settings → Applications and generate a token with repository write permissions. The token is shown only once — copy it to a secure secret store.
The image shows the Gitea user settings interface, specifically the applications section where access tokens and OAuth2 applications can be managed. A new token has been generated and is displayed with a note to copy it as it won't be shown again.
Gitea exposes a REST API (Swagger) that documents payloads for operations such as creating pull requests. You can review the API and try sample requests: https://docs.gitea.io/en-us/swagger/
The image shows a webpage displaying a list of API endpoints related to a repository, with various HTTP methods like GET, POST, and DELETE highlighted in different colors.
Example shell script to create a PR (invoked by the Jenkins pipeline). This script relies on the GITEA_TOKEN environment variable and assumes the repo owner/name and branch names are set appropriately: gitea-pr.sh (example):

Pipeline stages at a glance

StagePurposeExample action
Unit TestsVerify application logicRun npm test or equivalent
Build ImageBuild and tag containerdocker build -t highway-animation:...
Push ImagePush image to registrydocker push <repo>/highway-animation:...
Update ManifestUpdate deployment image in Gitsed replace in deployment.yaml
Commit & PRPush branch and open PR for reviewUse Gitea API to create PR
Argo CD syncApply manifest changes to clusterArgo CD observes main and applies

End-to-end flow (summary)

  1. Developer updates application source (UI or server).
  2. Jenkins builds, tags, and pushes a new image to the registry.
  3. Jenkins clones/pulls the manifest repo, updates deployment.yaml with the new image tag, commits, and pushes a feature branch.
  4. Jenkins opens a PR in Gitea for the manifest change.
  5. A reviewer inspects and merges the PR; once merged, Argo CD detects the change on main and applies it to the cluster (or Argo CD can be configured for automated sync).
For more background reading: Later we will create the Jenkins job, run the pipeline, and observe the full flow end-to-end — from source change to Argo CD applying the updated manifest.

Watch Video