Despliegue de aplicaciones con Docker-Compose

Despliegue de aplicaciones con Docker-Compose

En este tutorial vamos a aprender a desplegar nuestros proyectos en contenedores Docker de manera sencilla y rápida, utilizando docker-compose.

Características del SO

  • Ubuntu 20.04
  • Docker 20.10.12
  • Docker-compose 1.29.2
  • Node.js v18.12.1
  • NPM 8.19.2

¿Qué es docker-compose?

Docker Compose es una herramienta de Docker que orquesta contenedores en un mismo cliente. Consiste en un archivo de texto en formato YAML, que define de forma declarativa los contenedores que se van a desplegar, así como las dependencias entre ellos.

Al utilizar el paradigma declarativo, en este documento sólo se especifican las características de los contenedores deseados, y no cómo se despliegan. Para el despliegue, se basa en la definición de servicios, que referencian imágenes Docker de un registro y las características de los contenedores que se desean desplegar.

Este archivo normalmente tiene el nombre

Text
docker-compose.yml

pero puede tener cualquier otro nombre, siempre que se especifique al ejecutar el comando

Text
docker-compose

con el argumento

Text
-f

. La utilidad más habitual para esta herramienta es el despliegue de aplicaciones en entornos locales, para el desarrollo y pruebas. Sin embargo, también puede utilizarse para desplegar aplicaciones en entornos de producción, aunque en este caso se recomienda utilizar herramientas de orquestación como

Text
Kubernetes

.

Instalación

Para instalar docker-compose, primero necesitamos tener instalada la herramienta Docker en nuestro sistema. Después, en Ubuntu, ejecutamos el siguiente comando:


Text
sudo apt install docker-compose

En el caso de Mac, podemos utilizar

Text
Homebrew

de la siguiente manera:


Text
brew install docker-compose

Y en el caso de Windows, podemos descargar el instalador de Docker Desktop de la página oficial y seguir las instrucciones, aunque recomendamos el uso de sistemas Linux o Mac.

Servicios y recursos

Dentro del archivo

Text
docker-compose.yml

podemos definir diferentes recursos que desplegar. A la hora de desplegar nuestras aplicaciones, lo primero es definir los servicios que vamos a desplegar.

Servicios

Estos servicios son los que conforman nuestra aplicación y, normalmente, cada uno despliega un contenedor, asociado a una imagen Docker. Dentro de cada servicio podemos definir las características de cada contenedor, como el nombre, la imagen, los puertos que expone, volúmenes y redes a las que se conecta, etc.

Para definir un servicio en el archivo

Text
docker-compose.yml

, debemos definir un bloque con el nombre del servicio, y dentro una serie de propiedades que definen las características del contenedor:

  • Text
    image

    : Nombre de la imagen Docker usada para desplegar el contenedor.

  • Text
    [container_name]

    : Nombre del contenedor a desplegar.

  • Text
    [build]

    : Ruta al directorio donde se encuentra el Dockerfile para construir la imagen, si no está construida.

  • Text
    [command]

    : Comandos que ejecutar al iniciar el contenedor.

  • Text
    [ports]

    : Puertos que exponer al exterior del contenedor. Tienen el formato

    Text
    puerto_host:puerto_contenedor

    , donde, como host, podremos acceder al servicio desde

    Text
    localhost:puerto_host

    .

  • Text
    [volumes]

    : Volúmenes que montar en el contenedor.

  • Text
    [environment]

    : Variables de entorno que definir en el contenedor. Es recomendable configurar nuestros contenedores con variables de entorno y no valores fijos, para poder cambiar la configuración de los servicios sin tener que reconstruir las imágenes.

  • Text
    [depends_on]

    : Servicios que deben desplegarse antes que este.

  • Text
    [networks]

    : Redes a las que pertenece el contenedor.

  • Text
    [restart]

    : Política de reinicio del contenedor. Puede tomar los valores

    Text
    no

    ,

    Text
    always

    ,

    Text
    on-failure

    ,

    Text
    unless-stopped

    , por defecto

    Text
    no

    .

Todas las propiedades entre corchetes son opcionales, y si no se especifican, se toman valores por defecto. Es decir, sólo con definir la propiedad

Text
image

ya se peude desplegar un contenedor con la imagen especificada.

