Saltar a contenido

Introducción a Azure Pipelines con Docker

Esta actividad ha sido sacada de los laboratorios de "Play with Docker"

Tarea

Esta guía le mostrará cómo crear y desplegar imágenes Docker utilizando Azure Pipelines , lo que permite un flujo de trabajo de CI optimizado y seguro para aplicaciones en contenedores. Aprenderá a:

  • Configure la autenticación de Docker de forma segura.
  • Configura un proceso automatizado para crear y subir imágenes.

Requisitos previos

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

  • Una cuenta de Docker Hub con un token de acceso generado.
¿Cómo hacerlo?
  1. Entramos en: https://hub.docker.com
  2. Iniciamos sesión con la cuenta.
  3. Arriba a la derecha:

    • Hacemos clic en el icono de nuesto perfil
    • Vamos a "Account Settings"
  4. En el menú lateral buscamos "Personal access tokens"

  5. Le damos a "Generate new token" y lo creamos

acces token

IMPORTANTE

El token solo se muestra una vez así que, copialo y guárdalo.

  • Un proyecto activo de Azure DevOps con un repositorio Git conectado .
  • Un proyecto que incluye un Dockerfile válido en su directorio raíz o en un contexto de construcción adecuado.

Configuramos Azure DevOps para que funcione con Docker Hub.

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

Para autenticarse de forma segura con Docker Hub meidante Azure Pipelines:

  1. En el proyecto de Azure DevOps, ve a Configuración del proyecto > Conexiones de servicio.

    configuración

  2. Selecciona Nueva conexión de servicio > Registro de Docker.

    registro de docker

  3. Selecciona Docker Hub y proporcione sus credenciales o token de acceso de Docker Hub

  4. Asignamos a la conexión de servicio un nombre reconocible, como por ejemplo my-docker-registry

    service connection

  5. Concedemos acceso únicamente a las pipelines específicas que lo requieran para mejorar la seguridad y garantizar el principio de mínimo privilegio.

Importante

Debemos evitar la opción de otorgar acceso a todas las tuberías a menos que sea absolutamente necesario. Aplicaremos siempre el principio del mínimo provilegio.


Paso 2: Crea tu pipeline

Agregamos el archivo azure-pipelines.yml a la raíz de nuestro repositorio:

azure-pipelines.yml
# Trigger pipeline on commits to the main branch
trigger:
  - main

# Trigger pipeline on pull requests targeting the main branch
pr:
  - main

# Define variables for reuse across the 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:          # Quitamos esto porque provoca errores de ejecución ya que no encontraba un agente q cumpliera explícitamente  este requisito
        #    - docker
        steps:
          - checkout: self
            displayName: Checkout Code

          - task: Docker@2
            displayName: Docker Login
            inputs:
              command: login
              containerRegistry: 'my-docker-registry'  # Service connection name

          - task: Docker@2
            displayName: Build Docker Image
            inputs:
              command: build
              repository: $(imageName)
              tags: |
                $(buildTag)
                $(latestTag)
              dockerfile: './Dockerfile'
            #  arguments: |                 # Quitamos esto porque son funcionalidades avanzadas de seguridad que requieren el uso de Docker BuildKit con el driver docker-container. Además Docker@2 no soporta dichas funcionalidades.
            #    --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)

          # Optional: logout for self-hosted agents
          - script: docker logout
            displayName: Docker Logout (Self-hosted only)
            condition: ne(variables['Agent.OS'], 'Windows_NT')

Después lo subiremos a nuestro Azure DevOps como ya sabemos con git:

Comandos git para subir
    cd carpeta proyecto

    git add .
    git commit -m "fix: descripción"
    git push
    # Ahora en Azure DevOps aceptaremos el commit

Problema en el codigo

Si nos aparece el código en VSCode como erroneo lo que podemos hacer es crearlo directamente en Azure DevOps como Pipeline.


¿Qué hace este pipeline?

Automatiza el procceso de creación e implementación de imágenes Docker para la rama principal. Garantiza un flujo de trabajo seguro y eficiente con buensa prácticas como el almacenamiento en caché, el etiquetado y la limpieza condicional. Esto lo que hace es:

  • Se activan con las confirmaciones y las solicitudes de extracción dirigidas a la rama main
  • Se autentica de forma segura con Docker hub mediante conexión al servicio Azure DevOps.
  • Crear y etiqueta la imagen de Docker utilizando Docker BuildKit para el almacenamiento en caché.
  • Envía las etiquetas buildId y latest a Docker Hub.
  • Cierra la sesión de Docker si se ejecuta en un agente Linux autohospedado.

¿Cómo funciona la pipeline?

1. Definir los activadores de la pipeline
trigger:
    - main

pr:
    - main

Esta pipeline se activa automáticamente en:

  • Confirmaciones enviadas a la rama main
  • Solicitudes de extracción dirigidas a la rama principal main

2. Definir variables comunes.
variables:
    imageName: 'docker.io/$(dockerusername)/my-image'
    buildTag: '$(Build.BuildId)'
    latestTag: 'latest'

Estas variables garantizan la coherencia en la nomenclatura, el versionado y la reutilización a lo largo de todos los pasos del proceso:

  • imageName: ruta de tu imagen en Docker Hub
  • buildTag: una etiqueta única para cada ejecución de canalización
  • latestTag: un alias estable para tu imagen más reciente

