CI/CD

Continuous integration and delivery — pipelines, platforms, and GitOps workflows

01

Overview

CI/CD stands for Continuous Integration, Continuous Delivery, and Continuous Deployment. It is the backbone of modern DevOps — automating the build, test, and release process so that code changes flow from a developer's machine to production reliably and repeatably. A CI/CD pipeline is a series of automated stages that code passes through: build, test, package, deploy.

Concept Continuous Integration

Developers merge code into a shared branch frequently. Each merge triggers an automated build and test suite. The goal: catch integration bugs early, keep the main branch always in a releasable state. CI is about building and testing.

Concept Continuous Delivery

Extends CI by ensuring that code is always in a deployable state. After passing all tests, the artifact is staged for release. A human still clicks "deploy" to production. Delivery means ready to ship at any time.

Concept Continuous Deployment

Goes one step further — every change that passes all stages is automatically deployed to production with no human gate. Requires high confidence in test coverage. Deployment means fully automated release.

Core Pipeline Concepts

Stages group related work (build, test, deploy). Jobs are individual units of work within a stage. Steps are commands within a job. Triggers start the pipeline (push, PR, schedule, manual). Artifacts are outputs passed between stages. Caching speeds up repeated builds.

Integrated Integrated Platforms

Some CI/CD tools are tightly coupled to their Git platform: GitHub Actions only works with GitHub, GitLab CI only works with GitLab, Gitea Actions only works with Gitea. These offer deep integration — status checks, merge gating, environment tracking — but lock you to one platform.

Agnostic Agnostic Platforms

Other tools are platform-agnostic: Jenkins, CircleCI, Azure Pipelines, ArgoCD. They connect to multiple Git providers via webhooks or integrations. More flexible, but require more setup and lack the seamless UX of integrated tools.

Key insight

Most production environments use multiple CI/CD tools together. A common pattern: GitHub Actions or GitLab CI for build/test (CI), then ArgoCD or Octopus Deploy for deployment (CD). CI and CD don't have to be the same tool.

02

GitHub Actions

GitHub Actions is GitHub's native CI/CD platform. Workflows are defined in YAML files under .github/workflows/. It is tightly integrated with GitHub — pull request checks, deployments, issue automation, and more — and only works with GitHub repositories.

Core concepts

Trigger Events

Workflows are triggered by events: push, pull_request, schedule (cron), workflow_dispatch (manual), release, workflow_call (reusable). You can filter by branches, paths, and tags.

Structure Jobs & Steps

A workflow contains jobs that run in parallel by default. Each job runs on a runner and contains steps. Steps can run shell commands (run:) or use pre-built actions (uses:).

Feature Matrix Strategies

Run the same job across multiple configurations (OS, language version, etc.) using strategy.matrix. GitHub expands the matrix into parallel jobs automatically. Great for testing across Node 20/22/24, Python 3.11/3.12/3.13, etc.

Feature Secrets & Variables

Secrets are encrypted and available as environment variables. Scoped at repository, environment, or organization level. GITHUB_TOKEN is auto-generated per workflow run with scoped permissions. Use OIDC for cloud auth without stored secrets.

Feature Reusable Workflows

Define workflows that can be called from other workflows using workflow_call. Centralizes common CI logic (build, lint, test) in a single repository. Inputs and secrets are passed explicitly.

Feature Environments

Define deployment environments (staging, production) with protection rules: required reviewers, wait timers, branch restrictions. The Actions UI shows deployment history per environment.

Sample workflow

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

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

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [20, 22, 24]
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: npm
      - run: npm ci
      - run: npm test

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
      - uses: aws-actions/configure-aws-credentials@v5
        with:
          role-to-assume: arn:aws:iam::123456789:role/deploy
          aws-region: us-east-1
      - run: aws s3 sync dist/ s3://my-bucket/
Best practice

Always pin actions to a full commit SHA (uses: actions/checkout@abc123def) rather than a mutable tag for supply-chain security. Use permissions: at the workflow level to restrict GITHUB_TOKEN to the minimum required scopes. Prefer OIDC federation over storing cloud API keys as secrets.

03

GitLab CI/CD

GitLab CI/CD is deeply integrated into GitLab and only works with GitLab repositories. Pipelines are defined in .gitlab-ci.yml at the repository root. GitLab CI is one of the most feature-complete CI/CD systems available — it handles everything from linting to production deployments within a single platform.