Volúmenes

Cuando hablamos de volúmen nos referimos a los volúmenes de Docker, el mecanismo que tienen los contenedores para persistir y compartir datos. Para ello, consumen o generan ficheros en un directorio del sistema de ficheros del host.

Para definir un volumen en el archivo

Text
docker-compose.yml

, debemos definir un bloque con el nombre del volumen, y luego referenciarlo en los servicios que lo necesiten. Dentro de este bloque podemos definir las siguientes propiedades:

  • Text
    [driver]

    : Driver del volumen. Por defecto,

    Text
    local

    .

  • Text
    [driver_opts]

    : Opciones del driver del volumen.

    • Text
      type

      : Tipo de volumen. Puede ser

      Text
      none

      ,

      Text
      bind

      ,

      Text
      volume

      o

      Text
      tmpfs

      .

    • Text
      device

      : Ruta al directorio del host que se va a montar.

    • Text
      o

      : Opciones de montaje del volumen. Puede tomar valores

      Text
      bind

      ,

      Text
      private

      ,

      Text
      ro

      ,

      Text
      rw

      y

      Text
      shared

      , entre otros.

  • Text
    [external]

    : Indica si el volumen es externo o no. Por defecto,

    Text
    false

    .

  • Text
    [labels]

    : Etiquetas del volumen.

  • Text
    [name]

    : Nombre del volumen.

  • Text
    [scope]

    : Alcance del volumen. Por defecto,

    Text
    local

    .

Como podemos ver, todas las propiedades son opcionales, pero es recomendable definir al menos un nombre y la ruta en el sistema de archivos del host. Un ejemplo de volumen con estas propiedades sería:


Text
volumes:
  my-volume:
    driver: local
    driver_opts:
      type: none
      device: /path/to/my-volume
      o: bind
    external: false
    labels:
      - "com.example.description=Volume for my service"
    name: my-volume
    scope: local

Referenciado en el servicio como:


Text
services:
  my-service:
    image: my-image
    volumes:
      - my-volume:/path/in/container

Recuerda que la ruta que definas en la propiedad

Text
device

del bloque

Text
volumes

debe existir en el sistema de archivos del host. No importa si es una ruta absoluta

Text
/path/to/my-volume

o relativa

Text
../path/to/my-volume

, siempre que exista.

Redes

Cuando desplegamos aplicaciones con múltiples módulos, por ejemplo, una aplicación web con un servidor web y una base de datos, las alojamos en contenedores separados. Para que estos contenedores puedan comunicarse entre sí, necesitamos definir una red en común, siguiendo el modelo

Text
Container Network Model

:

  • Text
    SandBox

    : Aisla el contenedor del resto del sistema, limitando el acceso a esta red al tráfico que llega por los

    Text
    endpoints

    .

  • Text
    Endpoints

    : Puntos de comunicación entre las redes aisladas de los contenedores y la

    Text
    network

    que los conecta con el resto del sistema.

  • Text
    Network

    : Red que comunica las

    Text
    sandbox

    de los diferentes contenedores por medio de los

    Text
    endpoints

    .

Siguiendo este modelo existen varias implementaciones en Docker:

Nombre Alcance Descripción
Text
bridge
Local Red por defecto de Docker. Crea una red virtual en el host, que conecta los contenedores por medio de un

Text
bridge

virtual.

Text
host
Local Deshabilita el aislamiento entre los contenedores y el host,

Text
no hace falta exponer puertos
Text
overlay
Global Permite conectar múltiples demonios Docker entre sí y habilitar la

Text
comunicación entre servicios distribuidos

.

Text
ipvlan
Global El usuario obtiene el control total del

Text
direccionamiento IPv4 e IPv6

de la red.

Text
macvlan
Global Permite asignarle una

Text
dirección MAC

a un contenedor, haciendo que aparezca como un dispositivo físico en la red. El tráfico se direcciona por MAC.

Text
none
Local Dehabilita toda la gestión de red, normalmente usado en conjunto con un driver de red propio.

Estas implementaciones se definen en el campo

Text
driver

de la sección

Text
networks

del archivo

Text
docker-compose.yml

. Por defecto, si no se especifica, se utiliza la red

Text
bridge

.

Entonces, para definir una red común entre contenedores, debemos definirla en la sección

