E-NO Logo
EN FR
Bash CI/CD 10 Min Read

Automatisation CI/CD en Bash : guide prêt pour la production avec exemples pratiques

calendar_today Published: 2026-07-24
update Last Updated: 2026-07-25
analytics SEO Efficiency: 97%
Technical guide illustration pour Automatisation CI/CD en Bash : guide prêt pour la production avec exemples pratiques.

Intro

Bash reste la colle de nombreux pipelines CI/CD (Continuous Integration / Continuous Delivery) : intégration continue et livraison continue. Il est disponible partout, rapide à itérer, et idéal pour orchestrer des outils comme Git, Docker et Kubernetes. Ce guide propose une approche Bash prête pour la production en CI/CD : sécurité stricte du shell, journalisation claire, fonctions modulaires, reprises (retries) et timeouts, validation avant déploiement, vérification de santé, et rollbacks rapides. Vous verrez aussi des exemples GitHub Actions et GitLab CI que vous pouvez copier dans vos dépôts dès aujourd’hui.

Ce que vous allez construire :

  • Une bibliothèque Bash réutilisable (modes stricts, logs, traps, reprises, timeouts, verrous)
  • Des scripts pour lint, test, build, validate, deploy, health-check et rollback
  • Des pipelines CI pour GitHub Actions et GitLab CI
  • Des modèles de rollback pratiques pour Kubernetes et Docker Compose

Arborescence recommandée du dépôt :

.
├─ scripts/
│  ├─ lib.sh
│  ├─ lint.sh
│  ├─ test.sh
│  ├─ build.sh
│  ├─ validate.sh
│  ├─ deploy.sh
│  ├─ healthcheck.sh
│  └─ rollback.sh
├─ docker/
│  └─ compose.yaml
├─ k8s/
│  ├─ deployment.yaml
│  └─ service.yaml
└─ app/
   └─ ...

1) Fondation Bash sécurisée (scripts/lib.sh)

Activez les modes stricts, un découpage des mots prévisible, une gestion d’erreurs défensive et une journalisation structurée.

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

# Colors only if stdout is a TTY
if [ -t 1 ]; then
  GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RED='\033[0;31m'; NC='\033[0m'
else
  GREEN=''; YELLOW=''; RED=''; NC=''
fi

log()  { printf "%sINFO%s  %s\n"  "$GREEN" "$NC" "$*"; }
warn() { printf "%sWARN%s  %s\n"  "$YELLOW" "$NC" "$*"; }
err()  { printf "%sERROR%s %s\n" "$RED" "$NC" "$*" 1>&2; }
fail(){ err "$*"; exit 1; }

require() { command -v "$1" >/dev/null 2>&1 || fail "Missing command: $1"; }
assert_env(){ for v in "$@"; do [ -n "${!v-}" ] || fail "Missing env var: $v"; done; }

# Basic error context
_on_error() {
  local ec=$? line=${1:-}
  err "Command failed at line $line (exit=$ec). Enable trace with: export TRACE=1"
}

# Optional tracing for debugging
[ "${TRACE-}" = "1" ] && set -x
trap '_on_error $LINENO' ERR

# Run CMD with retries
with_retry() {
  local attempts=${1:-5} sleep_s=${2:-2}; shift 2
  local n=1
  until "$@"; do
    if [ $n -ge "$attempts" ]; then
      err "Retry failed after $attempts attempts: $*"; return 1
    fi
    warn "Attempt $n failed. Retrying in ${sleep_s}s..."; sleep "$sleep_s"; n=$((n+1))
  done
}

# Timebox a command if 'timeout' exists
timebox() {
  local seconds=$1; shift
  if command -v timeout >/dev/null 2>&1; then
    timeout "$seconds" "$@"
  else
    warn "timeout not found; running without time limit"
    "$@"
  fi
}

# Serialize critical sections
lock_run() {
  local lockfile=$1; shift
  require flock
  exec 9>"$lockfile"
  flock -n 9 || fail "Another process holds $lockfile"
  "$@"
}

# Simple on-exit cleanup hook
_cleanup_funcs=()
on_exit() { _cleanup_funcs+=("$*"); }
trap 'for f in "${_cleanup_funcs[@]}"; do eval "$f" || true; done' EXIT