Core structure

Structure Stages & Jobs

Define stages in order (stages: [build, test, deploy]). Jobs belong to a stage. All jobs in a stage run in parallel by default. Use needs: to create a DAG that skips stage ordering for faster pipelines.

Feature Rules & Conditions

The rules: keyword controls when jobs run: if: (conditions on variables), changes: (file path globs), exists: (file presence). Replaces the legacy only:/except: system. Rules are evaluated top-to-bottom, first match wins.

Feature Variables

Variables at project, group, or instance level. Can be masked (hidden in logs), protected (only available on protected branches/tags), or file type (written to a temp file, path in the variable). Also available per-job in YAML.

Feature Environments & Review Apps

Jobs declare an environment: to track deployments. GitLab shows deployment history per environment. Review apps spin up a temporary environment per merge request for live previewing. Auto-stopped when the MR is merged or closed.

Pipeline keyword reference

KeywordPurpose
stagesDefine pipeline stage order
needsDAG dependencies (skip stage ordering)
rulesConditional job execution
variablesDefine CI/CD variables
artifactsFiles passed between jobs/stages
cacheReuse files across pipeline runs
servicesSpin up Docker containers alongside the job (databases, etc.)
triggerStart child or multi-project pipelines
includeImport external YAML files (templates, shared configs)
environmentLink job to a deployment environment
resource_groupLimit concurrent deployments

GitLab Runners

GitLab CI executes jobs on Runners. Runners can be shared (available to all projects), group (available to a group), or project-specific. Executors include Docker, Shell, Kubernetes, Instance, Docker Autoscaler, and VirtualBox. On GitLab.com, shared runners use Google Cloud VMs. The legacy Docker Machine executor is deprecated (GitLab 17.5) and scheduled for removal in GitLab 20.0 — use the Docker Autoscaler executor with the Fleeting plugin architecture instead.

Advanced pipelines

  • Parent-child pipelines — a parent pipeline triggers child pipelines defined in separate YAML files. Each child is an independent pipeline with its own stages. Great for monorepos.
  • Multi-project pipelines — trigger pipelines in other GitLab projects using trigger: project: other/repo. Used for cross-repository deployment orchestration.
  • Auto DevOps — GitLab auto-detects the language and applies a default pipeline (build, test, code quality, SAST, deploy to K8s). Zero-config CI/CD for simple projects.

Sample .gitlab-ci.yml

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

variables:
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

build:
  stage: build
  image: docker:27
  services:
    - docker:27-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

test:unit:
  stage: test
  image: node:22
  needs: ["build"]
  script:
    - npm ci
    - npm run test:unit
  artifacts:
    reports:
      junit: junit.xml

test:integration:
  stage: test
  image: node:22
  needs: ["build"]
  services:
    - postgres:16
  variables:
    POSTGRES_DB: testdb
    POSTGRES_PASSWORD: testpass
  script:
    - npm ci
    - npm run test:integration

deploy:staging:
  stage: deploy
  needs: ["test:unit", "test:integration"]
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - kubectl set image deployment/app app=$DOCKER_IMAGE
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

deploy:production:
  stage: deploy
  needs: ["deploy:staging"]
  environment:
    name: production
    url: https://example.com
  script:
    - kubectl set image deployment/app app=$DOCKER_IMAGE
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
Platform lock-in

GitLab CI is exclusive to GitLab. You cannot use .gitlab-ci.yml with GitHub, Bitbucket, or Gitea repos. If you're evaluating CI/CD and might change Git platforms later, consider an agnostic tool like Jenkins or CircleCI alongside GitLab CI.

04

Jenkins

Jenkins is the most widely deployed CI/CD server in the world. It is fully self-hosted, platform-agnostic, and extensible via thousands of plugins. Jenkins can be triggered by webhooks from GitHub, GitLab, Bitbucket, Gitea, or virtually any Git provider. This flexibility comes at the cost of significant maintenance overhead — you manage the server, plugins, security patches, and scaling yourself.

Pipeline types

Pipeline Declarative

Structured Groovy DSL with a fixed syntax. Easier to read and maintain. Has built-in stages, steps, post actions, and when conditions. The recommended approach for most teams.