Text
networks

y referenciarla en los servicios que la necesiten. Recuerda que cada red, según su driver, se puede definir con diferentes opciones.

Ejemplo

Vamos a crear un ejemplo sencillo, que consiste en un servicio web que utiliza una base de datos. Para ello, el archivo

Text
docker-compose.yml

quedaría de la siguiente manera:


Text
version: '3.7' # Versión de docker-compose, depende de Docker
services:
  web:
    image: nginx
    ports:
      - 80:80
    volumes:
      - web_data:/usr/share/nginx/html
    networks:
      - web_net
  db:
    image: postgres
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - web_net
volumes:
    web_data:
    db_data:
networks:
    web_net:
        driver: bridge

En el ejemplo podemos observar dos servicios:

Text
web

, que despliega un contenedor nginx exponiendo el puerto 80, y

Text
db

, que despliega un contenedor postgresql, sólo accesible desde la red que comparten ambos contenedores,

Text
web_net

. Esta red está declarada en la sección

Text
networks

como una red de tipo

Text
bridge

. Por otro lado, cada contenedor tiene asociado un volumen, definido en la sección

Text
volumes

, y referenciado en el servicio.

Dependencias entre servicios

Ahora que ya sabemos cómo definir los servicios de nuestra aplicación y sus recursos asociados, vamos a ver cómo podemos definir las dependencias entre ellos. Un servicio depende de otro cuando necesita que el segundo esté desplegado antes de poder desplegarse. Por ejemplo, si tenemos un servicio que despliega una API REST conectada a una base de datos, necesitamos que la base de datos esté desplegada antes de desplegar el servicio que la utiliza.

Para definir estas dependencias en el fichero

Text
docker-compose.yml

podemos utilizar el campo

Text
depends_on

en los servicios de la sección

Text
services

. Este campo es un array de cadenas, donde cada cadena es el nombre del servicio del que depende el servicio actual.

Veamos el ejemplo anterior, pero esta vez definiendo las dependencias entre web y db:


Text
version: '3.7' # Versión de docker-compose, depende de Docker
services:
  web:
    image: nginx
    ports:
      - 80:80
    volumes:
      - web_data:/usr/share/nginx/html
    networks:
      - web_net
    depends_on: # Ahora web depende de db
      - db
  db:
    image: postgres
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - web_net
volumes:
    web_data:
    db_data:
networks:
    web_net:
        driver: bridge

De manera que, al desplegar la aplicación con docker-compose, primero se desplegará el servicio

Text
db

y, si ha tenido éxito, se desplegará el servicio

Text
web

.

Comandos de docker-compose

Una vez definido el fichero

Text
docker-compose.yml

, los siguientes comandos son útiles para gestionar el despliegue de nuestra aplicación y sus recursos asociados.

NOTA: Todos los comandos siguientes están siendo ejecutados desde el directorio donde se encuentra el fichero

Text
docker-compose.yml

, de lo contrario, hay que especificar la ruta completa al fichero con el parámetro

Text
-f

.

Text
docker-compose up

Sirve para desplegar la aplicación. Si no se especifica ningún servicio, se desplegarán todos los servicios definidos en el fichero

Text
docker-compose.yml

. Si se especifica un servicio, se desplegará sólo ese servicio y sus dependencias.


Text
docker-compose up

Con el parámetro

Text
-d

el despliegue se hará en segundo plano.

Text
docker-compose down

Aunque no hayamos utilizado el parámetro

Text
-d

en el comando

Text
docker-compose up

, para detener la ejecución de los contenedores correctamente debemos ejecutar el comando:


Text
docker-compose down

Text
docker-compose ps

Sirve para listar los servicios desplegados, y sus contenedores asociados, y ver su estado actual junto con los puertos que tienen expuestos. Es muy útil para comprobar si el despliegue definido en el fichero

Text
docker-compose.yml

es el esperado:


Text
docker-compose ps

Text
docker-compose logs

Sirve para ver los logs de los contenedores desplegados. Si no se especifica ningún nombre de servicio, se mostrarán los logs de todos los contenedores. Si se especifica un servicio, se mostrarán los logs de los contenedores asociados a ese servicio.

Es muy útil si un servicio ha fallado al desplegarse o simplemente para monitorizar el funcionamiento de dicho servicio.


