Generando y publicando imágenes Docker con GitHub Actions

Tiempo de lectura: 5 minutos

¡Después de muchos meses sin escribir, por fin volvemos a la carga! Han pasado muchas cosas durante estos meses (de las que supongo que hablaré en la entrada de los 3 años), pero entre ellas hay una que ha empujado el tema de esta entrada:
Docker está moviendo ficha en lo referente a sus políticas de precios.

Lo que nos lleva a una pregunta obvia, ¿en qué me afecta eso a mí? Pues seguramente en nada, pero tal vez seas una de esas personas que estaban utilizando utilidades como las «builds automáticas» de DockerHub.

Esta es precisamente la parte que más me ha impactado a mí, tengo algunas imágenes Docker que hasta hace poco generaba directamente usando DockerHub, pero ese tiempo se acabó, y ha tocado buscarse otra solución. Esta solución, en la mayoría de los casos va a pasar por utilizar los propios sistemas de CI/CD que ofrecen muchos proveedores, y ya que estamos en GitHub, ¿por qué no usar GitHub Actions para construir la imagen Docker?

Generando una imagen Docker con GitHub

No es la primera entrada en este blog sobre qué son y cómo utilizar GitHub Actions, así que podemos ir al turrón y centrarnos en como generar nuestras imágenes Docker. Esto en sí mismo ya es algo muy útil que podemos utilizar como parte de nuestro proceso de integración continua, para validar que no se está rompiendo nada con un cambio.

Para este pequeño ejemplo que estamos haciendo, vamos a utilizar un Dockerfile muy sencillo, ya que solo queremos ver como generar la imagen Docker con Github Actions, no recrearnos en Docker 🙂 Algo como esto por ejemplo:

FROM alpine
ENTRYPOINT ["echo", "Hello World!"]

Simplemente usando una imagen ‘alpine’, imprimimos por consola ‘Hello World!’.

El primer paso, como toda GitHub Action, es crear el yam correspondiente dentro de la carpata ‘.github/workflows’, en este caso, algo tan simple como esto:

name: Docker GitHub

on: [push, pull_request,workflow_dispatch]

jobs:
  build-and-push-images:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

Sobre esta Action que de momento no hace nada, vamos a añadir el paso que generará la imagen. Para ello vamos a utilizar la acción build-push-action, que es su modo más simple, basta con añadir el paso a nuestro ‘yaml’:

      - name: Build and push Docker image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .  //Contexto para el comando Docker
          push: false //Indicamos si queremos hacer push de la imagen

Con esto ya tenemos todo listo, nuestra GitHub Action va a generar una imagen Docker automáticamente.

Publicando una imagen Docker en DockerHub con GitHub

Muy bien, tenemos lista nuestra Action y ya genera la imagen Docker, pero esto aún no resuelve el problema inicial, de que no tengo las auto builds de DockerHub para mantener las imágenes actualizadas. Precisamente por eso, hay que ir un paso más allá y subir la imagen al registro.

Lo primero que vamos a hacer, es añadir una nueva GitHub Action más a nuestro ‘yaml’ (antes de hacer el build & push, obviamente), con la que haremos un login en DockerHub:

      - name: Login to DockerHub
        if: github.ref == 'refs/heads/master'
        uses: docker/login-action@v1 
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

Ten en cuenta que es necesario crear un token en DockerHub y añadirlo junto al usuario como secreto para el repositorio.

Una vez hecho esto, basta con un par de cositas más, editar el paso donde hacemos el build, para indicarle que queremos también hacer un push por un lado, y por otro lado, darle un tag a la imagen:

      - name: Build and push Docker image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          push: true
          tags: 'fixedbuffer/hello'

Con esto, ya tenemos lista nuestra imagen Docker, y subida a DockerHub. Pero… ¿Y si quiero más de un tag? Es muy habitual que cuando creamos una nueva imagen, le demos un tag especifico y además ‘latest’.

Gestionando múltiples nombres y etiquetas

Vale, ya tenemos el proceso listo, pero como hemos comentado con el tema de los tags, es mejorable para simplificarnos la vida. Es por eso que mi propuesta es utilizar otra GitHub Action que nos va a generar diferentes tags según la configuremos. Esta Action es metadata-action.

Simplemente vamos a tener que configurar la Action y editar un poco la Action con la que generamos la imagen Docker, de modo que utilice como entrada la salida de esta. Lo primero será configurar los metadatos, para lo que añadimos esto a nuestro ‘yaml’:

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v3
        with:
          images: |
            fixedbuffer/hello
          tags: |
            latest
            type=sha

Como resultado de esta Action, se van a generar las etiquetas ‘fixedbuffer/hello:latest’ y ‘fixedbuffer/hello:sha-COMMIT-SHA’. Esta solo es una configuración muy simple para poder probar que funciona, pero realmente ofrece una gran cantidad de opciones, que puedes consultar en el mismo repositorio.

Ahora, vamos a usar esas etiquetas. La Action que genera y publica la imagen se verá así:

      - name: Build and push Docker image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Listo, ya tenemos solucionado el problema y se va a generar y subir la imagen con ambos tags.

¿Y si quiero usar otro regristry? (El de GitHub por ejemplo)

Buena pregunta, y la verdad es que tal cual hemos planteado nuestro ‘yaml’, es algo muy muy sencillo. Bastaría con hacer login en el regritry que queramos usar (ghcr, acr, ecs…) y añadir el nombre. Por ejemplo, con DockerHub Y el registry de GitHub nos quedaría una cosa así:

name: Github Registry

on: [push, pull_request, workflow_dispatch]

env:
  IMAGE_NAME: fixedbuffer/hello

jobs:
  build-and-push-images:
    runs-on: ubuntu-latest
    # Asignamos los permisos sobre los recursos de GitHub
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v3
        with:
          images: |
            ${{ env.IMAGE_NAME}}
            ghcr.io/${{ env.IMAGE_NAME}}
          tags: |
            latest
            type=sha

      - name: Login to DockerHub
        if: github.ref == 'refs/heads/master' # Solo master
        uses: docker/login-action@v1 
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GHCR
        if: github.ref == 'refs/heads/master' # Solo master
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          push: github.ref == 'refs/heads/master' # Solo master
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Conclusión

La verdad es que en mi caso concreto, perder las auto builds de DockerHub fue un problema, ya que tuve que hacer un trabajo extra para añadir la generación de imágenes directamente desde GitHub.

De todos modos, como puedes comprobar es muy muy sencillo utilizar GitHub Actions para generar imágenes Docker. En este artículo hemos hecho una pequeña review de alto nivel, pero usando las dos Actions principales (docker/build-push-action y docker/metadata-action) podemos configurar incluso las plataformas de destino, usar buildx, …

Puedes verlo en marcha en este repo de GitHub (obviamente xD)

Deja un comentario