Skip to content

CI/CD Pipeline Migration: Jenkins, GitLab CI, and Tekton to AKS-Native Pipelines

Status: Authored 2026-04-30 Audience: DevOps engineers migrating CI/CD pipelines from on-premises tooling to AKS-integrated pipelines. Scope: Jenkins, GitLab CI, and Tekton migration to GitHub Actions and Azure DevOps, GitOps with Flux and ArgoCD, ACR Build Tasks, and deployment strategies.


1. CI/CD landscape mapping

Source tool Recommended AKS equivalent Alternative Migration effort
Jenkins (on-prem) GitHub Actions or Azure DevOps Pipelines Jenkins on AKS (lift-and-shift) M--L
GitLab CI (on-prem or SaaS) GitHub Actions or Azure DevOps Pipelines GitLab Runner on AKS M
Tekton (on OCP or K8s) Tekton on AKS (unchanged) GitHub Actions / Azure DevOps XS (if staying on Tekton)
OCP Pipelines (Tekton-based) Tekton on AKS or GitHub Actions Azure DevOps S--M
ArgoCD (GitOps) ArgoCD on AKS (unchanged) or AKS Flux extension -- XS
Flux v2 (GitOps) AKS Flux extension (managed) Self-managed Flux on AKS XS
Source-to-Image (S2I) Dockerfile + ACR Build Tasks GitHub Actions Docker build M
OCP BuildConfig ACR Build Tasks or GitHub Actions Azure DevOps build pipeline M
Docker-in-Docker builds ACR Build Tasks (no Docker daemon) Kaniko on AKS S
Spinnaker Flux / ArgoCD + GitHub Actions Azure DevOps + deployment slots M

2. Image build migration

From Source-to-Image (S2I) to Dockerfile + ACR Tasks

OpenShift S2I builds images from source code using builder images. The AKS equivalent is Dockerfile + ACR Build Tasks.

# OpenShift S2I build (before)
oc new-build python:3.11~https://github.com/org/api-server.git \
  --name=api-server

# ACR Build Tasks (after)
# Step 1: Create Dockerfile (if not exists)
cat > Dockerfile << 'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "app:app"]
EOF

# Step 2: Build and push with ACR Tasks (no Docker daemon needed)
az acr build \
  --registry csainaboxacr \
  --image team/api-server:{{.Run.ID}} \
  --file Dockerfile \
  .

# Step 3: Set up automatic builds on git push
az acr task create \
  --name build-api-server \
  --registry csainaboxacr \
  --image team/api-server:{{.Run.ID}} \
  --context https://github.com/org/api-server.git \
  --file Dockerfile \
  --git-access-token $GITHUB_PAT \
  --commit-trigger-enabled true \
  --base-image-trigger-enabled true

From Docker-in-Docker to ACR Tasks or Kaniko

Replace Docker-in-Docker (DinD) builds with ACR Tasks (preferred) or Kaniko (for in-cluster builds):

# Kaniko build (in-cluster, no Docker daemon)
apiVersion: batch/v1
kind: Job
metadata:
    name: build-api-server
spec:
    template:
        spec:
            containers:
                - name: kaniko
                  image: gcr.io/kaniko-project/executor:latest
                  args:
                      - "--dockerfile=Dockerfile"
                      - "--context=git://github.com/org/api-server.git"
                      - "--destination=csainaboxacr.azurecr.io/team/api-server:latest"
                  volumeMounts:
                      - name: docker-config
                        mountPath: /kaniko/.docker
            volumes:
                - name: docker-config
                  secret:
                      secretName: acr-credentials
            restartPolicy: Never

3. Pipeline migration: Jenkins to GitHub Actions

Jenkins pipeline (before)

// Jenkinsfile
pipeline {
    agent {
        kubernetes {
            yaml '''
            apiVersion: v1
            kind: Pod
            spec:
              containers:
              - name: docker
                image: docker:latest
                command: ['cat']
                tty: true
                volumeMounts:
                - name: docker-sock
                  mountPath: /var/run/docker.sock
              volumes:
              - name: docker-sock
                hostPath:
                  path: /var/run/docker.sock
            '''
        }
    }
    stages {
        stage('Build') {
            steps {
                container('docker') {
                    sh 'docker build -t registry.internal.gov/team/api:${BUILD_NUMBER} .'
                    sh 'docker push registry.internal.gov/team/api:${BUILD_NUMBER}'
                }
            }
        }
        stage('Deploy') {
            steps {
                sh 'kubectl set image deployment/api api=registry.internal.gov/team/api:${BUILD_NUMBER} -n production'
            }
        }
    }
}

