CI/CD
Continuous integration and delivery — pipelines, platforms, and GitOps workflows
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.
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.
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/
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.
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
| Keyword | Purpose |
|---|---|
stages | Define pipeline stage order |
needs | DAG dependencies (skip stage ordering) |
rules | Conditional job execution |
variables | Define CI/CD variables |
artifacts | Files passed between jobs/stages |
cache | Reuse files across pipeline runs |
services | Spin up Docker containers alongside the job (databases, etc.) |
trigger | Start child or multi-project pipelines |
include | Import external YAML files (templates, shared configs) |
environment | Link job to a deployment environment |
resource_group | Limit 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
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.
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}"
}
}
}
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.
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
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.
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)
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.
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/
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.
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.
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
| Aspect | Push (Traditional CI/CD) | Pull (GitOps) |
|---|---|---|
| Who deploys? | CI pipeline pushes to cluster | Cluster agent pulls from Git |
| Credentials | CI needs cluster credentials | Agent runs inside cluster (no external creds) |
| Drift detection | None (deploy-and-forget) | Continuous reconciliation |
| Rollback | Re-run pipeline or manual | Git revert (automatic re-sync) |
| Tools | Jenkins, GitHub Actions, GitLab CI | ArgoCD, FluxCD |
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.
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
| Platform | Secret Storage | Scoping | Masking |
|---|---|---|---|
| GitHub Actions | GitHub Secrets | Repo, Environment, Org | Automatic |
| GitLab CI | CI/CD Variables | Project, Group, Instance | Opt-in (masked flag) |
| Jenkins | Credentials Store | Global, Folder, Pipeline | With credentials() binding |
| CircleCI | Contexts + Project vars | Org (contexts), Project | Automatic |
| Azure DevOps | Variable Groups | Pipeline, Stage, Key Vault link | Secret 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-credentialswithrole-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.
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.
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 Platform | Native CI | Jenkins | CircleCI | ArgoCD | Azure Pipelines |
|---|---|---|---|---|---|
| GitHub | GitHub Actions | Webhook | Native | Native | Native |
| GitLab | GitLab CI (exclusive) | Webhook | Supported | Native | — |
| Bitbucket | Bitbucket Pipelines | Webhook | Supported | Native | Supported |
| Gitea | Gitea Actions | Webhook | — | Native | — |
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.ymlis 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.
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.
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.
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 restrictsGITHUB_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.