Pasar al contenido principal
Please wait...
Image

CI/CD casero: buffet libre, food-truck gourmet… ¿o cocina de 3 estrellas?

En OBI Partner nos encanta programar en Python y PHP. Pero seamos honestos: el momento en que todo pasa a producción suele parecerse más a un sketch cómico que a una receta de cocina bien engrasada.
Entre los servidores que echan humo, las bases de datos que lloran y los devs jurando “pero en mi compu sí funciona”… había que encontrar un método mejor.

Ahí entra en escena el CI/CD auto-hospedado. Sin SaaS metiches, sin depender de una nube caprichosa: todo corre en tu propia infraestructura.

Y la buena noticia: te tengo preparadas varias recetas 🔥.

🥘 Opción A: GitLab CE (el buffet libre)

GitLab es el buffet chino del DevOps: encuentras de todo. Forge, issues, merge requests, pipelines, registry Docker, monitoreo… solo faltan los rollitos primavera.

Por qué amarlo

  • All-in-one: una sola interfaz, sin perseguir 10 herramientas distintas.

  • Pipeline poderoso: GitLab Runner hace de todo (Docker, Kubernetes, SSH).

  • Registry integrado: sin hacks para guardar imágenes.

  • Gran ecosistema: si pides ayuda con GitLab, nunca estás solo.

Por qué sufrir con él

  • Consume RAM como loco: GitLab es un ogro. Si no le das 16 GB, te mira como si intentaras alimentarlo con una ensalada.

  • Monolítico: te llevas el paquete entero, aunque no uses la mitad.

docker-compose GitLab CE

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

(Consejito: ponlo en un servidor dedicado, o va a estrangular a tus otros servicios como boa).

 

🌮 Opción B: Gitea + Woodpecker + Harbor (el food-truck gourmet)

Si GitLab es el buffet, Gitea + Woodpecker + Harbor es el food-truck gourmet. Cada herramienta es ligera, rápida y tú eliges qué poner en tu plato.

Por qué amarlo

  • Ultra ligero: Gitea arranca más rápido que un microondas.

  • Modular: usas solo lo que necesitas.

  • Woodpecker CI: pipelines fáciles de leer y mantener.

  • Harbor: registry con escáner integrado (tu guardaespaldas digital).

Por qué sufrir con él

  • Más fontanería: tienes que conectar las piezas tú mismo.

  • Menos integraciones: no hay gestión de proyectos avanzada como en GitLab.

docker-compose Gitea + Woodpecker + Harbor

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

 

👨‍🍳 Receta bonus: Pipelines CI/CD para Python + PHP

👉 El objetivo es simple: verificar tu código (lint), probarlo (unit tests) y luego desplegarlo.

GitLab CI

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

Woodpecker CI

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

 

🍱 Dockerfiles multi-stage

Laravel (PHP)

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
 

FastAPI (Python)

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"]
 

 

🚚 Despliegue automatizado con Ansible (cero downtime)

Playbook

- 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)

👉 Corre dos contenedores en paralelo (app_v1 y app_v2) y deja que Traefik haga rolling update.

🔒 Modo Air-Gapped (el bunker high-tech)

Algunas empresas quieren CI/CD en modo bunker: sin internet, sin dependencias que se vayan a la nube gringa, todo queda detrás de tus muros.
Suena a la serie Dark, pero funciona.

Ingredientes secretos

  • Nexus OSS: para espejar PyPI, Composer, npm, Docker Hub.

  • Harbor: para guardar imágenes Docker internas.

  • Runners: siempre usando imágenes internas.

  • Ansible: para desplegar en VMs o clusters sin hablar con el exterior.

docker-compose Nexus

version: '3.7'
services:
  nexus:
    image: sonatype/nexus3:latest
    container_name: nexus
    restart: always
    ports:
      - "8082:8081"
    volumes:
      - ./nexus-data:/nexus-data

(Tip: configura Nexus en modo proxy → baja las librerías una vez, y luego las sirve como buen maestro quesero 🧀).

🎩 Despliegue de lujo: Deployer PHP para Laravel

A veces no quieres pelearte con Docker Compose, Ansible o Kubernetes.
Solo quieres que tu código Laravel llegue al servidor rápido, limpio y con migraciones ejecutadas sin sudar frío.

Ahí brilla Deployer PHP.

Imagina: un archivo deploy.php, un dep deploy production, y tu app Laravel servida como plato Michelin 🍽️.

Installaciòn

composer require deployer/deployer --dev

Ejemplo de deploy.php

<?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');
 
  • Clona el código desde Git (Gitea, GitLab, lo que uses).

  • Instala dependencias con Composer.

  • Optimiza Laravel (config:cache, route:cache).

  • Despliega en un folder versionado releases/.

  • Actualiza el symlink → despliegue instantáneo, sin downtime.

  • Ejecuta migraciones automáticamente.

👉 Lo integras a tu pipeline y listo: commit → pipeline → Deployer → producción al día.
Sin git pull torpes por SSH a las 2 AM.

Conclusión

➡️ GitLab CE = buffet libre: todo incluido, pero traga RAM como si nada.
➡️ Gitea + Woodpecker = food-truck gourmet: ligero, rápido, sabroso, pero con más bricolaje.
➡️ Air-Gapped Mode = bunker high-tech: ideal para sectores sensibles (industria, defensa, salud).
➡️ Deployer PHP = cocina de 3 estrellas: despliegue Laravel refinado, como servicio con guantes blancos.

En OBI Partner decimos:
“No importa la forja, mientras la producción salga caliente, sin humo y sin pizza fría.” 🔥

👉 ¿Y tú? ¿Prefieres buffet , food-truck artesanal, cocina en bunker o servicio de lujo con Deployer PHP?

Añadir nuevo comentario

HTML Restringido

  • Puede alinear imágenes (data-align="center") pero también videos, citas, y demás.
  • No sólo puede subtitular imágenes (data-caption="Text"), sino también videos, blockquotes, y mucho más.