GitHub Actions (after)

# .github/workflows/deploy.yml
name: Build and Deploy to AKS
on:
    push:
        branches: [main]

permissions:
    id-token: write # Required for OIDC
    contents: read

jobs:
    build-and-deploy:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v4

            - name: Azure Login (OIDC)
              uses: azure/login@v2
              with:
                  client-id: ${{ secrets.AZURE_CLIENT_ID }}
                  tenant-id: ${{ secrets.AZURE_TENANT_ID }}
                  subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

            - name: Build and push to ACR
              uses: azure/docker-login@v2
              with:
                  login-server: csainaboxacr.azurecr.io
            - run: |
                  docker build -t csainaboxacr.azurecr.io/team/api:${{ github.sha }} .
                  docker push csainaboxacr.azurecr.io/team/api:${{ github.sha }}

            - name: Set AKS context
              uses: azure/aks-set-context@v4
              with:
                  resource-group: rg-aks-prod
                  cluster-name: aks-prod-eastus2

            - name: Deploy to AKS
              uses: azure/k8s-deploy@v5
              with:
                  namespace: production
                  manifests: k8s/
                  images: csainaboxacr.azurecr.io/team/api:${{ github.sha }}
                  strategy: canary
                  percentage: 20

Azure DevOps Pipeline (alternative)

# azure-pipelines.yml
trigger:
    branches:
        include:
            - main

pool:
    vmImage: ubuntu-latest

variables:
    acrName: csainaboxacr
    imageName: team/api
    aksCluster: aks-prod-eastus2
    aksResourceGroup: rg-aks-prod

stages:
    - stage: Build
      jobs:
          - job: BuildAndPush
            steps:
                - task: Docker@2
                  inputs:
                      containerRegistry: acr-connection
                      repository: $(imageName)
                      command: buildAndPush
                      Dockerfile: Dockerfile
                      tags: $(Build.SourceVersion)

    - stage: Deploy
      dependsOn: Build
      jobs:
          - deployment: DeployToAKS
            environment: production
            strategy:
                runOnce:
                    deploy:
                        steps:
                            - task: KubernetesManifest@1
                              inputs:
                                  action: deploy
                                  connectionType: azureResourceManager
                                  azureSubscriptionConnection: azure-connection
                                  azureResourceGroup: $(aksResourceGroup)
                                  kubernetesCluster: $(aksCluster)
                                  namespace: production
                                  manifests: k8s/
                                  containers: $(acrName).azurecr.io/$(imageName):$(Build.SourceVersion)

4. GitOps migration

AKS provides Flux as a managed extension with first-class Azure integration.

# Install Flux extension on AKS
az k8s-configuration flux create \
  --resource-group rg-aks-prod \
  --cluster-name aks-prod-eastus2 \
  --cluster-type managedClusters \
  --name cluster-config \
  --namespace flux-system \
  --scope cluster \
  --url https://github.com/org/aks-gitops \
  --branch main \
  --kustomization name=infrastructure path=./infrastructure prune=true \
  --kustomization name=applications path=./applications prune=true dependsOn=infrastructure

Flux repository structure:

aks-gitops/
  infrastructure/
    namespaces.yaml
    network-policies.yaml
    ingress-nginx/
      kustomization.yaml
      release.yaml
    cert-manager/
      kustomization.yaml
      release.yaml
  applications/
    production/
      api-server/
        kustomization.yaml
        deployment.yaml
        service.yaml
        ingress.yaml
      worker/
        kustomization.yaml
        deployment.yaml

ArgoCD (self-managed on AKS)

ArgoCD runs on AKS identically to on-prem. No migration required for the ArgoCD deployment itself.

# Install ArgoCD on AKS
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd \
  --namespace argocd --create-namespace \
  --set server.ingress.enabled=true \
  --set server.ingress.ingressClassName=nginx \
  --set server.ingress.hosts[0]=argocd.app.gov \
  --set server.ingress.tls[0].secretName=argocd-tls \
  --set server.ingress.tls[0].hosts[0]=argocd.app.gov

Migrate ArgoCD Application resources:

# Update ArgoCD Application to target AKS
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
    name: api-server
    namespace: argocd