Pipeline Scripted

Full Groovy programming with node { ... } blocks. More flexible but harder to maintain. You can mix scripted blocks inside declarative using script { }. Used for complex logic that declarative can't express.

Feature Plugins Ecosystem

Over 1,800 plugins: Git, Docker, Kubernetes, Slack, Vault, LDAP, SAML, artifact managers, cloud providers. Plugins are the core of Jenkins' power — and its Achilles' heel (version conflicts, security vulnerabilities, abandoned plugins).

Feature Agents & Nodes

Jenkins runs jobs on agents (also called nodes). Agents can be permanent (always-on VMs) or ephemeral (Docker containers, Kubernetes pods spun up per build). The controller node orchestrates; agents execute. Label-based assignment routes jobs to the right agent.

Key features

  • Credentials store — built-in encrypted secret storage. Supports username/password, SSH keys, secret text, certificates, and cloud credentials. Scoped to global, folder, or pipeline level.
  • Shared libraries — reusable Groovy code stored in a Git repo and loaded into pipelines with @Library('my-lib'). Centralizes common build logic across many Jenkinsfiles.
  • Blue Ocean UI — a pipeline visualization interface that shows stage-by-stage progress, parallel branches, and log output. Note: Blue Ocean is in maintenance-only mode and will not receive new features. Consider the Pipeline Graph View plugin as a lighter alternative.
  • Jenkins X — a Kubernetes-native flavor of Jenkins designed for cloud-native CI/CD. Uses Tekton pipelines under the hood, with GitOps-based promotion between environments. Jenkins X has a steep learning curve and relatively low adoption compared to ArgoCD/FluxCD for the GitOps use case.

Sample Jenkinsfile (Declarative)

// Jenkinsfile
pipeline {
    agent { docker { image 'node:22' } }

    environment {
        REGISTRY = credentials('docker-registry')
        IMAGE    = "myapp:${env.BUILD_NUMBER}"
    }

    stages {
        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }
        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'npm run test:unit'
                    }
                    post {
                        always {
                            junit 'test-results/unit/*.xml'
                        }
                    }
                }
                stage('Lint') {
                    steps {
                        sh 'npm run lint'
                    }
                }
            }
        }
        stage('Build') {
            steps {
                sh "docker build -t ${IMAGE} ."
                sh "docker push ${IMAGE}"
            }
        }
        stage('Deploy') {
            when {
                branch 'main'
            }
            input {
                message 'Deploy to production?'
                ok 'Deploy'
            }
            steps {
                sh "kubectl set image deployment/app app=${IMAGE}"
            }
        }
    }

    post {
        failure {
            slackSend channel: '#ci', message: "Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
        }
        success {
            slackSend channel: '#ci', message: "Build passed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
        }
    }
}
Maintenance warning

Jenkins is the most flexible CI/CD tool but also the most maintenance-heavy. You are responsible for: server provisioning, plugin updates (breaking changes are common), security patching (Jenkins is a frequent CVE target), backup/restore, scaling agents, and Groovy troubleshooting. Budget significant ops time for Jenkins management.

05

CircleCI

CircleCI is a cloud-native CI/CD platform that works with GitHub, GitLab, and Bitbucket. Pipelines are defined in .circleci/config.yml. CircleCI is known for fast builds, powerful caching, and orbs — reusable configuration packages that simplify common tasks.

Core concepts

Structure Jobs & Workflows

Jobs define what to run. Workflows define the order and dependencies between jobs (sequential, parallel, fan-in/fan-out). Workflows support conditional logic with when/unless and branch/tag filters.

Feature Orbs

Reusable packages of jobs, commands, and executors. CircleCI maintains official orbs (aws, docker, node, slack) and the community contributes thousands more. Import with orbs: node: circleci/node@7.2.

Feature Executors

Run jobs on Docker (fast, layered caching), machine (full Linux VM, including Arm), macOS (Xcode builds), or Windows. Each executor type has different performance characteristics and available tools.

Feature Contexts & Parallelism

Contexts are named groups of secrets shared across projects. Parallelism splits a single job into N containers and distributes tests using circleci tests split for faster test suites.

Sample config.yml

# .circleci/config.yml
version: 2.1

orbs:
  node: circleci/node@7.2
  docker: circleci/docker@2.6