Text
docker-compose logs

Text
docker-compose exec

Sirve para ejecutar un comando en un contenedor desplegado. Es muy útil si queremos conectarnos al contenedor de un servicio para depurar algún tipo de errores. Por ejemplo, si queremos obtener una shell en el servicio

Text
web

:


Text
docker-compose exec web bash

Text
docker-compose build

o

Text
docker-compose pull

Text
build

sirve para reconstruir las imágenes de los servicios definidos en el fichero

Text
docker-compose.yml

. Es muy útil si hemos modificado el fichero

Text
Dockerfile

de algún servicio y queremos reconstruir la imagen asociada a dicho servicio.


Text
docker-compose build

Por otro lado, si nuestras imágenes vienen de un registro,

Text
pull

sirve para descargar las imágenes de los servicios definidos en el fichero

Text
docker-compose.yml

desde el registro.


Text
docker-compose pull

Comandos docker

Como docker-compose es una herramienta de Docker, podemos inspeccionar todos los contenedores con el comando:


Text
docker container inspect <container_id>

De la misma forma podemos inspeccionar y modificar

Text
imágenes

,

Text
volumenes

y

Text
redes

con los comandos:


Text
docker image inspect <image_id>
docker volume inspect <volume_id>
docker network inspect <network_id>

Sin embargo, es recomendable utilizar los comandos de docker-compose para gestionar los recursos de nuestra aplicación, ya que docker-compose gestiona los recursos de forma automática y nos evita tener que conocer los identificadores de los recursos.

Ejemplo práctico

Ahora que ya somos expertos en docker-compose y tenemos una chuleta de comandos que podemos utilizar, vamos a desplegar una aplicación de ejemplo para poner a prueba nuestros nuevos conocimientos.

1. Aplicaciones de ejemplo

Para este ejemplo vamos a utilizar dos aplicaciones de ejemplo:

  • Text
    Servidor web

    : Una API REST, en nodejs, que nos permite consultar el estado de una base de datos de coches.

  • Text
    Base de datos

    : Una base de datos MySQL que contiene la tabla con información sobre coches.

En un proyecto real programaréis vuestro propio servicio web, pero en este caso vamos a clonar la apliación del siguiente repositorio. Para ello, ejecutamos el siguiente comando:


Text
# Podemos clonar por https
https://github.com/nicolaemolnar/dockerizeNode.git
# o clonar por ssh
git clone git@github.com:nicolaemolnar/dockerizeNode.git

Una vez clonado el repositorio, tendremos el directorio

Text
dockerizeNode

con un servidor en express que maneja la base de datos mencionada. La implementación de este servidor no es importante, lo que sí es importante es el uso de variables de entorno en lugar de variables fijas:


Text
const db = mysql.createConnection({
  host: process.env.DB_HOST || 'localhost',
  user: process.env.DB_USER || 'admin',
  password: process.env.DB_PASSWORD || '1234',
  database: process.env.DB_NAME || 'cars'
});

El nombre de estas variables de entorno es importante ya que deben coincidir en la definición del parámetro

Text
environment

del fichero, en el fichero

Text
docker-compose.yml

.

Por otro lado, tenemos un contenedor de base de datos MySQL que debe contener una base de datos

Text
cars

y una tabla

Text
cars

con los siguientes campos:

  • Text
    id

    : Clave primaria de tipo entero

  • Text
    name

    : Nombre del coche

  • Text
    model

    : Modelo del coche

  • Text
    price

    : Precio del coche

Para ello, al dockerizar la aplicación necesitaremos un fichero

Text
SQL

que sirva de entrypoint. Este fichero contendrá la creación de la base de la tabla coches y la configuración inicial de la BBDD:


Text
-- Creación de base de datos (si no existe)
CREATE DATABASE IF NOT EXISTS cars;
USE cars;

-- Creación de la tabla cars
CREATE TABLE IF NOT EXISTS cars (
  id INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL,
  model VARCHAR(255) NOT NULL,
  price INT NOT NULL,
  PRIMARY KEY (id)
);

-- Carga inicial de la aplicación
INSERT INTO cars (name, model, price) VALUES
('Audi', 'A4', 30000),
('BMW', 'X5', 50000),
('Mercedes', 'C200', 40000);