Points clés :

  • set -euo pipefail et IFS limitent les pièges courants et les échecs silencieux.
  • trap avec un gestionnaire personnalisé fournit un contexte d’erreur exploitable.
  • with_retry et timebox protègent des opérations instables ou longues.
  • lock_run sérialise les déploiements pour éviter les conditions de concurrence.

2) Scripts de lint et de tests

Lintez vos scripts Bash et exécutez des tests unitaires rapides. Remplacez l’extrait de test par le runner de tests de votre langage au besoin.

scripts/lint.sh

#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/lib.sh"

log "Linting bash scripts"
find . -type f -name "*.sh" -print0 | xargs -0 -I{} bash -n {}

if command -v shellcheck >/dev/null 2>&1; then
  log "Running shellcheck"
  find . -type f -name "*.sh" -print0 | xargs -0 shellcheck -x
else
  warn "shellcheck not found; skipping static analysis"
fi

scripts/test.sh

#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/lib.sh"

log "Running unit tests"

add() { echo $(( $1 + $2 )); }
assert_eq(){ [ "$1" = "$2" ] || fail "assert_eq failed: got=$1 want=$2"; }

assert_eq "$(add 2 3)" 5
log "Tests passed"

3) Construire un artefact

Produisez une image Docker quand Docker est disponible; sinon créez une archive tarball pour que les étapes suivantes disposent quand même d’un artefact stable.

scripts/build.sh

#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/lib.sh"

APP_NAME=${APP_NAME:-demoapp}
IMAGE_REPO=${IMAGE_REPO:-example/demoapp}
GIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo local)
TAG=${TAG:-$GIT_SHA}
ART_DIR=${ART_DIR:-dist}

mkdir -p "$ART_DIR"

if command -v docker >/dev/null 2>&1; then
  log "Building Docker image $IMAGE_REPO:$TAG"
  docker build -t "$IMAGE_REPO:$TAG" -f docker/Dockerfile .
  echo "$IMAGE_REPO:$TAG" > "$ART_DIR/image.tag"
else
  warn "docker not found; creating tarball artifact instead"
  tar -czf "$ART_DIR/$APP_NAME-$TAG.tgz" app/
  echo "$ART_DIR/$APP_NAME-$TAG.tgz" > "$ART_DIR/artifact.path"
fi

4) Valider avant de déployer

Validez les manifests et les configurations Compose en CI pour échouer tôt.

scripts/validate.sh

#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/lib.sh"

STAGE=${STAGE:-staging}
COMPOSE_FILE=${COMPOSE_FILE:-docker/compose.yaml}

log "Validating deployment manifests for $STAGE"

if command -v kubectl >/dev/null 2>&1 && [ -d k8s ]; then
  require kubectl
  kubectl apply --dry-run=server -f k8s/
  kubectl diff -f k8s/ || true
elif command -v docker >/dev/null 2>&1 && [ -f "$COMPOSE_FILE" ]; then
  require docker
  docker compose -f "$COMPOSE_FILE" config >/dev/null
else
  warn "No k8s or compose config found to validate"
fi

5) Déployer en sécurité et vérifier la santé

Déployez avec un verrou pour éviter les exécutions qui se chevauchent, puis vérifiez un endpoint de santé avant de marquer la réussite.

scripts/deploy.sh

#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/lib.sh"

