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.sockversion: '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:/datastages: [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/phpunitpipeline:
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/phpunitFROM 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 opcacheFROM 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