RousselTMLEARNING

DevOps

End-to-end CI/CD (5/5): GitHub Actions, GitLab CI, Jenkins and Argo CD in practice

Compare and use CI/CD tools: GitHub Actions, GitLab CI, Jenkins, Argo CD and Flux; a complete pipeline from commit to production, reusable workflows, runner security and pipeline metrics.

1. Tool landscape #

Choosing without dogma.

Comparison #

ToolModelStrengthsPoints of attention
GitHub ActionsYAML in .github/workflows, hosted or self-hosted runnersGitHub integration, actions marketplace, OIDCpin third-party actions, cost of minutes
GitLab CI.gitlab-ci.yml, GitLab runnersall-in-one platform (registry, security, environments), self-hostablecomplex files at scale (use include and components)
JenkinsJenkinsfile (Groovy), agentshighly extensible, self-hosted, large legacyplugin maintenance, controller security
Argo CD / FluxGitOps: the cluster pulls the state from Gitcontinuous sync, drift detection, rollback through GitKubernetes only, does not replace CI
OthersCircleCI, Azure Pipelines, Tekton, Dagger, Buildkitedepending on the ecosystem—

Push or pull #

Complete CI/CD chain
CI builds and publishes; Argo CD deploys by pulling the state described in Git.

In push mode, the pipeline connects to the target and deploys (kubectl, Helm, SSH): simple, but CI holds access to production. In pull mode (GitOps), an agent in the cluster reads the repository and applies the changes: CI only needs to write to Git.

2. A complete pipeline with GitHub Actions #

From commit to production.

The workflow #

name: delivery
on:
  push: { branches: [main] }
  pull_request: {}
permissions: { contents: read }
concurrency: { group: delivery-${{ github.ref }}, cancel-in-progress: true }

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12", cache: pip }
      - run: pip install -r requirements.txt && ruff check . && pytest -q

  build:
    needs: test
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    permissions: { contents: read, packages: write, id-token: write }
    outputs: { image: "${{ steps.meta.outputs.image }}" }
    steps:
      - uses: actions/checkout@v4
      - id: meta
        run: echo "image=ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
      - uses: docker/login-action@v3
        with: { registry: ghcr.io, username: "${{ github.actor }}", password: "${{ secrets.GITHUB_TOKEN }}" }
      - uses: docker/build-push-action@v6
        with: { push: true, tags: "${{ steps.meta.outputs.image }}" }
      - uses: aquasecurity/trivy-action@0.28.0
        with: { image-ref: "${{ steps.meta.outputs.image }}", severity: CRITICAL, exit-code: "1" }

  deploy-staging:
    needs: build
    environment: staging
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh staging "${{ needs.build.outputs.image }}"
      - run: ./smoke-test.sh https://staging.example.com

  deploy-production:
    needs: [build, deploy-staging]
    environment: production          # règle d'approbation configurée sur l'environnement
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh production "${{ needs.build.outputs.image }}"
      - run: ./smoke-test.sh https://shop.example.com

Reusing #

# workflow réutilisable appelé par plusieurs dépôts
jobs:
  ci:
    uses: mon-org/pipelines/.github/workflows/python-ci.yml@v3
    with: { python-version: "3.12" }
    secrets: inherit

3. The same pipeline with GitLab CI and Jenkins #

The concepts are identical.

GitLab CI #

stages: [test, build, deploy]
variables: { IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" }
test:
  stage: test
  image: python:3.12
  cache: { paths: [.cache/pip] }
  script: [pip install -r requirements.txt, pytest -q]
build:
  stage: build
  image: docker:27
  services: [docker:27-dind]
  rules: [{ if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' }]
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $IMAGE . && docker push $IMAGE
deploy-staging:
  stage: deploy
  environment: { name: staging, url: https://staging.example.com }
  script: [./deploy.sh staging $IMAGE]
deploy-production:
  stage: deploy
  environment: { name: production }
  when: manual
  script: [./deploy.sh production $IMAGE]

Jenkins #

// Jenkinsfile (pipeline déclaratif)
pipeline {
  agent { docker { image 'python:3.12' } }
  environment { IMAGE = "registry.example.com/shop/api:${env.GIT_COMMIT.take(7)}" }
  stages {
    stage('Test')  { steps { sh 'pip install -r requirements.txt && pytest -q' } }
    stage('Build') { agent any
      steps { sh 'docker build -t $IMAGE . && docker push $IMAGE' } }
    stage('Staging') { steps { sh './deploy.sh staging $IMAGE' } }
    stage('Production') {
      input { message 'Déployer en production ?' }
      steps { sh './deploy.sh production $IMAGE' } }
  }
  post { failure { echo 'Pipeline en échec : prévenir l\'équipe' } }
}

4. Pipeline security and health #

CI is a prime target.

Securing #

  • Minimal permissions by default (permissions: contents: read), widened job by job.
  • Third-party actions pinned by commit SHA, reviewed before adoption.
  • No secrets exposed to pull requests coming from forks.
  • Ephemeral self-hosted runners (one job, one machine), never shared between projects of different trust levels.
  • OIDC to the cloud rather than long-lived keys.
Common mistake: A persistent self-hosted runner attached to a public repository lets anyone run code on your network through a pull request.

Measuring the pipeline #

IndicatorTarget
Commit pipeline duration< 10 min
Success rate on main> 90%
Time to fix a broken build< 1 h
Rerun rate (flaky tests)decreasing
Commit → production lead timesee the DORA metrics

Related articles

Organizing DevOps: value streams, teams and a blameless culture

Set up a DevOps organization: value chain and value stream mapping, Team Topologies, « you build it, you run …

Git for DevOps: branches, code reviews and versions

Organize work with Git in a DevOps approach: trunk-based development or GitFlow, short-lived branches, pull r…

End-to-end CI/CD (1/5): principles and anatomy of a pipeline

Understand end-to-end CI/CD: continuous integration, continuous delivery and continuous deployment, pipeline …

Keep learning

Support us

♥ SUPPORT US

Support RousselTM Learning

Courses, articles, labs and certificates stay free with no intrusive ads. A voluntary contribution, of any amount you choose, pays for hosting, lab environments and new content.

Contribute with PayPal Any amount · card or PayPal accountLearn more
QR code PayPal
Or scan this QR code with your phone

Thank you, every contribution counts!