STAGE=${STAGE:-staging}
HEALTH_URL=${HEALTH_URL:-http://localhost:8080/healthz}
LOCKFILE=${LOCKFILE:-/tmp/deploy.lock}

_do_deploy() {
  log "Starting deploy to $STAGE"

  if command -v kubectl >/dev/null 2>&1 && [ -d k8s ]; then
    kubectl apply -f k8s/
    if kubectl get deploy >/dev/null 2>&1; then
      for d in $(kubectl get deploy -o name); do
        log "Waiting for $d to roll out"
        with_retry 30 2 kubectl rollout status "$d"
      done
    fi
  elif command -v docker >/dev/null 2>&1 && [ -f docker/compose.yaml ]; then
    docker compose -f docker/compose.yaml up -d --build
  else
    warn "No deploy target detected; skipping apply"
  fi

  "$(dirname "$0")/healthcheck.sh" "$HEALTH_URL" 60 2
  log "Deployment verified"
}

lock_run "$LOCKFILE" _do_deploy

scripts/healthcheck.sh

#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/lib.sh"

URL=${1:-http://localhost:8080/healthz}
ATTEMPTS=${2:-30}
SLEEP=${3:-2}

require curl
with_retry "$ATTEMPTS" "$SLEEP" bash -c "curl -fsS --max-time 2 '$URL' >/dev/null"

6) Promotion et rollback

Enregistrez la version déployée pour rendre les rollbacks prévisibles.

scripts/rollback.sh

#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/lib.sh"

STAGE=${STAGE:-staging}
PREV_FILE=${PREV_FILE:-.previous_version}
CURR_FILE=${CURR_FILE:-dist/image.tag}
COMPOSE_FILE=${COMPOSE_FILE:-docker/compose.yaml}

promote() {
  [ -f "$CURR_FILE" ] || fail "Missing current version file: $CURR_FILE"
  cp "$CURR_FILE" "$PREV_FILE"
  log "Promoted version $(cat "$CURR_FILE") for $STAGE"
}

rollback() {
  [ -f "$PREV_FILE" ] || fail "No previous version recorded"
  local prev repo tag
  prev=$(cat "$PREV_FILE")
  repo=${prev%:*}
  tag=${prev#*:}

  if command -v kubectl >/dev/null 2>&1 && kubectl get deploy >/dev/null 2>&1; then
    for d in $(kubectl get deploy -o name); do
      log "Rolling back $d to image $prev"
      kubectl set image "$d" "*=$prev" --record
      with_retry 30 2 kubectl rollout status "$d"
    done
  elif command -v docker >/dev/null 2>&1 && [ -f "$COMPOSE_FILE" ]; then
    # Requires compose.yaml to use image: "${IMAGE_REPO}:${TAG}" or "${IMAGE}:${TAG}"
    IMAGE_REPO="$repo" TAG="$tag" docker compose -f "$COMPOSE_FILE" up -d
    log "Compose services updated to $repo:$tag"
  else
    fail "No supported platform for rollback"
  fi
}

case "${1:-}" in
  promote) promote ;;
  rollback) rollback ;;
  *) echo "Usage: $0 {promote|rollback}"; exit 2 ;;
esac

Astuce : Kubernetes prend aussi en charge kubectl rollout undo deploy/<nom> pour revenir au ReplicaSet précédent sans gérer les tags d’image. L’usage de tags explicites plus un fichier enregistré rend l’historique portable entre clusters et runners.

7) Intégration CI

GitHub Actions (/.github/workflows/ci.yaml) :

name: ci
on:
  push:
    branches: [ main ]

jobs:
  build-test-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Lint
        run: scripts/lint.sh
      - name: Test
        run: scripts/test.sh
      - name: Build
        run: scripts/build.sh
      - name: Validate manifests
        env:
          STAGE: staging
        run: scripts/validate.sh
      - name: Deploy to staging and verify
        env:
          STAGE: staging
          HEALTH_URL: https://example-staging.local/healthz
        run: scripts/deploy.sh
      - name: Record version for rollback
        run: scripts/rollback.sh promote

GitLab CI (/.gitlab-ci.yml) :

stages: [lint, test, build, validate, deploy]

variables:
  GIT_STRATEGY: fetch