Si no seguir el proceso de creación de imágenes docker podéis utilizar la imágen pública

Text
nicolaemolnar/dockerize-node

para el servidor web y

Text
mysql:5.7

para la base de datos, y pasar directamente al paso 4.

2. Contenerización con Docker

Ahora que ya tenemos la aplicación lista para desplegar, necesitamos crear las imágenes que contengan, por separado, el servicio web y la base de datos. Para ello, vamos a crear un fichero

Text
Dockerfile

para cada servicio.

Para dockerizar el servicio web, creamos un fichero

Text
Dockerfile

en el directorio

Text
dockerizeNode

con el siguiente contenido:


Text
FROM node:12.18.3-alpine3.9
WORKDIR /app
COPY src/ /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

Con el que copiaremos el código fuente de la carpeta

Text
src

al contenedor, instalaremos sus depencias y ejecutaremos el servicio web. Ya tenemos listo el Dockerfile, ahora sólo necesitamos crear la imagen con el comando:


Text
docker build -t dockerize-node .

Donde el parámetro

Text
-t

define el tag de la imagen, que nos sirve a nosotros y a otros contenedores para identificar la imagen que hemos creado. En el ejemplo, el

Text
.

se refiere a la ruta actual, si quisieramos utilizar algún otro directorio, deberíamos indicar su ruta.

Podemos comprobar que la imagen se ha creado correctamente con el comando:


Text
docker images

Por otro lado, para dockerizar la base de datos, no es necesaria una imagen Docker personalizada, ya que con la imagen

Text
mysql:5.7

podemos desplegar la base de datos, definiendo las variables de entorno necesarias. Esto lo haremos en el paso 4.

3. Subir imagenes a un registro

Ahora que ya tenemos creada la imagen en nuestro equipo, necesitamos subirla a un registro para poder desplegarla en un servidor. Para ello, vamos a utilizar el registro de Docker Hub, que es un registro público de imágenes Docker.

Necesitamos crear una cuenta en Docker Hub y crear un repositorio público en la sección Repositories con el nombre

Text
dockerize-node

.

Una vez creado el repositorio, podemos subir la imagen a Docker Hub con el comando:


Text
docker tag dockerize-node <username>/dockerize-node
docker push <username>/dockerize-node

Donde

Text
<username>

corresponde a vuestro nombre de usuario de DockerHub o el registro utilizado. Sin embargo, antes de ejecutar el comando

Text
docker push

, necesitamos iniciar sesión en Docker Hub con el comando:


Text
docker login

Que nos solicitará el nombre de usuario y contraseña con los que nos hemos registrado en la plataforma. Una vez logados, en mi caso, ejecuto:


Text
docker tag dockerize-node nicolaemolnar/dockerize-node
docker push nicolaemolnar/dockerize-node

Y ya tenemos la imagen subida a Docker Hub. Si no especificamos ninguna etiqueta en los comandos anteriores, se establece la etiqueta

Text
latest

, pero podemos versionar nuestras imágenes con los comandos:


Text
docker tag dockerize-node <username>/dockerize-node:<version>
docker push <username>/dockerize-node:<version>

4. Fichero de despliegue

Llegados a este punto tenemos todas las imágenes necesarias en el registro y podemos desplegarlas con docker-compose. Para ello, vamos a crear un fichero

Text
docker-compose.yml

en el directorio

Text
dockerizeNode

con el siguiente contenido:


Text
version: '3.7'
services:
  web:
    image: nicolaemolnar/dockerize-node
    ports:
      - 3000:3000
    environment:
      DB_HOST: db
      DB_USER: admin
      DB_PASSWORD: 1234
      DB_NAME: cars
    networks:
      - backend
  db:
    image: mysql:5.7
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: 1234
      MYSQL_DATABASE: cars
      MYSQL_USER: admin
      MYSQL_PASSWORD: 1234
    volumes:
      - ./db/entrypoint.sql:/docker-entrypoint-initdb.d/entrypoint.sql
    networks:
      - backend
networks:
  backend:
    driver: bridge

Este fichero define dos servicios, uno para el servicio web y otro para la base de datos. En el servicio web, definimos la imagen que vamos a utilizar, las variables de entorno necesarias para la conexión con la base de datos y el puerto que va a exponer al host.

