CI/CD maison : buffet à volonté, food-truck gourmet… ou cuisine 3 étoiles ? Christopher HENRY 13/11/2025

CI/CD maison : buffet à volonté, food-truck gourmet… ou cuisine 3 étoiles ?

Chez OBI Partner, on adore coder Python et PHP. Mais soyons honnêtes : le moment où tout part en prod, c’est souvent plus proche d’un sketch de Kaamelott que d’une recette de cuisine bien huilée.
Entre les serveurs qui fument, les bases de données qui pleurent et les devs qui jurent “mais chez moi ça marche”… il fallait trouver une meilleure méthode.

C’est là qu’entre en scène le CI/CD auto-hébergé. Pas de SaaS américain indiscret, pas de dépendance à un nuage capricieux : tout tourne chez toi, dans ton infra.

Et bonne nouvelle : j’ai cuisiné pour toi plusieurs recettes 🔥.

version: '3.7'
services:
  gitlab:
    image: gitlab/gitlab-ce:latest
    restart: always
    hostname: gitlab.local
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://gitlab.local'
        gitlab_rails['gitlab_shell_ssh_port'] = 2222
    ports:
      - "8080:80"
      - "443:443"
      - "2222:22"
    volumes:
      - ./gitlab/config:/etc/gitlab
      - ./gitlab/logs:/var/log/gitlab
      - ./gitlab/data:/var/opt/gitlab

  runner:
    image: gitlab/gitlab-runner:latest
    restart: always
    volumes:
      - ./runner/config:/etc/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock
version: '3.8'

services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    volumes:
      - ./gitea:/data
    ports:
      - "3000:3000"
      - "222:22"

  woodpecker-server:
    image: woodpeckerci/woodpecker-server:latest
    container_name: woodpecker-server
    environment:
      - WOODPECKER_OPEN=true
      - WOODPECKER_ADMIN=admin
      - WOODPECKER_GITEA=true
      - WOODPECKER_GITEA_URL=http://gitea:3000
      - WOODPECKER_GITEA_CLIENT=supersecret
      - WOODPECKER_GITEA_SECRET=supersecret
      - WOODPECKER_SECRET=woodpeckersecret
    ports:
      - "8000:8000"
    restart: always
    depends_on:
      - gitea

  woodpecker-agent:
    image: woodpeckerci/woodpecker-agent:latest
    container_name: woodpecker-agent
    environment:
      - WOODPECKER_SERVER=woodpecker-server:9000
      - WOODPECKER_SECRET=woodpeckersecret
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always
    depends_on:
      - woodpecker-server

  harbor:
    image: goharbor/harbor-core:v2.12.0
    container_name: harbor
    environment:
      - HARBOUR_ADMIN_PASSWORD=Harbor12345
    ports:
      - "8081:8080"
    restart: always
    volumes:
      - ./harbor:/data
stages: [lint, test, build, deploy]

lint:python:
  stage: lint
  image: python:3.12
  script:
    - pip install ruff black
    - ruff check .
    - black --check .

lint:php:
  stage: lint
  image: php:8.3-cli
  script:
    - composer install --no-interaction
    - vendor/bin/phpstan analyse
    - vendor/bin/php-cs-fixer fix --dry-run --diff

test:python:
  stage: test
  image: python:3.12
  script:
    - pip install -r requirements.txt
    - pytest -q

test:php:
  stage: test
  image: php:8.3-cli
  script:
    - composer install --no-interaction
    - vendor/bin/phpunit
pipeline:
  lint_python:
    image: python:3.12
    commands:
      - pip install ruff black
      - ruff check .
      - black --check .

  lint_php:
    image: php:8.3-cli
    commands:
      - composer install --no-interaction
      - vendor/bin/phpstan analyse
      - vendor/bin/php-cs-fixer fix --dry-run --diff

  test_python:
    image: python:3.12
    commands:
      - pip install -r requirements.txt
      - pytest -q

  test_php:
    image: php:8.3-cli
    commands:
      - composer install --no-interaction
      - vendor/bin/phpunit
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.* ./
RUN composer install --no-dev --prefer-dist --no-progress --no-interaction
COPY . .
RUN php artisan route:cache && php artisan config:cache

FROM php:8.3-fpm-alpine
WORKDIR /var/www/html
COPY --from=vendor /app /var/www/html
RUN docker-php-ext-install pdo pdo_mysql opcache
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml poetry.lock* ./
RUN pip install poetry && poetry export -f requirements.txt -o requirements.txt
RUN pip install -r requirements.txt --target /deps
COPY . .

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /deps /usr/local/lib/python3.12/site-packages
COPY . .
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
- hosts: app
  become: true
  tasks:
    - name: Pull nouvelle image
      community.docker.docker_compose:
        project_src: /srv/app/
        pull: yes
        build: no
        state: present
        restarted: yes

    - name: Laravel migrate (si PHP)
      command: docker exec app-php php artisan migrate --force
      when: laravel | default(false)
version: '3.7'
services:
  nexus:
    image: sonatype/nexus3:latest
    container_name: nexus
    restart: always
    ports:
      - "8082:8081"
    volumes:
      - ./nexus-data:/nexus-data
<?php
namespace Deployer;

require 'recipe/laravel.php';

// Nom du projet
set('application', 'MonSuperLaravel');

// Repo Git
set('repository', 'git@gitea:moncompte/monrepo.git');

// Branch par défaut
set('branch', 'main');

// Hosts (serveurs cibles)
host('production')
    ->setHostname('monserveur.example.com')
    ->setRemoteUser('deploy')
    ->setPort(22)
    ->set('deploy_path', '/var/www/{{application}}');

// Options Laravel
set('keep_releases', 5);
set('php_fpm_service', 'php8.3-fpm');

// Hooks personnalisés
after('deploy:failed', 'deploy:unlock');

// Tâche de migration auto
after('deploy:symlink', 'artisan:migrate');
deploy:production:
  stage: deploy
  image: php:8.3-cli
  script:
    - composer install --no-dev
    - php vendor/bin/dep deploy production -vvv
  only:
    - main
Write a comment
Your email address will not be published. Required fields are marked *
Scroll