lint:
  stage: lint
  image: alpine:3.20
  before_script: ["apk add --no-cache bash findutils shellcheck"]
  script:
    - chmod +x scripts/*.sh
    - scripts/lint.sh

unit_test:
  stage: test
  image: alpine:3.20
  before_script: ["apk add --no-cache bash"]
  script:
    - chmod +x scripts/*.sh
    - scripts/test.sh

build:
  stage: build
  image: docker:26
  services:
    - docker:26-dind
  variables:
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
  script:
    - chmod +x scripts/*.sh
    - scripts/build.sh
  artifacts:
    paths:
      - dist/

validate:
  stage: validate
  image: bitnami/kubectl:1.29
  script:
    - chmod +x scripts/*.sh
    - STAGE=staging scripts/validate.sh

deploy_staging:
  stage: deploy
  image: bitnami/kubectl:1.29
  when: manual
  script:
    - chmod +x scripts/*.sh
    - STAGE=staging HEALTH_URL=https://example-staging.local/healthz scripts/deploy.sh
    - scripts/rollback.sh promote

8) Tableaux de comparaison

Étapes du pipeline et objectifs :

ÉtapeScriptObjectifÉchec si…
Lintscripts/lint.shDétecter les erreurs syntaxe/styleUne erreur bash -n ou shellcheck survient
Testscripts/test.shVérifier le comportement cléDes assertions échouent
Buildscripts/build.shProduire image/archivesÉchec du build Docker ou de la création tar
Validatescripts/validate.shDry-run des manifests/configSchéma invalide ou références erronées
Deployscripts/deploy.shAppliquer + attendre + health checkRollout ou endpoint de santé en échec
Promoterollback.sh promoteEnregistrer la version courantedist/image.tag manquant
Rollbackrollback.sh rollbackRétablir rapidementAucune version précédente ou erreur de plateforme

Approches de rollback :

PlateformeMéthodeAvantagesLimites
Kuberneteskubectl rollout undoRapide, exploite le ReplicaSet précédentLimité au dernier ReplicaSet
KubernetesDéfinir l’image sur le tag enregistréExplicite, traçable, portableNécessite l’enregistrement/poussée des tags
Docker ComposeRedéployer avec variables IMAGE/TAGSimple, pas d’outil additionnelLe compose.yaml doit utiliser des variables

9) Dépannage et durcissement

  • Le script s’arrête sans contexte : export TRACE=1 pour activer le traçage set -x via lib.sh.
  • Échecs cachés dans les pipelines : utilisez toujours -euo pipefail et mettez les variables entre guillemets, par exemple "${var}".
  • Contrôle de santé instable : augmentez les tentatives de with_retry ou le délai SLEEP; assurez-vous que votre application renvoie 200 OK.
  • kubectl non configuré : fournissez KUBECONFIG ou exécutez kubectl config set-context avant le déploiement.
  • Permission Docker refusée : assurez-vous que le runner a accès au socket Docker ou utilisez DinD avec DOCKER_HOST.
  • Secrets dans les logs : ne loggez jamais des variables d’environnement secrètes; préférez des fichiers ou des variables CI masquées.
  • Déploiements concurrents : garantissez que lock_run utilise un chemin partagé (p. ex. /tmp/deploy.lock) entre les étapes.
  • Rollback en échec sur Compose : confirmez que compose.yaml utilise image: "${IMAGE_REPO}:${TAG}" et que IMAGE_REPO/TAG sont définies depuis le fichier enregistré.
  • Commandes longues : encapsulez avec timebox 60 <cmd>; installez coreutils/timeout si absent.

10) Pilote local exécutable dès aujourd’hui

  • Rendez les scripts exécutables : chmod +x scripts/*.sh
  • .env optionnel pour les exécutions locales :
  • APP_NAME=demoapp
  • IMAGE_REPO=example/demoapp
  • STAGE=staging
  • HEALTH_URL=http://localhost:8080/healthz
  • Enchaînez le flux :
  • scripts/lint.sh
  • scripts/test.sh
  • scripts/build.sh
  • scripts/validate.sh || true
  • scripts/deploy.sh
  • scripts/rollback.sh promote
  • Tester le rollback :
  • Déployez un mauvais tag (p. ex., modifiez k8s/deployment.yaml ou définissez TAG=bad et lancez deploy)
  • Observez l’échec du health check
  • Exécutez scripts/rollback.sh rollback et confirmez le rétablissement

Mesurez : temps total, taux de succès du health-check pendant le déploiement, et temps moyen de rollback.

Conclusion

Vous disposez maintenant d’une boîte à outils Bash pratique pour la CI/CD : sécurité stricte du shell, logs et utilitaires réutilisables, validation avant déploiement, rollouts vérifiés et rollbacks rapides et traçables. Commencez par le pilote local, connectez-le à GitHub Actions ou GitLab CI, et faites évoluer les mêmes scripts du staging à la production. Entraînez-vous régulièrement aux scénarios d’échec, conservez les versions précédentes dans un stockage pérenne, et votre automatisation Bash restera prévisible, inspectable et sûre à étendre à mesure que votre plateforme grandit.

Article Quality Score

Reader usefulness 97%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL