GitOps & CI/CD on Kubernetes

Pushing YAML with kubectl apply from a laptop does not scale past one team. GitOps makes git the single source of truth: CI builds and tests artifacts; a cluster reconciler (ArgoCD or Flux) continuously applies declared state and self-heals drift. On OpenShift, certified operators ship ArgoCD and Tekton Pipelines, and BuildConfig integrates image builds with ImageStream triggers—closing the loop from commit to running pod.

developer devops architect ArgoCD 2.x Flux 2 OCP 4.x Tekton

GitOps Principles

GitOps is not a product—it is an operational model. The cluster reconciler watches a git repository (or OCI artifact registry) and continuously drives live state toward declared state. Humans change production by merging pull requests, not by running CLI commands.

flowchart LR
  DEV["Developer\nPR merge"]
  GIT["Git repo\nsource of truth"]
  CI["CI pipeline\nbuild + test"]
  REG["Container registry\nimage tag"]
  GITOPS["GitOps controller\nArgoCD / Flux"]
  K8S["Kubernetes API\netcd desired state"]
  RUN["Running workloads\npods, services, ingress"]

  DEV --> GIT
  GIT --> CI
  CI --> REG
  CI -->|"update image tag\nin git"| GIT
  GIT --> GITOPS
  GITOPS -->|"apply / prune"| K8S
  K8S --> RUN
  RUN -.->|"drift detected"| GITOPS
  GITOPS -.->|"self-heal"| K8S

Core principles

Principle What it means Anti-pattern
Declarative Desired state expressed as YAML/Helm/Kustomize in git—not imperative shell scripts kubectl set image in a deploy script
Versioned & immutable Every change is a git commit with audit trail, review, and rollback via revert Editing live manifests with no record
Automatic Controller polls or webhooks git; applies changes without manual intervention Ops ticket to "please deploy v2.3"
Self-healing Drift from git (manual edits, failed partial apply) is corrected on next sync Cluster diverges silently from documentation
Git as source of truth What is in git is what should run—not what someone last typed in a terminal Spreadsheet of "current prod versions"

No kubectl in production

Production changes flow through git merge → reconciler apply. Direct kubectl apply or oc apply from CI or laptops bypasses review, breaks audit trails, and fights the reconciler (self-heal will revert manual edits—or worse, you disable self-heal and drift accumulates).

  • Allowed: read-only debugging (kubectl get/describe/logs), emergency break-glass with documented procedure
  • CI job writes to git: bump image tag, open PR, merge after checks—never push manifests directly to API
  • Break-glass: time-limited RBAC, incident ticket, follow-up PR to restore git truth
⚠️ Pitfall

Teams adopt ArgoCD but keep kubectl apply -f in Jenkins. The controller and CI fight over ownership; rollbacks become unpredictable. Pick one delivery path: git → reconciler, period.

🔬 Under the Hood

GitOps controllers are themselves Kubernetes operators. They watch CRDs (Application, Kustomization) and custom resources, render manifests (helm template, kustomize build), then use server-side apply or strategic merge to the API server—same machinery as kubectl, but automated and auditable.

🎯 Interview Tip

"How is GitOps different from CI/CD?" — CI/CD answers how artifacts are built and tested. GitOps answers how cluster state is delivered and kept in sync. CI ends at the registry (and a git commit); GitOps starts at that commit and reconciles continuously.

ArgoCD

Argo CD is a declarative GitOps continuous delivery tool for Kubernetes. It ships as a set of controllers plus a UI and CLI, watches git repos, and manages the lifecycle of applications defined by the Application CRD.

Architecture components

  • API Server — gRPC/REST API, Web UI, RBAC enforcement, webhook receiver
  • Repository Server — clones git, generates manifests (plain YAML, Helm, Kustomize, Jsonnet, plugins)
  • Application Controller — compares live vs desired state, drives sync, tracks health
  • Redis — caching layer for generated manifests and session state
  • Dex (optional) — OIDC/LDAP/SAML SSO connector
  • ApplicationSet Controller — generates many Application resources from generators
  • Notifications Controller — Slack, email, GitHub status via Notification CRs
flowchart TB
  subgraph git["Git / Helm repos"]
    REPO["Git repository\nmanifests + values"]
  end
  subgraph argo["Argo CD namespace"]
    API["API Server\nUI + webhooks"]
    RS["Repo Server\nhelm template / kustomize"]
    AC["Application Controller\ncompare + sync"]
    AS["ApplicationSet Controller"]
    NC["Notifications Controller"]
    RD["Redis cache"]
  end
  subgraph cluster["Target cluster(s)"]
    K8S["Kubernetes API"]
    WL["Workloads"]
  end
  REPO --> RS
  API --> AC
  RS --> AC
  AC --> RD
  AS -->|"generates Application CRs"| AC
  AC -->|"apply / prune / hook"| K8S
  K8S --> WL
  AC --> NC

Application CR

An Application binds a source (git path, Helm chart, OCI) to a destination (cluster + namespace). ArgoCD continuously reconciles that binding. Store Application CRs in the argocd namespace (or allow them in app namespaces with project RBAC).

yaml — ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payments-api
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: team-payments          # AppProject RBAC scope
  source:
    repoURL: https://github.com/acme/gitops-config.git
    targetRevision: main          # branch, tag, or commit SHA
    path: apps/payments/overlays/prod
    kustomize:
      images:
        - payments-api=registry.acme.io/payments:v2.4.1
  destination:
    server: https://kubernetes.default.svc
    namespace: payments-prod
  syncPolicy:
    automated:
      prune: true                 # delete resources removed from git
      selfHeal: true              # revert manual cluster edits
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
  revisionHistoryLimit: 10

Sync policy

  • Manual sync — operator clicks Sync or runs argocd app sync; good for regulated change windows
  • Automated sync — applies on every git change; pair with prune and selfHeal carefully
  • Sync optionsCreateNamespace, ServerSideApply, ApplyOutOfSyncOnly, PruneLast
  • Sync waves & hooks — ordering via argocd.argoproj.io/sync-wave annotation; PreSync/PostSync/Skip hooks for jobs and migrations

Sync & health status

Sync status Meaning
SyncedLive state matches desired git revision
OutOfSyncDrift or pending changes to apply
UnknownCannot compare (repo access, render error)
Health status Meaning
HealthyAll resources pass built-in or custom health checks
ProgressingRollout in flight (Deployment replicas coming up)
DegradedFailed pods, crash loops, missing dependencies
MissingExpected resources absent from cluster
SuspendedApp scaled to zero or paused

App of Apps

Bootstrap pattern: a root Application points at a directory of child Application manifests. Deploying the root app creates the entire fleet. Common for platform teams managing dozens of microservices and cluster add-ons.

ApplicationSet