executors:
  default:
    docker:
      - image: cimg/node:22.14

jobs:
  test:
    executor: default
    parallelism: 4
    steps:
      - checkout
      - node/install-packages
      - run:
          name: Run tests
          command: |
            TESTS=$(circleci tests glob "tests/**/*.test.js" | circleci tests split)
            npx jest $TESTS

  build-and-push:
    executor: default
    steps:
      - checkout
      - setup_remote_docker:
          version: default
      - docker/build:
          image: myapp
          tag: $CIRCLE_SHA1
      - docker/push:
          image: myapp
          tag: $CIRCLE_SHA1

workflows:
  ci:
    jobs:
      - test
      - build-and-push:
          requires: [test]
          context: docker-credentials
          filters:
            branches:
              only: main
Note

CircleCI has its own dedicated topic page with deeper coverage of advanced features, security contexts, runner management, and optimization strategies. This section covers the essentials for CI/CD comparison purposes.

06

Azure DevOps

Azure Pipelines is the CI/CD component of Azure DevOps. It supports YAML-based pipelines (azure-pipelines.yml) and works with Azure Repos, GitHub, and Bitbucket. Deeply integrated with the Azure ecosystem — Key Vault, Container Registry, App Services, AKS — but also works well for non-Azure deployments.

Core concepts

Structure Stages, Jobs, Steps

Pipelines are composed of stages (logical groups), jobs (units of work), and steps (individual tasks). Steps can be script: (shell commands), task: (built-in tasks), or template: (reusable YAML). Templates enable DRY pipeline definitions.

Feature Variable Groups & Key Vault

Variable groups store sets of variables shared across pipelines. Can be linked to Azure Key Vault to pull secrets directly — secrets stay in Key Vault and are fetched at runtime. Variables can be scoped to specific stages or jobs.

Feature Service Connections

Named connections to external services (Azure subscriptions, Docker registries, Kubernetes clusters, AWS, GCP). Service connections handle authentication so pipelines don't need raw credentials. Support workload identity federation for Azure.

Feature Environments & Approvals

Define deployment environments with approval gates, exclusive locks, and deployment history. Microsoft-hosted agents are available (Ubuntu, Windows, macOS) or run self-hosted agents on your infrastructure.

Classic vs YAML pipelines

Azure DevOps has two pipeline systems: Classic (GUI-based, release pipelines with drag-and-drop stages) and YAML (code-as-config, version-controlled). Microsoft recommends YAML for new pipelines. Classic pipelines are not formally deprecated but are in maintenance mode — all new pipeline features and security improvements are YAML-only. Classic pipelines also lack features like templates and multi-repo triggers.

Sample azure-pipelines.yml

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

pool:
  vmImage: ubuntu-latest

variables:
  - group: production-secrets
  - name: imageTag
    value: $(Build.BuildId)