Importante

La variable dockerusername no se establece automáticamente. Tienes que establecerla de forma segura en las variables de su pipeline de Azure DevOps:

Vamos a Pipelines > Editar > Variables

Añadimos dockerUsername tu nombre de Docekr Hub.

Más información en: Definir y usar variables en Azure Pipelines


3. Definir las etapas y tareas del proceso.
stages:
    - stage BuildAndPush
      displayName: Build and Push Docker Image

Esta etapa se ejecutará solo si la rama de origen es main.

Consejo

Más información: Condiciones de etapa en Azure Pipelines


4. Configuración del trabajo.
    jobs:
        - job: DockerJob
        displayName: Build an Push
        pool:
            vmImage: ubuntu-latest
            demands:
                - docker

Este trabajo utiliza la última imagen de máqunia virtuial Ubuntu con soporte para Docker, proporcionada por agentes alojados por Microsoft. Si es necesario, se puede reemplazar con un grupo personalizado para agentes autoalojados.

Consejo

Más información: Especifique un pool en su pipeline


4.1 Código de pago

steps:
    - checkout: self
    displayName: Checkout Code

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

Consejo

Más información: documentacion del paso de pago


4.2 Autenticarse en Docker hub

    - task: Docker@2
    displayName: Docker Login
    inputs:
        command: login
        containerRegistry: 'my-docker-registry' # Remplazalo por el nombre q le hayas puesto tu

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


4.3 Construir la imagen de Docker

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

Esto construye la imagen con:

  • Dos etiquetas: una con el ID de compilación único y otra como la más reciente.
  • Docker BuildKit habilitado para compilaciones más rápidas y almacenamiento en caché de capas eficiente.
  • Extraer la caché de la imagen más reciente subida.
  • Lista de materiales de software (SBOM) para la transparencia de la cadena de suministro.
  • Certificación de procedencia para verificar cómo y dónde se creó la imagen.

4.4 Subir la imagen de Docker

    - 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, la canalización crea la imagen de Docker en cada ejecución para garantizar la detección temprana de problemas, pero solo envía la imagen al registro cuando los cambios se fusionan en la rama principal, manteniendo así su Docker Hub limpio y enfocado.

Esto sube ambas etiquetas a Docker Hub;

  • $(buildTag): Garantiza la trazabilidad por cada ejecución.
  • latest: Se utiliza para las referencias de imágenes más recientes.

4.5 Cerrar sesión en Docker (agentes autohospedados)

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

Ejecuta el comando docker logout al final del proceso en agentes autohospedados basados en Linux para limpiar de forma proactiva las credenciales y mejorar la seguridad.


Paso 3: Crear variables

Iremos a:

Pipelines --> [Elegimos nuestra pipeline] --> Edit

Variables --> Add

Donde pondremos:

Name: dockerUsername
Value: [nuestro usuario de dockerhub]
variables

Podemos ver que hay 2 opciones para marcar, en este caso no las marcaremos porque no es necesario, pero es muy buena práctica de seguridad y además es importante saberlo por lo que:

  • "Keep this value secret": Es para ocultar el valor si por ejemplo es una password, una API key etc.
  • "Let users override this value when running this pipeline": Esto permite que alguien cambie el usuario al ejecutar la pipeline, pero hay que tener cuidado porque puede causar errores si alguien lo modifica.

Paso 4: Asegurate de tener el Dockerfile Multi-Stage

Si no tienes idea de que estoy hablando puedes ir a consultar la parte anterior de esta documentación.

Enlace a la documentación


Paso 5: Version Pinning Asegurarnos de que tenemos especificada la versión.

En los archivos:

  • Dockerfile
  • requirements.txt

Debemos tener especificada la versión de cada recurso a instalar para q no existan problemas al procesar la pipeline, lo que se llama "Version Pinning".

Dependencies are not pinning

Es super típico que pase esto por lo que hay que tenerlo muy en cuenta


Paso 6: Lanzar la pipeline

Iremos a Pipelines dentro de nuestro Azure DevOps y seleccionaremos nuestra Pipeline para darle a "Run".

pipeline


Paso 7: Comprobar en Docker Hub y hacer privado

Comprobaremos que efectivamente se han subido los commits que hemos estado haciendo dockerhub images

¡IMPORTANTE!

Debemos dejar el repositorio de Dockerhub en privado para que nadie pueda ver lo que hemos subido, en este caso no es nada sensible pero podría contenerlo, así que es todo caso lo mejor que podemos hacer es hacerlo privado.

Para ello, dentro del repo de DockerHub iremos a Ajustes, bajamos un poco y aparecerá "Hacerlo Privado".


Resumen

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

  • Autenticacion segura de Docker mediante una conexion de servicio integrada.
  • Creación y etiquetado automatizados de imágenes, activados por cambios en el código.
  • Compilaciones eficientes que aprovechan la caché de Docker BuildKit.
  • Limpieza segura con cierre de sesión en agentes persistentes.
  • Crear imágenes que cumplan con los requsitos de la cadena de suministrao de software moderna con SBOM y certificación.

Más información