Por otro lado, en el servicio de la base de datos, definimos la imagen que vamos a utilizar, las variables de entorno necesarias para la creación de la base de datos y el puerto que va a utilizar. Además, definimos un volumen que contiene el fichero

Text
entrypoint.sql

que creamos en el paso 1. Este fichero se ejecutará automáticamente al iniciar el contenedor de la base de datos y creará la base de datos y la tabla necesarias.

Por último, definimos una red interna para que los contenedores puedan comunicarse entre ellos. Observa que la variable de entorno

Text
DB_HOST

del servicio

Text
web

tiene un nombre y no una dirección IP. Esto se debe a que Docker crea un DNS interno para que los contenedores puedan comunicarse entre ellos utilizando los nombres de los servicios definidos en el fichero

Text
docker-compose.yml

, evitando tener que ir cambiando las direcciones IP cada vez que se crea un nuevo contenedor.

6. Despliegue

Para desplegar la aplicación, sólo necesitamos ejecutar el comando:


Text
docker-compose up -d

NOTA: Si la imagen

Text
mysql:5.7

no existe en nuestro equipo este comando tardará más de lo normal ya que tiene que descargar la imagen del registro.

Y si todo ha ido bien, ya podemos acceder a la aplicación en el puerto 3000 de nuestro servidor. Para comprobar que todo funciona correctamente, podemos ejecutar el comando:


Text
docker-compose ps

Que nos mostrará los contenedores que se han creado y su estado.

Para utilizar nuestra nueva aplicación podemos utilizar el comando

Text
curl

o Postman. Por ejemplo, podemos usar

Text
curl

para enumerar todos los coches:


Text
curl localhost:3000/api/cars

Obtener sólo uno:


Text
curl localhost:3000/api/cars/1

Y crear uno nuevo por el verbo

Text
POST

:


Text
curl -X POST -H "Content-Type: application/json" -d '{"name":"Ford","model":"Focus","price":15000}' localhost:3000/api/cars

Por último, para finalizar el despliegue, ejecutamos el comando:


Text
docker-compose down

Conclusiones

Como hemos visto, desplegar aplicaciones en contenedores con

Text
Docker

y

Text
docker-compose

es muy sencillo. La definición de servicios es intuitiva y docker nos ofrece un servicio de DNS que nos evita tener que configurar IPs a mano. Por eso, es importante programar nuestras aplicaciones con valores definidos en variables de entorno para que podamos cambiarlos fácilmente en el momento de desplegar la aplicación, en lugar de crear otra imagen Docker distinta.

Además, hemos aprendido a crear nuestra propia imagen Docker a partir de una aplicación, por medio de

Text
Dockerfile

, y a subirla a un registro para que pueda ser utilizada por otros usuarios.

Bibliografía

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

He leído y acepto la política de privacidad

Información básica acerca de la protección de datos

  • Responsable: IZERTIS S.A.
  • Finalidad: Envío información de carácter administrativa, técnica, organizativa y/o comercial sobre los productos y servicios sobre los que se nos consulta.
  • Legitimación: Consentimiento del interesado
  • Destinatarios: Otras empresas del Grupo IZERTIS. Encargados del tratamiento.
  • Derechos: Acceso, rectificación, supresión, cancelación, limitación y portabilidad de los datos.
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad

Estudiante de Ingeniería Informática, con mención en computación, en la Universidad de Alcalá de Henares.

¿Quieres publicar en Adictos al trabajo?

Te puede interesar

Aprende cómo migrar tu sitio Joomla 3 a Joomla 5 de forma segura, manteniendo el diseño, la funcionalidad y compatibilidad con extensiones. Una guía paso a paso con recomendaciones, imágenes y buenas prácticas para actualizar sin sorpresas.
Descubre qué es Yocto Project, sus ventajas, usos reales en Izertis y cómo crear tu propia distribución Linux para Raspberry Pi paso a paso, de forma sencilla y flexible.
¿Trabajas con Drupal y SonarQube 9.9? En este artículo exploramos cómo adaptar el análisis estático para evitar falsos positivos, desactivar reglas conflictivas del Quality Profile y delegar el estilo a PHP CodeSniffer. Una guía práctica para mejorar la integración sin depender aún de SonarQube 10.