# Introducción a Azure Pipelines con Docker


> Esta guía es una contribución de la comunidad. A Docker le gustaría agradecer a [Kristiyan Velkov](https://www.linkedin.com/in/kristiyan-velkov-763130b3/) por su valiosa contribución.

## Prerrequisitos

Antes de comenzar, asegúrate de cumplir con los siguientes requisitos:

- Una [cuenta de Docker Hub](https://hub.docker.com) con un token de acceso generado.
- Un [proyecto activo de Azure DevOps](https://dev.azure.com/) con un [repositorio Git](https://learn.microsoft.com/en-us/azure/devops/repos/git/?view=azure-devops) conectado.
- Un proyecto que incluya un [`Dockerfile`](https://docs-docker.esdocu.com/engine/reference/builder/) válido en su raíz o en el contexto de compilación adecuado.

## Descripción general

Esta guía te acompaña a través de la compilación y subida de imágenes de Docker usando [Azure Pipelines](https://azure.microsoft.com/en-us/products/devops/pipelines), lo que permite un flujo de trabajo de CI simplificado y seguro para aplicaciones contenedorizadas. Aprenderás a:

- Configurar la autenticación de Docker de forma segura.
- Configurar un pipeline automatizado para compilar y subir imágenes.

## Configurar Azure DevOps para trabajar con Docker Hub

### Paso 1: Configurar una conexión de servicio de Docker Hub

Para autenticarse de forma segura en Docker Hub utilizando Azure Pipelines:

1. Navega a **Project Settings > Service Connections** en tu proyecto de Azure DevOps.
2. Selecciona **New service connection > Docker Registry**.
3. Selecciona **Docker Hub** y proporciona tus credenciales o el token de acceso de Docker Hub.
4. Asigna un nombre reconocible a la conexión de servicio, como `my-docker-registry`.
5. Concede acceso únicamente a los pipelines específicos que lo requieran para mejorar la seguridad y aplicar el principio de menor privilegio.

> [!IMPORTANT]
>
> Evita seleccionar la option para conceder acceso a todos los pipelines a menos que sea absolutamente necesario. Aplica siempre el principio de menor privilegio.

### Paso 2: Crear tu pipeline

Añade el siguiente archivo `azure-pipelines.yml` en la raíz de tu repositorio:

```yaml
# Activa el pipeline con los commits en la rama main
trigger:
  - main

# Activa el pipeline en pull requests destinados a la rama main
pr:
  - main

# Define variables para su reutilización en todo el pipeline
variables:
  imageName: 'docker.io/$(dockerUsername)/my-image'
  buildTag: '$(Build.BuildId)'
  latestTag: 'latest'

stages:
  - stage: BuildAndPush
    displayName: Build and Push Docker Image
    jobs:
      - job: DockerJob
        displayName: Build and Push
        pool:
          vmImage: ubuntu-latest
          demands:
            - docker
        steps:
          - checkout: self
            displayName: Checkout Code

          - task: Docker@2
            displayName: Docker Login
            inputs:
              command: login
              containerRegistry: 'my-docker-registry'  # Nombre de la conexión de servicio

          - task: Docker@2
            displayName: Build Docker Image
            inputs:
              command: build
              repository: $(imageName)
              tags: |
                $(buildTag)
                $(latestTag)
              dockerfile: './Dockerfile'
              arguments: |
                --sbom=true
                --attest type=provenance
                --cache-from $(imageName):latest
            env:
              DOCKER_BUILDKIT: 1

          - task: Docker@2
            displayName: Push Docker Image
            condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
            inputs:
              command: push
              repository: $(imageName)
              tags: |
                $(buildTag)
                $(latestTag)

          # Opcional: cerrar sesión para agentes auto-alojados
          - script: docker logout
            displayName: Docker Logout (Self-hosted only)
            condition: ne(variables['Agent.OS'], 'Windows_NT')
```

## Qué hace este pipeline

Este pipeline automatiza el proceso de compilación y despliegue de imágenes de Docker para la rama main. Garantiza un flujo de trabajo seguro y eficiente con buenas prácticas como el almacenamiento en caché, el etiquetado y la limpieza condicional. Esto es lo que hace:

- Se activa con los commits y pull requests destinados a la rama `main`.
- Se autentica de forma segura con Docker Hub mediante una conexión de servicio de Azure DevOps.
- Compila y etiqueta la imagen de Docker utilizando Docker BuildKit para el almacenamiento en caché.
- Sube tanto las etiquetas de buildId como latest a Docker Hub.
- Cierra la sesión de Docker si se ejecuta en un agente Linux auto-alojado.

## Cómo funciona el pipeline

### Paso 1: Definir los activadores (triggers) del pipeline

```yaml
trigger:
  - main

pr:
  - main
```

Este pipeline se activa automáticamente en:
- Commits subidos a la rama `main`
- Pull requests destinados a la rama principal `main`

> [!TIP]
> Más información: [Definir activadores de pipeline en Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops)

### Paso 2: Definir variables comunes

```yaml
variables:
  imageName: 'docker.io/$(dockerUsername)/my-image'
  buildTag: '$(Build.BuildId)'
  latestTag: 'latest'
```

Estas variables garantizan la coherencia en el nombre, la versión y la reutilización en todos los pasos del pipeline:

- `imageName`: la ruta de tu imagen en Docker Hub
- `buildTag`: una etiqueta única para cada ejecución del pipeline
- `latestTag`: un alias estable para tu imagen más reciente

> [!IMPORTANT]
>
> La variable `dockerUsername` no se establece automáticamente.  
> Configúrala de forma segura en las variables de tu pipeline de Azure DevOps:  
>   1. Ve a **Pipelines > Edit > Variables**  
>   2. Añade `dockerUsername` con tu nombre de usuario de Docker Hub  
>
> Más información: [Definir y utilizar variables en Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch)

### Paso 3: Definir etapas (stages) y trabajos (jobs) del pipeline

```yaml
stages:
  - stage: BuildAndPush
    displayName: Build and Push Docker Image
```

Esta etapa se ejecuta solo si la rama de origen es `main`.

> [!TIP]
>
> Más información: [Condiciones de etapa en Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/stages?view=azure-devops&tabs=yaml)

### Paso 4: Configuración del trabajo (job)

```yaml
jobs:
  - job: DockerJob
  displayName: Build and Push
  pool:
    vmImage: ubuntu-latest
    demands:
      - docker
```

Este trabajo utiliza la última imagen de VM de Ubuntu con soporte para Docker, proporcionada por los agentes alojados por Microsoft. Se puede reemplazar con un grupo (pool) personalizado para agentes auto-alojados si es necesario.

> [!TIP]
>
> Más información: [Especificar un pool en tu pipeline](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/pools-queues?view=azure-devops&tabs=yaml%2Cbrowser)

#### Paso 4.1: Descargar código (Checkout)

```yaml
steps:
  - checkout: self
    displayName: Checkout Code
```

Este paso descarga el código de tu repositorio en el agente de compilación, de modo que el pipeline pueda acceder al Dockerfile y a los archivos de la aplicación.

> [!TIP]
>
> Más información: [Documentación del paso checkout](https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/steps-checkout?view=azure-pipelines)

#### Paso 4.2: Autenticarse en Docker Hub

```yaml
- task: Docker@2
  displayName: Docker Login
  inputs:
    command: login
    containerRegistry: 'my-docker-registry'  # Reemplaza con el nombre de tu conexión de servicio
```

Utiliza una conexión de servicio del registro de Docker preconfigurada en Azure DevOps para autenticarse de forma segura sin exponer las credenciales directamente.

> [!TIP]
>
> Más información: [Usar conexiones de servicio para Docker Hub](https://learn.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops#docker-hub-or-others)

#### Paso 4.3: Compilar la imagen de Docker

```yaml
 - task: Docker@2
    displayName: Build Docker Image
    inputs:
      command: build
      repository: $(imageName)
      tags: |
          $(buildTag)
          $(latestTag)
      dockerfile: './Dockerfile'
      arguments: |
          --sbom=true
          --attest type=provenance
          --cache-from $(imageName):latest
    env:
      DOCKER_BUILDKIT: 1
```

Esto compila la imagen con:

- Dos etiquetas: una con el ID de compilación único (Build ID) y otra como latest
- Docker BuildKit habilitado para compilaciones más rápidas y un almacenamiento en caché de capas eficiente
- Obtención de caché (cache pull) de la imagen latest subida más recientemente
- Lista de materiales de software (SBOM) para la transparencia de la cadena de suministro
- Atestación de procedencia (provenance attestation) para verificar cómo y dónde se compiló la imagen

> [!TIP]
>
> Más información: 
> - [Tarea de Docker para Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/docker-v2?view=azure-pipelines&tabs=yaml)
> - [Atestaciones SBOM de Docker](/build/metadata/attestations/slsa-provenance/)

#### Paso 4.4: Subir (push) la imagen de Docker

```yaml
- task: Docker@2
  displayName: Push Docker Image
  condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
  inputs:
      command: push
      repository: $(imageName)
      tags: |
        $(buildTag)
        $(latestTag)
```

Al aplicar esta condición, el pipeline compila la imagen de Docker en cada ejecución para garantizar la detección temprana de problemas, pero solo sube la imagen al registro cuando los cambios se fusionan (merge) en la rama main, manteniendo tu Docker Hub limpio y enfocado.

Esto sube ambas etiquetas a Docker Hub:
- `$(buildTag)` garantiza la trazabilidad por ejecución.
- `latest` se utiliza para las referencias a la imagen más reciente.

#### Paso 4.5 Cerrar sesión de Docker (agentes auto-alojados)

```yaml
- script: docker logout
  displayName: Docker Logout (Self-hosted only)
  condition: ne(variables['Agent.OS'], 'Windows_NT')
```

Ejecuta docker logout al final del pipeline en agentes auto-alojados basados en Linux para limpiar proactivamente las credenciales y mejorar la postura de seguridad.

## Resumen

Con esta configuración de CI de Azure Pipelines, obtienes:

- Autenticación segura de Docker mediante una conexión de servicio integrada.
- Compilación y etiquetado automático de imágenes activados por los cambios de código.
- Compilaciones eficientes aprovechando la caché de Docker BuildKit.
- Limpieza segura con cierre de sesión en agentes persistentes.
- Compilación de imágenes que cumplen con los requisitos modernos de la cadena de suministro de software con SBOM y atestaciones.

## Más información

- [Documentación de Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/?view=azure-devops): Guía completa para configurar y gestionar pipelines de CI/CD en Azure DevOps.
- [Tarea de Docker para Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/build/docker): Referencia detallada para usar la tarea de Docker en Azure Pipelines para compilar y subir imágenes.
- [Docker Buildx Bake](/build/bake/): Explora la herramienta de compilación avanzada de Docker para configuraciones de compilación complejas, multi-etapa y multi-plataforma. Consulta también la [Guía de dominio de Buildx Bake](/guides/bake/) para ver ejemplos prácticos y buenas prácticas.
- [Docker Build Cloud](/guides/docker-build-cloud/): Aprende sobre el servicio de compilación administrado de Docker para compilaciones de imágenes más rápidas, escalables y multi-plataforma en la nube.

