Resumen de contenidos
¡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)