spec:
    project: default
    source:
        repoURL: https://github.com/org/app-manifests
        targetRevision: main
        path: apps/api-server/overlays/aks # AKS-specific overlay
    destination:
        server: https://kubernetes.default.svc # In-cluster AKS
        namespace: production
    syncPolicy:
        automated:
            prune: true
            selfHeal: true
        syncOptions:
            - CreateNamespace=true

5. Deployment strategies on AKS

Canary deployment with Flux Flagger

# Flagger Canary resource
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
    name: api-server
    namespace: production
spec:
    targetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: api-server
    service:
        port: 8080
        targetPort: 8080
    analysis:
        interval: 1m
        threshold: 5
        maxWeight: 50
        stepWeight: 10
        metrics:
            - name: request-success-rate
              thresholdRange:
                  min: 99
              interval: 1m
            - name: request-duration
              thresholdRange:
                  max: 500
              interval: 1m

Blue-green deployment with ArgoCD

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
    name: api-server
    namespace: production
spec:
    replicas: 5
    strategy:
        blueGreen:
            activeService: api-server-active
            previewService: api-server-preview
            autoPromotionEnabled: false
            prePromotionAnalysis:
                templates:
                    - templateName: smoke-tests
                args:
                    - name: service-name
                      value: api-server-preview
    selector:
        matchLabels:
            app: api-server
    template:
        metadata:
            labels:
                app: api-server
        spec:
            containers:
                - name: api
                  image: csainaboxacr.azurecr.io/team/api:v2.3.1

Rolling update (default)

apiVersion: apps/v1
kind: Deployment
metadata:
    name: api-server
spec:
    strategy:
        type: RollingUpdate
        rollingUpdate:
            maxUnavailable: 1
            maxSurge: 1
    minReadySeconds: 30

6. ACR integration patterns

Multi-stage builds with ACR Tasks

# acr-task.yaml
version: v1.1.0
steps:
    - build: -t csainaboxacr.azurecr.io/team/api:{{.Run.ID}} -t csainaboxacr.azurecr.io/team/api:latest .
    - push:
          - csainaboxacr.azurecr.io/team/api:{{.Run.ID}}
          - csainaboxacr.azurecr.io/team/api:latest
    - cmd: csainaboxacr.azurecr.io/team/api:{{.Run.ID}} python -m pytest tests/

Base image auto-update

# Trigger rebuild when base image updates
az acr task create \
  --name rebuild-on-base-update \
  --registry csainaboxacr \
  --image team/api:{{.Run.ID}} \
  --context https://github.com/org/api-server.git \
  --file Dockerfile \
  --base-image-trigger-enabled true \
  --base-image-trigger-type All \
  --git-access-token $GITHUB_PAT

Geo-replication for multi-region

# Replicate ACR across regions (for multi-region AKS)
az acr replication create \
  --registry csainaboxacr \
  --location westus2

az acr replication create \
  --registry csainaboxacr \
  --location usgovvirginia  # Azure Government

7. Secret management in CI/CD

No stored credentials. GitHub Actions uses OIDC federation with Entra ID:

# Create Entra ID app registration for GitHub Actions
az ad app create --display-name "github-actions-aks"

# Create federated credential
az ad app federated-credential create \
  --id $APP_ID \
  --parameters '{
    "name": "github-main",
    "issuer": "https://token.actions.githubusercontent.com",
    "subject": "repo:org/repo:ref:refs/heads/main",
    "audiences": ["api://AzureADTokenExchange"]
  }'

Azure DevOps with service connections

# Create service connection in Azure DevOps
# Uses Entra ID workload identity federation (no secrets)
az devops service-endpoint create \
  --name azure-aks-connection \
  --type azurerm \
  --authorization-scheme WorkloadIdentityFederation

8. CI/CD migration checklist

  • Container images pushed to ACR (not on-prem registry)
  • Build pipelines using ACR Tasks, GitHub Actions, or Azure DevOps (not Jenkins DinD)
  • OIDC authentication configured (no stored credentials)
  • GitOps configured (Flux or ArgoCD)
  • Deployment strategy chosen (rolling, canary, blue-green)
  • Base image auto-update configured (ACR Tasks)
  • Image scanning in pipeline (Defender for Containers or Trivy)
  • Integration tests running against AKS staging cluster
  • Rollback procedure tested
  • Pipeline secrets stored in Key Vault or GitHub Secrets (not in code)
  • Branch protection and approval gates configured
  • Pipeline monitoring and alerting configured

Maintainers: CSA-in-a-Box core team Last updated: 2026-04-30 Related: Workload Migration | Security Migration | Best Practices