stages:
  - stage: Build
    jobs:
      - job: BuildApp
        steps:
          - task: NodeTool@0
            inputs:
              versionSpec: '22.x'
          - script: |
              npm ci
              npm run build
              npm test
            displayName: 'Install, build, test'
          - task: Docker@2
            inputs:
              command: buildAndPush
              repository: myapp
              containerRegistry: acr-connection
              tags: $(imageTag)

  - stage: Deploy
    dependsOn: Build
    condition: succeeded()
    jobs:
      - deployment: DeployProd
        environment: production
        strategy:
          runOnce:
            deploy:
              steps:
                - task: KubernetesManifest@1
                  inputs:
                    action: deploy
                    kubernetesServiceConnection: aks-connection
                    manifests: k8s/*.yml
                    containers: myapp:$(imageTag)
Recommendation

Azure Pipelines shines in Microsoft/Azure-heavy environments. Use service connections with workload identity federation instead of storing Azure credentials. Use template references (template: steps/build.yml@templates) for shared pipeline logic across repositories.

07

Gitea CI

Gitea Actions is Gitea's built-in CI/CD system, intentionally designed to be compatible with GitHub Actions. Workflow files go in .gitea/workflows/ (also supports .github/workflows/ for migration ease). It uses the same YAML syntax, supports many GitHub Actions marketplace actions, and runs on Act Runner (self-hosted).

Key details

Compat GitHub Actions Syntax

Gitea Actions uses the same YAML structure as GitHub Actions: on:, jobs:, steps:, uses:, run:. Most GitHub Actions from the marketplace work directly. Some GitHub-specific features (like GITHUB_TOKEN scopes, environments with protection rules) have partial or no support.

Runner Act Runner

Gitea uses Act Runner, a self-hosted runner based on the act project. Runs workflows in Docker containers or directly on the host. You must deploy and manage your own runners — there is no cloud-hosted option.

Feature Variables & Secrets

Secrets are configured per-repository or per-organization in the Gitea web UI. Available as environment variables in workflows. Variable management mirrors GitHub's approach but is less feature-rich (no environment-scoped secrets yet in most Gitea versions).

Why Self-Hosted Alternative

Gitea Actions matters because it provides a fully self-hosted, GitHub Actions-compatible CI/CD system. Teams that want to run their own Git platform (for compliance, sovereignty, or cost reasons) get GitHub-like CI/CD without GitHub dependency.

Differences from GitHub Actions

  • No cloud-hosted runners — you must self-host Act Runner instances
  • Limited marketplace — most actions work but some GitHub-specific ones (like github-script) need alternatives
  • No environment protection rules — deployment environments with required reviewers are not available in most Gitea versions
  • No OIDC provider (yet) — Gitea does not yet issue OIDC tokens for workload identity federation with cloud providers (you must use stored secrets). OIDC token support is under active development.
  • Workflow path — prefers .gitea/workflows/ but also reads .github/workflows/
Migration tip

If migrating from GitHub to Gitea, your existing .github/workflows/ files will mostly work as-is. Test thoroughly — actions that rely on GitHub's API (github.event payloads, GITHUB_TOKEN permissions model, status checks API) may need adjustments.

08

GitOps & K8s-Native

GitOps is a different paradigm from traditional CI/CD. Instead of pipelines pushing changes to infrastructure, GitOps uses Git as the single source of truth and operators running inside the cluster that continuously reconcile the desired state (in Git) with the actual state (in the cluster). This is the pull model vs the traditional push model.

ArgoCD

ArgoCD is the most popular GitOps tool for Kubernetes. It watches a Git repository containing Kubernetes manifests (plain YAML, Helm charts, or Kustomize overlays) and automatically syncs the cluster to match.

Core Application CRD

An ArgoCD Application defines: the source (Git repo, path, revision), the destination (K8s cluster, namespace), and the sync policy (manual or automatic). ArgoCD compares the desired state in Git against the live state in the cluster.

Feature Sync Policies

Auto-sync: automatically apply changes when Git changes. Self-heal: revert manual cluster changes back to Git state. Prune: delete resources removed from Git. Combined, these ensure the cluster always matches Git.

Pattern App-of-Apps

A single ArgoCD Application that manages other Applications. A parent app points to a directory of Application manifests. Adding a new app is just adding a YAML file to Git. Scales to hundreds of applications.

Feature ApplicationSet

Generates multiple Applications from templates using generators: Git directory generator, cluster generator, matrix generator, pull request generator. Essential for multi-cluster and multi-tenant deployments.

ArgoCD GitOps Flow: Developer --push--> Git Repo (manifests/Helm/Kustomize) | ArgoCD watches | v ArgoCD compares desired (Git) vs actual (K8s cluster) | +------+------+ | | In Sync Out of Sync (no-op) | Auto-sync or manual sync | v kubectl apply (reconcile) | v Kubernetes Cluster (actual == desired)

FluxCD

FluxCD is the CNCF-graduated GitOps toolkit. It takes a more modular, composable approach than ArgoCD — each concern is handled by a separate controller.

  • GitRepository CRD — watches a Git repo and makes it available to other controllers
  • Kustomization CRD — reconciles Kustomize overlays or plain manifests from a GitRepository
  • HelmRelease CRD — manages Helm chart releases declaratively
  • Notification controller — sends alerts to Slack, Teams, webhooks when reconciliation events occur
  • Image automation — watches container registries and auto-updates image tags in Git

FluxCD is fully CLI-driven (no web UI out of the box) and designed for teams that prefer a Unix-philosophy of small, composable tools.

Octopus Deploy

Octopus Deploy is a deployment automation platform that sits downstream of your CI tool. It is not strictly GitOps but handles deployment orchestration — the CD in CI/CD. CI tools (Jenkins, GitHub Actions, GitLab CI) build and test; Octopus handles the release and deployment.

  • Tenanted deployments — deploy the same application differently per tenant (customer, region)
  • Runbooks — operational tasks (database migrations, cert renewals) managed alongside deployments
  • Deployment targets — Kubernetes, Azure, AWS, SSH, Windows servers, offline drops
  • Variable scoping — variables scoped by environment, tenant, role, channel, and deployment step
  • Lifecycles — define promotion paths (Dev → Staging → Production) with approval gates

Pull vs Push model

AspectPush (Traditional CI/CD)Pull (GitOps)
Who deploys?CI pipeline pushes to clusterCluster agent pulls from Git
CredentialsCI needs cluster credentialsAgent runs inside cluster (no external creds)
Drift detectionNone (deploy-and-forget)Continuous reconciliation
RollbackRe-run pipeline or manualGit revert (automatic re-sync)
ToolsJenkins, GitHub Actions, GitLab CIArgoCD, FluxCD
Common pattern

Use a CI tool (GitHub Actions, GitLab CI, Jenkins) for build and test, then ArgoCD or FluxCD for deployment. The CI pipeline builds a container image, pushes it to a registry, and updates a Git repo with the new image tag. ArgoCD detects the Git change and deploys it. This separates CI from CD cleanly.

09

Secrets & Vault Integration

Every CI/CD platform handles secrets differently, but the principle is the same: never hardcode credentials in your pipeline files. Secrets should be injected at runtime from a secure store, masked in logs, and rotated regularly.

Native secrets by platform

PlatformSecret StorageScopingMasking
GitHub ActionsGitHub SecretsRepo, Environment, OrgAutomatic
GitLab CICI/CD VariablesProject, Group, InstanceOpt-in (masked flag)
JenkinsCredentials StoreGlobal, Folder, PipelineWith credentials() binding
CircleCIContexts + Project varsOrg (contexts), ProjectAutomatic
Azure DevOpsVariable GroupsPipeline, Stage, Key Vault linkSecret type auto-masked

HashiCorp Vault / OpenBao integration

For teams that need dynamic, short-lived secrets, HashiCorp Vault (or its MPL-2.0-licensed fork OpenBao, maintained by the Linux Foundation) is the gold standard. Instead of storing a static database password in CI, Vault generates a temporary credential that expires after the pipeline completes. OpenBao is API-compatible with Vault and includes features formerly exclusive to Vault Enterprise, such as namespaces.

Method AppRole Auth

CI pipelines authenticate to Vault using AppRole (role ID + secret ID). The pipeline receives a short-lived Vault token, reads secrets, and the token expires. Jenkins Vault plugin, GitLab CI vault: keyword, and GitHub Actions hashicorp/vault-action all support this pattern.

Method Dynamic Secrets

Vault generates on-demand credentials for databases (PostgreSQL, MySQL), cloud providers (AWS STS, GCP service accounts), and PKI certificates. Each pipeline run gets unique credentials that auto-expire. No shared passwords, no rotation headaches.

OIDC federation

OIDC federation eliminates stored cloud credentials entirely. The CI provider issues a signed JWT, and the cloud provider validates it and returns a short-lived access token.

CI Provider (GitHub/GitLab)          Cloud Provider (AWS/GCP/Azure)
        |                                        |
        |-- 1. issues JWT (signed) ------------->|
        |                                        |-- 2. validates JWT signature
        |                                        |-- 3. checks claims (repo, branch, etc.)
        |<-- 4. returns short-lived token -------|
        |                                        |
        |-- 5. uses token for API calls -------->|
  • GitHub Actions — use permissions: id-token: write + aws-actions/configure-aws-credentials with role-to-assume
  • GitLab CI — use id_tokens: keyword to generate OIDC tokens, configure cloud provider trust
  • Azure DevOps — use workload identity federation on service connections

Secret scanning in pipelines

  • gitleaks — scans git history and staged changes for hardcoded secrets (API keys, passwords, tokens). Run as a pre-commit hook or CI step.
  • trufflehog — entropy-based and regex-based secret detection. Scans git repos, S3 buckets, and filesystem paths. Verifies found secrets against live APIs.
  • Pre-commit hooks — catch secrets before they enter Git history. Prevention is better than detection.
Best practice

Dynamic secrets > static secrets > hardcoded secrets. Use Vault dynamic secrets when possible. Use OIDC federation for cloud access. Use native CI secrets as a fallback. Run gitleaks in CI as a safety net. Never commit secrets to Git — even in private repos.

10

Platform Compatibility Matrix

Not every CI/CD tool works with every Git platform. Some are tightly coupled (native only), while others are agnostic. This matrix shows which combinations work.

Compatibility grid

Git PlatformNative CIJenkinsCircleCIArgoCDAzure Pipelines
GitHubGitHub ActionsWebhookNativeNativeNative
GitLabGitLab CI (exclusive)WebhookSupportedNative
BitbucketBitbucket PipelinesWebhookSupportedNativeSupported
GiteaGitea ActionsWebhookNative

Lock-in rules

Locked Platform-Exclusive CI

  • GitLab CI only works with GitLab repos — cannot be used with GitHub or Bitbucket
  • GitHub Actions only works with GitHub repos — cannot be used with GitLab or Gitea
  • Gitea Actions only works with Gitea repos — cannot be used with GitHub or GitLab
  • Bitbucket Pipelines only works with Bitbucket repos

These CI systems are baked into their Git platforms. The YAML syntax is not portable.

Agnostic Platform-Agnostic CI

  • Jenkins — connects to anything via webhooks (GitHub, GitLab, Bitbucket, Gitea, self-hosted Git)
  • CircleCI — native integration with GitHub, GitLab, and Bitbucket
  • ArgoCD — works with any Git repository (it only reads manifests from Git)
  • Azure Pipelines — supports Azure Repos, GitHub, and Bitbucket

These tools don't care where your code lives.

Combos that work (despite seeming odd)

  • CircleCI + GitLab — fully supported. CircleCI added GitLab support as a first-class integration.
  • Azure Pipelines + GitHub — very common. Many teams use Azure DevOps for CI/CD with GitHub repos.
  • Jenkins + literally anything — if it has a webhook or can be polled, Jenkins can build it.
  • ArgoCD + Gitea — ArgoCD just needs a Git URL. Works with any Git server including Gitea.

Combos that don't work

  • GitLab CI from GitHub — nope. .gitlab-ci.yml is meaningless on GitHub.
  • GitHub Actions from GitLab — nope. .github/workflows/ is ignored on GitLab.
  • Gitea Actions from GitHub — nope (the reverse works though: GitHub workflow files run on Gitea).
  • Bitbucket Pipelines from anywhere except Bitbucket — nope.
Migration warning

If you're migrating Git platforms (e.g., GitHub to GitLab), your CI/CD configs do not migrate. GitHub Actions YAML has no meaning on GitLab and vice versa. The pipeline logic must be rewritten for the new platform's CI system, or you switch to an agnostic tool like Jenkins that works with both.

11

Comparison

A side-by-side view of every CI/CD platform covered in this guide.

Platform comparison table

Platform Config File Self-Hosted Native Git Agnostic K8s Native Learning Curve Maintenance
GitHub Actions .github/workflows/*.yml Runners only GitHub No No Low Low
GitLab CI .gitlab-ci.yml Full (GitLab + Runners) GitLab No No Medium Medium
Jenkins Jenkinsfile Full (required) None Yes Via Jenkins X High High
CircleCI .circleci/config.yml Runners only None Yes No Low-Medium Low
Azure Pipelines azure-pipelines.yml Agents only Azure Repos Partial No Medium Low-Medium
Gitea Actions .gitea/workflows/*.yml Full (required) Gitea No No Low Medium
ArgoCD Application CRDs Full (in-cluster) None Yes Yes Medium Medium
FluxCD GitRepository/Kustomization CRDs Full (in-cluster) None Yes Yes Medium-High Medium
Octopus Deploy UI / Config-as-Code Full or Cloud None Yes Partial Medium Medium

When to use what

Scenario Small team on GitHub

Use GitHub Actions. Zero infrastructure to manage, tight integration with PRs and deployments, huge marketplace of pre-built actions. The default choice unless you have specific reasons not to.

Scenario Enterprise on GitLab

Use GitLab CI. It's built into the platform you already use. Environments, review apps, container registry, SAST/DAST scanning — all integrated. No reason to add another CI tool.

Scenario Multi-platform enterprise

Use Jenkins or CircleCI. If you have repos on GitHub, GitLab, and Bitbucket, you need an agnostic tool. Jenkins for maximum control (at the cost of maintenance), CircleCI for a managed experience.

Scenario K8s-native GitOps

Use ArgoCD + any CI for build. Let GitHub Actions or GitLab CI handle build/test. ArgoCD handles deployment to Kubernetes via GitOps. Clean separation of CI and CD.

Scenario Azure shop

Use Azure DevOps. Deep integration with Azure services, Key Vault, AKS, and Active Directory. Service connections make auth seamless. YAML pipelines with templates keep configs DRY.

Scenario Self-hosted + GitHub-compatible

Use Gitea Actions. If you want a self-hosted Git platform with GitHub Actions-compatible CI/CD, Gitea is the answer. Reuse existing workflow files with minimal changes.

12

Checklist

Production CI/CD setup checklist. Work through these items when building or auditing your pipeline infrastructure.

Pipeline fundamentals

  • Pipeline config is version-controlled — all CI/CD configuration lives in Git alongside the application code. No GUI-only pipeline definitions.
  • Stages are clearly separated — build, test, security scan, deploy are distinct stages. Each stage has a clear purpose and failure mode.
  • Tests run before deploy — no deployment can happen without passing unit tests, integration tests, and linting. This is non-negotiable.
  • Artifacts are immutable — build once, deploy everywhere. The same artifact (container image, binary) is promoted through environments. Never rebuild per environment.
  • Pipeline DAG is optimized — use needs: (GitLab) or job dependencies (GitHub Actions) to skip unnecessary waits. Parallel where possible.

Security

  • No hardcoded secrets — all credentials come from a secret store (native CI secrets, Vault, Key Vault). Zero secrets in YAML files, Dockerfiles, or source code.
  • OIDC federation for cloud access — eliminate long-lived API keys. Use OIDC tokens for AWS, GCP, and Azure authentication from CI.
  • Secret scanning enabled — run gitleaks or trufflehog in CI. Block merges if secrets are detected. Use pre-commit hooks as a first line of defense.
  • Minimal permissions — CI tokens and service accounts have the least privilege needed. GitHub Actions permissions: block restricts GITHUB_TOKEN. Vault policies are narrow.
  • Dependency scanning — run SAST, SCA (Dependabot, Snyk, Trivy) in the pipeline. Fail builds on critical vulnerabilities.
  • Signed artifacts — sign container images (cosign/sigstore) and verify signatures before deployment.

Runners & infrastructure

  • Runners are ephemeral — use fresh containers or VMs per build. No shared state between builds. Prevents leaking secrets or artifacts across pipelines.
  • Runner autoscaling configured — scale runners based on queue depth. Don't over-provision or leave developers waiting for builds.
  • Caching strategy defined — cache dependency directories (node_modules, .m2, pip cache) to speed up builds. Invalidate caches on lockfile changes.
  • Build times monitored — track pipeline duration over time. Alert when builds exceed SLO. Investigate and optimize slow steps.

Deployment & environments

  • Environment promotion path defined — Dev → Staging → Production (or similar). Each promotion has clear criteria (tests pass, approval obtained).
  • Production deploys require approval — use environment protection rules (GitHub), manual gates (GitLab when: manual), or approval workflows (Azure DevOps, Octopus Deploy).
  • Rollback mechanism tested — know how to roll back before you need to. GitOps: git revert. Traditional: re-deploy previous artifact. Test rollback regularly.
  • Deployment notifications configured — Slack, Teams, or email notifications for deploy start, success, and failure. Include commit info and actor.
  • Feature flags for risky changes — decouple deploy from release. Ship disabled features and enable them independently of deployment.

Observability & governance

  • Pipeline audit trail — every deployment is logged with who triggered it, what was deployed, and when. Required for compliance (SOC2, ISO 27001).
  • Branch protection enforced — main/production branches require passing CI, code review, and no direct pushes. CI is a gate, not a suggestion.
  • Pipeline-as-code reviewed — changes to CI/CD config go through code review just like application code. A malicious pipeline change can exfiltrate secrets.
  • Disaster recovery plan — if your CI/CD platform goes down, how do you deploy? Have a manual deployment runbook for emergencies.