Generates Application CRs from generators—git directory discovery, cluster list, SCM provider (GitHub/GitLab apps), matrix, merge. Ideal for multi-cluster (same app to dev/stage/prod) and monorepo folder-per-service layouts.

  • Git generator — one Application per subdirectory under apps/*
  • Cluster generator — fan-out to registered cluster secrets
  • SCM provider — discover repos in an org automatically

Hooks & health checks

ArgoCD respects Helm-style hooks and sync-wave annotations. Use PreSync for DB migrations, PostSync for smoke tests, Sync for main resources. Custom health checks in argocd-cm ConfigMap extend Lua logic for CRDs (e.g. cert-manager Certificate, Crossplane claims).

Notifications

The notifications controller watches Application events and sends alerts on sync failed, health degraded, deployed. Configure triggers and templates in argocd-notifications-cm; subscribe per-app via annotations (notifications.argoproj.io/subscribe.on-sync-succeeded.slack: channel).

RBAC & Projects

AppProject restricts which repos, clusters, namespaces, and resource kinds an Application may target. ArgoCD RBAC (Casbin policies in argocd-rbac-cm) maps OIDC groups to roles: role:team-payments → get/sync apps in project team-payments only.

OpenShift GitOps operator

Red Hat ships ArgoCD as the OpenShift GitOps operator (OLM). Install via OperatorHub; it creates an ArgoCD instance in openshift-gitops namespace, integrates with OpenShift OAuth, and is supported on OCP subscriptions. Use ArgoCD CR to configure HA, resource limits, and repo credentials.

terminal — ArgoCD operations
$ kubectl get applications -n argocd
$ kubectl describe application payments-api -n argocd
→ Sync Status: Synced | Health: Healthy
$ argocd app diff payments-api --revision main
$ argocd app sync payments-api --prune
$ kubectl logs -n argocd deploy/argocd-application-controller -f$ oc get argocd -n openshift-gitops
$ oc get applications -n openshift-gitops
$ oc route -n openshift-gitops
→ openshift-gitops-server URL for Web UI (OAuth login)
$ oc adm policy add-cluster-role-to-user cluster-admin developer -z openshift-gitops-argocd-application-controller -n openshift-gitops
# ↑ only for break-glass cross-namespace debugging — prefer AppProject RBAC
🔴 OpenShift

Install OpenShift GitOps from redhat-operators CatalogSource. Default instance lives in openshift-gitops; cluster admins get ArgoCD admin via OAuth. For tenant isolation, create separate ArgoCD instances or strict AppProjects per team namespace.

⚙️ Config

Store repo credentials in Kubernetes Secrets referenced by repository CRs or argocd-repo-creds label selectors. For private Helm OCI registries, configure enableOCI and credential templates. Pin targetRevision to tags or SHAs in production—not floating HEAD.

🔒 Security

ArgoCD's application controller needs broad RBAC on target clusters. Scope blast radius with AppProjects (deny cluster-scoped resources, restrict namespaces), use separate ArgoCD instances per environment, and never embed long-lived cluster-admin kubeconfigs in CI.

Flux

Flux CD (v2) is a CNCF GitOps toolkit built from composable controllers. Instead of one monolithic app server, you install only the controllers you need—source, kustomize, helm, notification, image automation.

Modular controllers

  • Source Controller — watches GitRepository, HelmRepository, Bucket, OCIRepository
  • Kustomize Controller — applies Kustomization CRs (built-in Kustomize + health checks)
  • Helm Controller — manages HelmRelease CRs (install/upgrade/test/rollback)
  • Notification Controller — dispatches events to Slack, Teams, Git commit status
  • Image Reflector / Automation — scans registries, updates git with new tags

GitRepository

Declares a git source and revision to track. The source controller clones (or pulls) on interval and exposes an artifact in the status (.status.artifact) that downstream CRs reference.

yaml — Flux GitRepository + Kustomization
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: platform-config
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/acme/platform-gitops.git
  ref:
    branch: main
  secretRef:
    name: github-deploy-key
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: payments-prod
  namespace: flux-system
spec:
  interval: 5m
  targetNamespace: payments-prod
  sourceRef:
    kind: GitRepository
    name: platform-config
  path: ./apps/payments/overlays/prod
  prune: true
  wait: true                          # block until resources healthy
  timeout: 5m
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: payments-api
      namespace: payments-prod
  postBuild:
    substitute:
      cluster_env: production
  dependsOn:
    - name: cluster-addons          # ordering: addons before apps

HelmRelease

Flux-native Helm operator. Declares chart source (HelmRepository or git), values, upgrade remediation (rollback on failure), and test hooks. Replaces helm upgrade --install in CI.

yaml — Flux HelmRelease
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 10m
  targetNamespace: ingress-nginx
  chart:
    spec:
      chart: ingress-nginx
      version: "4.10.x"
      sourceRef:
        kind: HelmRepository
        name: ingress-nginx
        namespace: flux-system
  install:
    remediation:
      retries: 3
  upgrade:
    remediation:
      remediateLastFailure: true
  values:
    controller:
      replicaCount: 2

Flux vs ArgoCD

Dimension Flux ArgoCD
Architecture Modular controllers; Kubernetes-native CRs only Unified control plane + rich UI/CLI
UI Minimal (Weave GitOps / third-party); CLI + flux get First-class Web UI, app topology, diff view
Multi-tenancy CRs in tenant namespaces; RBAC on CRDs AppProject + built-in RBAC policies
Image automation Built-in ImageRepository / ImagePolicy / ImageUpdateAutomation Image Updater (Argo CD Image Updater project)
Helm HelmRelease CR with native rollback/remediation Helm source type in Application CR
OpenShift Community / self-managed install Red Hat OpenShift GitOps operator (supported)

Image Automation

Flux can close the image tag loop without custom CI scripts:

  1. ImageRepository — scans container registry on interval
  2. ImagePolicy — semver range or alphabetical filter (1.0.x)
  3. ImageUpdateAutomation — commits updated tags to git via GitRepository push credentials

Kustomize images field or Helm values markers (# {"$imagepolicy": ...}) tell the automation which fields to rewrite.

terminal — Flux status
$ flux check
$ flux get sources git -A
$ flux get kustomizations -A
→ READY=True means last reconcile succeeded
$ flux reconcile kustomization payments-prod --with-source
$ kubectl describe kustomization payments-prod -n flux-system$ flux install --namespace=flux-system
$ oc get pods -n flux-system
$ flux get helmreleases -A
⚖️ Trade-off

Choose ArgoCD when teams need a visual ops console, multi-cluster app maps, and Red Hat support on OCP. Choose Flux when you want pure CRD-driven GitOps, tighter Helm lifecycle control, built-in image automation, and a smaller operational footprint without a UI dependency.

💡 Pro Tip

Use dependsOn on Flux Kustomizations to enforce ordering: install CRDs and operators before application workloads. Equivalent to ArgoCD sync waves but declared between CRs rather than annotations.

CI/CD Pipeline Patterns

CI builds trust in artifacts; GitOps delivers them. The boundary is a git commit—not a kubectl apply step in Jenkins.

GitOps pipeline steps

  1. Developer merges to app repo — feature branch → main; triggers CI on application source code
  2. CI builds & tests — unit tests, SAST, container image build, push to registry with immutable tag (git SHA or semver)
  3. CI updates GitOps repo — bot opens PR (or commits) bumping image tag in overlay/kustomization.yaml or Helm values
  4. GitOps reconciler deploys — ArgoCD/Flux detects new commit, syncs cluster; health checks gate promotion
flowchart TB
  subgraph app["Application repo"]
    CODE["Source code\nDockerfile / tests"]
    CIP["CI pipeline\nGitHub Actions / Tekton"]
  end
  subgraph reg["Registry"]
    IMG["payments-api:abc123f"]
  end
  subgraph gitops["GitOps repo"]
    MAN["manifests/overlays/prod\nimage tag abc123f"]
    PR["Pull request\nplatform review"]
  end
  subgraph deliver["Delivery"]
    REC["ArgoCD / Flux"]
    PROD["Production cluster"]
  end
  CODE --> CIP
  CIP --> IMG
  CIP -->|"PR: bump tag"| MAN
  MAN --> PR
  PR -->|"merge"| REC
  REC --> PROD
  IMG -.->|"pulled by kubelet"| PROD

CI vs GitOps repo separation

Keep application repos (code, Dockerfile, unit tests) separate from GitOps repos (K8s manifests, environment config). Reasons:

  • Different access models — developers push code; only platform bots merge to prod gitops
  • Different promotion paths — same image tag promoted dev → stage → prod via overlay PRs
  • Blast radius — compromised app CI cannot directly mutate cluster RBAC or cluster-scoped resources
  • Audit — gitops repo history is the deployment ledger

Monorepo variant: /src and /deploy directories with path-filtered CI workflows—works for smaller teams but weakens access separation.

Never kubectl in CI — anti-pattern

bash — anti-pattern (do not do this)
# Jenkins / GitLab CI stage — IMPERATIVE DEPLOY
docker build -t payments:$GIT_SHA .
docker push registry.acme.io/payments:$GIT_SHA
kubectl set image deployment/payments-api \
  payments=registry.acme.io/payments:$GIT_SHA -n prod
kubectl rollout status deployment/payments-api -n prod
# Problems: no git audit, fights GitOps self-heal, kubeconfig in CI is cluster-admin bait
bash — GitOps-friendly CI tail
# CI ends at registry + git commit to GitOps repo
docker build -t registry.acme.io/payments:${GIT_SHA} .
docker push registry.acme.io/payments:${GIT_SHA}
yq -i '.images[0].newTag = strenv(GIT_SHA)' deploy/overlays/prod/kustomization.yaml
git commit -am "deploy payments ${GIT_SHA}"
git push origin HEAD  # or open PR via gh pr create
# ArgoCD/Flux reconciles — CI holds zero cluster credentials
⚠️ Pitfall

Storing cluster-admin kubeconfigs in CI secrets "because it's faster" centralizes catastrophic risk. If CI is compromised, attackers own every cluster that credential touches. GitOps repos need only git write access—much smaller blast radius.

📦 Real World

Mature platforms use environment branches or folders (overlays/dev, overlays/prod) and require manual approval on prod PRs. CI auto-merges to dev; prod needs platform sign-off. ArgoCD Image Updater or Flux Image Automation handles patch bumps; minor/major still go through PR review.

🎯 Interview Tip

Draw the four-step loop on a whiteboard: merge → CI → git tag bump → reconciler. Emphasize that rollback is git revert, not re-running an old pipeline with different flags.

Tekton (OpenShift Pipelines)

Tekton is a Kubernetes-native CI/CD framework: pipelines are CRDs, steps run in pods, and artifacts flow through workspaces. Red Hat packages it as the OpenShift Pipelines operator.

Core CRDs

Resource Role Analogy
Task Single unit of work (sequence of steps in one pod) Makefile target / Jenkins stage script
Pipeline Ordered DAG of Tasks with params and workspaces Jenkinsfile / GitHub Actions workflow
TaskRun Runtime instance of a Task Single stage execution
PipelineRun Runtime instance of a Pipeline Full pipeline execution
ClusterTask Cluster-scoped Task shared across namespaces Shared library step

Workspaces

Workspaces pass data between tasks: source code (PVC, emptyDir, git clone), cache volumes, Docker config secrets, and optional results. PipelineRun binds concrete volumes to Pipeline workspace declarations.

  • volumeClaimTemplate — dynamic PVC per PipelineRun (CI scratch space)
  • persistentVolumeClaim — shared cache (Maven, npm) across runs
  • secret.dockerconfigjson for registry push
yaml — Tekton PipelineRun
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: payments-build-
  namespace: team-payments
spec:
  pipelineRef:
    name: build-and-push
  taskRunTemplate:
    podTemplate:
      securityContext:
        fsGroup: 65532
  params:
    - name: git-url
      value: https://github.com/acme/payments.git
    - name: git-revision
      value: main
    - name: image
      value: image-registry.openshift-image-registry.svc:5000/team-payments/payments-api
  workspaces:
    - name: source
      volumeClaimTemplate:
        spec:
          accessModes: [ReadWriteOnce]
          resources:
            requests:
              storage: 1Gi
    - name: docker-credentials
      secret:
        secretName: push-secret
  timeouts:
    pipeline: 30m

OpenShift Pipelines operator

Install from OperatorHub (redhat-openshift-pipelines). Ships Tekton Pipelines, Triggers, Chains (SLSA attestations), and Hub integration. Console adds a Pipelines perspective; tkn CLI is the primary UX.

Tekton Hub

Catalog of community Tasks and Pipelines (git-clone, buildah, kaniko, helm-upgrade). Reference via resolver: hub.tekton.dev/… in taskRef or bundle OCI artifacts for air-gapped installs.

Triggers & EventListener

Event-driven CI: TriggerBinding extracts payload fields (git SHA, repo URL), TriggerTemplate renders PipelineRun YAML, EventListener exposes HTTP endpoint (Knative or K8s Service) for GitHub/GitLab webhooks.

terminal — Tekton / tkn
$ tkn pipeline list -n team-payments
$ tkn pipelinerun logs -f -n team-payments
$ kubectl get taskrun -n team-payments
$ tkn pipelinerun describe payments-build-x7k2p -n team-payments$ oc get tektonconfig
$ oc get pods -n openshift-pipelines
$ tkn hub install task git-clone
$ oc get eventlistener -n team-payments
→ expose Route or Ingress for webhook URL
🔴 OpenShift

Tekton pipelines on OCP commonly chain: git-clonebuildah (or s2i-build) → push to internal ImageStream → GitOps PR bot bumps tag. Use Pipeline in app namespace; platform provides ClusterTask catalog.

🔬 Under the Hood

Each Tekton step runs in a container on the TaskRun pod. Unlike Jenkins agents, there's no long-lived worker—every run is ephemeral pods, which maps cleanly to Kubernetes resource quotas and Pod Security Standards.

⚙️ Config

Set timeouts.pipeline and timeouts.tasks to prevent stuck workspaces from holding PVCs. Use finally tasks for cleanup (slack notify, git status) even when pipeline fails.

Image Build in OpenShift

OpenShift predates widespread Dockerfile CI and ships a first-class BuildConfig API. Builds run as pods, output to ImageStream tags, and can trigger Deployments automatically—tight integration for teams not yet on external registry + GitOps-only flows.

BuildConfig strategies

Strategy How it works When to use
Source (S2I) Inject source into builder image; no Dockerfile required Standard frameworks (OpenJDK, Python, Node) with approved base images
Docker Build from Dockerfile in git or binary upload Custom OS packages, multi-stage builds
Custom Your builder image receives source + previous image layers Legacy build tooling wrapped as image
yaml — BuildConfig (Docker strategy)
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
  name: payments-api
  namespace: team-payments
spec:
  source:
    type: Git
    git:
      uri: https://github.com/acme/payments.git
      ref: main
    contextDir: /services/payments-api
  strategy:
    type: Docker
    dockerStrategy:
      dockerfilePath: Dockerfile
  output:
    to:
      kind: ImageStreamTag
      name: payments-api:latest
  triggers:
    - type: ConfigChange
    - type: ImageChange
      imageChange:
        from:
          kind: ImageStreamTag
          name: ubi9-openjdk-21:latest
    - type: GitHub
      github:
        secretReference:
          name: github-webhook-secret

Buildah

OpenShift builds increasingly use Buildah (rootless OCI image build) inside build pods instead of the Docker daemon. Tekton's buildah task is the portable equivalent for pipeline-based builds on OCP and vanilla K8s (with privileged or custom SCC).

oc start-build

Manually queue a build from CLI—useful for debugging, hotfix binary uploads, or triggering from scripts. Does not replace GitOps for deploy; only produces a new image tag.

terminal — OpenShift builds
$ # BuildConfig is OpenShift-specific — use Tekton/Kaniko on vanilla K8s
$ kubectl get builds 2>/dev/null || echo "Install OpenShift or use CI pipelines"$ oc start-build payments-api --from-dir=. --follow
$ oc logs -f build/payments-api-42
$ oc get imagestream payments-api -o yaml
→ status.tags lists pushed digests
$ oc describe bc payments-api
$ oc import-image ubi9/openjdk-21:latest --confirm

ImageStream triggers

When a build pushes a new image to an ImageStreamTag, OpenShift can automatically roll out dependent workloads:

  • DeploymentConfig (legacy) — native ImageChange trigger on DeploymentConfig
  • Deployment — annotate with image.openshift.io/triggers JSON referencing ImageStreamTag
  • Build chaining — base image ImageChange trigger rebuilds downstream apps when platform base updates
yaml — Deployment ImageStream trigger annotation
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payments-api
  namespace: team-payments
  annotations:
    image.openshift.io/triggers: |
      [{"from":{"kind":"ImageStreamTag","name":"payments-api:latest"},"fieldPath":"spec.template.spec.containers[0].image"}]
spec:
  template:
    spec:
      containers:
        - name: payments-api
          image: image-registry.openshift-image-registry.svc:5000/team-payments/payments-api:latest
sequenceDiagram
  participant GH as GitHub webhook
  participant BC as BuildConfig
  participant BP as Build pod\nBuildah/S2I
  participant IS as ImageStream
  participant DEP as Deployment
  participant GIT as GitOps repo
  participant AR as ArgoCD

  GH->>BC: Git push trigger
  BC->>BP: Start build pod
  BP->>IS: Push new image tag
  IS->>DEP: ImageChange rollout (optional)
  Note over GIT,AR: Preferred production path
  BP->>GIT: CI bumps image tag in git
  GIT->>AR: Sync deployment manifest
  AR->>DEP: Reconcile desired state
⚖️ Trade-off

ImageStream triggers give fast inner-loop deploys for dev namespaces. For production, still route through GitOps so cluster state matches git. Many teams use BuildConfig + ImageStream in dev and external registry + ArgoCD in prod.

📦 Real World

Platform teams publish golden S2I builder ImageStreams (java-21, nodejs-20) in a openshift namespace. App BuildConfigs reference them with ImageChange triggers—when Red Hat ships a security patch to the builder, all dependent apps rebuild automatically.

💡 Pro Tip

Internal registry URL format: image-registry.openshift-image-registry.svc:5000/<namespace>/<imagestream>:<tag>. Use this in manifests for cluster-local pulls; external routes exist for out-of-cluster CI push.