Enviando Deno a producción: Un Enfoque Práctico con Podman y GitHub Actions

¿Necesitas mejorar la reproducibilidad de tus despliegues de Deno? Te invito a leer este tutorial de como utilizar Deno compile y los contenedores de Podman a tu favor.

Enviando Deno a producción: Un Enfoque Práctico con Podman y GitHub Actions

¿Recuerdas que en el artículo pasado dije que iba a dejar descansar a Podman un rato? Bueno, mentí 😆. La tecnología no descansa, Linux no descansa y claro que el desconocido blog de caca-posteo informático tampoco. (Eso y que estoy tratando de cumplir mi palabra de no abandonar por largos periodos de tiempo este lugar).

Esta vez vamos a exprimir esas herramientas que hemos manoseado hasta el cansancio en este blog. Para no hacerla de emoción, te informo de una vez que, lo que haremos es back-end con Deno que correrá dentro de un contenedor de Podman, dicho back-end será un binario estático creado con deno compile. Para rematar este bodrio de idea y dar material de lectura con un mínimo de compostura, vamos a automatizar el proceso con GitHub Actions. Usaremos las acciones que Red Hat nos regala para OpenShift.

Podría considerar este artículo como un mini-tutorial, ya que, no voy a entrar en muchos detalles como lo hice con el artículo de PyInstaller. Me limitaré a dar un overview y algunas configuraciones que puedas copiar con modificaciones sencillas para que puedas desplegar tu código cuanto antes.

Un vistazo al proyecto 👀

Creado con: https://imgflip.com/

Voy a dar un poco de contexto antes. Últimamente, he trabajado mucho con Deno como alternativa a Node.js en entornos productivos. Gracias a un pequeño servidor de pruebas, puedo experimentar un poco con los despliegues productivos. En este caso, tuve que hacer mi propio monitor de servicios. Sé que existen herramientas como Uptime Kuma y Statping, sin embargo, no quiero una GUI Web y probablemente no usaré el 90% de las features que ofrecen. Solo necesito de algo que le haga ping a varios servicios y me reporte si están activos, el código de respuesta y cuanto tiempo tardó la petición en hacerse.

Distribuir una REST API en un binario generado con deno compile dentro de un contenedor de Podman sin privilegios de root puede tener varias ventajas:

  • Portabilidad: Al compilar nuestra aplicación Deno en un binario, podemos ejecutarla en cualquier sistema que soporte el binario ergo, cualquier sistema Gnu/Linux de 64 bits con una LibC reciente. Todo esto sin necesidad de tener Deno instalado. Esto puede ser especialmente útil si estás distribuyendo tu aplicación a múltiples entornos o si estás trabajando con sistemas que tienen restricciones sobre qué software se puede instalar.
  • Seguridad: Podman es una herramienta de contenedores que no requiere un daemon en ejecución y puede ser ejecutada por un usuario no privilegiado, lo que se conoce como “rootless”. Esto puede mejorar la seguridad de tu aplicación al limitar los privilegios del contenedor y reducir la superficie de ataque.
  • Aislamiento: Los contenedores proporcionan un aislamiento entre tu aplicación y el sistema host. Esto significa que puedes controlar exactamente qué recursos puede acceder tu aplicación y cómo interactúa con el resto del sistema.
  • Consistencia: Al usar contenedores, puedes garantizar que tu aplicación se ejecute en un entorno consistente, sin importar dónde se despliegue. Esto puede ayudar a evitar problemas que surgen de las diferencias entre los entornos de desarrollo y producción.

La estructura de directorios que seguí para el proyecto es la siguiente:

De aquí, no nos importan muchas cosas, salvo unos directorios y un par de archivos:

  • El directorio build/ se utilizará para guardar nuestro binario de Deno. Hacerlo aquí nos viene perfecto para que los pipelines sepan donde buscar el binario una vez clonado nuestro proyecto.
  • El directorio .github/workflows contendrá nuestros workflows de GitHub Actions.
  • El archivo Containerfile es lo mismo que un archivo Dockerfile, sin embargo, el proyecto que estamos desarrollando no usa contenedores de Docker sino Podman. De igual forma, no importaría si es un Dockerfile solo, es otro modo de nombrar las cosas.

Requisitos 🧠

Para este artículo no hay muchos requisitos realmente, es un tutorial muy resumido por dos razones:

  1. Todas las codebases son distintas. Mis configuraciones y pipelines mostradas aquí podrían necesitar de arreglos para funcionar correctamente en tu proyecto
  2. Es más un show how to que un how to propiamente.

Aun así, si deseas seguirlo al pie de la letra, los requisitos son:

Conocimiento básico de Deno 🦕🧠

Lo ideal es que conozcas un poco la CLI de Deno para invocar comandos y como funciona el archivo deno.json.

Generando binarios con Deno Compile 🦕💻

Actualmente, Deno nos permite crear binarios distribuibles con un subcomando llamado deno compile. No he encontrado mucha información al respecto de como funciona, pero podrías pensar en un comando que toma el runtime de Deno, tu código y lo mete en el equivalente de un .exe para que puedas ejecutarlo sin necesidad de instalar Deno en el sistema objetivo.

Puedes hacer una pequeña prueba compilando el clásico "Hola Mundo" en Deno:

Sí, no hay más

Ahora, al ejecutar deno compile en la terminal, deberíamos obtener una salida parecida a esta:

$ deno compile main.ts
Check file:///home/ventgrey/main.ts
Compile file:///home/ventgrey/main.ts to ventgrey
Archive:  /tmp/.tmpFMhu6L/denort.zip
  inflating: denort   

En este caso ventgrey es el nombre resultante del binario. Si deseas cambiarlo puedes usar la bandera -o.

Vamos a ejecutarlo para ver si funciona:

$ ./ventgrey
Hello World

El problema es que dichos binarios son gordos. Pero REALMENTE gordos. Si hacemos un ls -lah ventgrey vamos a comprobarlo:

$ ls -lah ventgrey
-rwxrwxrwx 1 ventgrey ventgrey 79M may 27 18:25 ventgrey

79 Megabytes para un Hola Mundo. Rust y Go quedarían atónitos ante tal tamaño 😆. Tampoco voy a ser tan duro con Deno, el binario tiene dentro todo el runtime + el código que estemos empaquetando, por lo que esto podría aumentar.

Editando el deno.json para simplificar la compilación

No entraré en más detalles de como funciona el comando deno compile porque siempre puedes escribir deno compile --help y leer las opciones que tiene para ti.

En el caso de la REST API que te acabo de mostrar, añadí la siguiente instrucción a la sección tasks de mi archivo deno.json:

"tasks": {
    "compile": "deno compile --deny-hrtime --unsafely-ignore-certificate-errors --allow-all -o build/greybackend main.ts"
}

El comando en cuestión tiene las siguientes opciones, puedes elegir omitirlas si lo deseas:

  • --deny-hrtime: Deno nos da esta opción para evitar ataques basados en tiempo y perfilación (fingerprinting).
  • --unsafely-ignore-certificate-errors: Desactiva esta opción si no estás usando certificados autofirmados. En mi caso, tengo una CA local que se encarga de emitirlos y dicho servicio no está expuesto a internet. De hecho, no sale de la propia red de Docker, por lo que, poco o nada importa que el certificado lo haya emitido una de las magnánimas entidades certificadoras.
  • --allow-all o -A: Es una bandera que le otorga todos los permisos a Deno. Deberás ajustar esto a tú REST API, si no tienes acceso a las variables de entorno o al sistema de archivos es probable que quieras elegir banderas con control un poco más granular.
  • -o build/greybackend: ¿Recuerdas el directorio build/ del que te hablé antes? Para esto sirve. Aquí deno compile guardará el binario resultante.
  • main.ts: El archivo principal que deberá compilar deno compile.
Cuida que tu código no tenga "dynamic imports", tendrás que añadirlos a mano al comando deno compile en caso de que tu código los contenga.

Creando el Containerfile 📦

Ya habíamos hablado de lo que era el Containerfile. Deno nos proporciona imágenes de Docker en Docker Hub, mismas que vamos a usar en este archivo para generar la imagen de nuestro proyecto. Te comparto mi Containerfile

FROM docker.io/denoland/deno:alpine-1.43.6 as build-stage

WORKDIR /app

COPY . .

RUN deno cache main.ts

RUN apk add unzip

RUN deno task compile

FROM docker.io/denoland/deno:alpine-1.43.6 as deploy-stage

USER deno

WORKDIR /deno

COPY --from=build-stage /app/build/greybackend /deno

EXPOSE 3000

ARG GREYBACKEND_PORT=3000
ARG PRIVATE_NTFY_URL="https://ntfy.notfify.com/"
ARG PRIVATE_NTFY_CHANNEL="changeme"

CMD [ "/deno/greybackend" ]

Vamos a desglosar las secciones una por una:

  1. FROM docker.io/denoland/deno:alpine-1.43.6 as build-stage

Para realizar la imagen del proyecto tomé en cuenta las mismas prácticas que se usan con los proyectos en el lenguaje de programación "Go". Recomiendo que uses la misma versión que tienes en tu entorno de desarrollo para evitar problemas de reproducibilidad. Este primer punto va a actuar como el entorno de construcción y será descartado después por una imagen limpia. Ese apéndice as build-stage va a actuar como un identificador que podremos usar más tarde, cuando hagamos un nuevo contenedor para alojar la aplicación ya compilada.

  1. WORKDIR /app

El WORKDIR como podrás intuir por el nombre es un directorio dentro del contenedor donde trabajaremos con todo el código y sus respectivas dependencias. También es una forma de decirle a Podman que queremos realizar todo el trabajo en este directorio específicamente y no contaminar la raíz del contenedor.

  1. COPY . .

En este paso de la imagen se copiarán todos los archivos del proyecto, excepto aquellos que hayas definido dentro del archivo .containerignore que funciona similar a un archivo .gitignore el patrón . . es equivalente a decir "copia todo alv, aquí mero".

  1. RUN deno cache main.ts

En este paso estamos precompilando las dependencias de nuestro proyecto. Se hace en una capa separada por motivos de caching. Con esto Deno va a descargar y optimizar todas las dependencias de nuestro proyecto, así nos ahorramos recompilar todas las dependencias en los pasos siguientes o en caso de necesitar reconstruir la imagen.

  1. RUN apk add unzip

Estamos usando una imagen basada en Alpine Linux, el cual es una distribución Musl/Linux muy ligera (Si, no es gnu). Es normal que no venga con muchas herramientas preinstaladas, pues el minimalismo es uno de sus objetivos. Este paso usa su gestor de paquetes apk para instalar unzip. Deno utiliza unzip para la creación de los binarios, si no está presente en el sistema deno compile no funcionará.

  1. RUN deno task compile

Al copiar todo con la instrucción COPY . . también nos trajimos el archivo deno.json con las instrucciones de compilación. Por lo que no debemos escribir el mismo comando dos veces. Solo falta esperar a que Deno compile tu backend. Ten en cuenta que, entre más grande y más dependencias tenga, el proceso podría tardar unos segundos o competir con Rust por ver quien es más lento.

  1. FROM docker.io/denoland/deno:alpine-1.43.6 as deploy-stage:

Vamos a usar exactamente la misma versión de la imagen con la que construimos nuestro binario de Deno pero esta vez como un nuevo contenedor limpio donde ejecutar el binario que acabamos de generar.

  1. USER deno

No me gustan los contenedores que se ejecutan como root y si bien ya existe el modo rootless en Docker, nosotros podemos interactuar con el runtime de contenedores del SO directamente, no como la 🐳. Como extra, vamos a ejecutar nuestra aplicación bajo un usuario no-root dentro del contenedor. Si de alguna forma llegan a vulnerarlo, ahora deberán pasar por encima de 2 usuarios y sus respectivos permisos

  1. WORKDIR /deno

Este es otro mini-paso de seguridad, no vamos a trabajar en el mismo directorio que antes, vamos a cambiar el espacio de trabajo a un directorio /deno. Esto por dar un ejemplo. Podrías hacer otro directorio completamente nuevo sobre la raíz del contenedor o seguir la jerarquía del sistema de archivos Linux y montar tu binario bajo /opt, /usr/bin o /bin. Queda en ti.

  1. COPY --from=build-stage /app/build/greybackend /deno

Similar a la instrucción COPY anterior, pero siendo mucho más específicos esta vez. ¿Recuerdas el identificador del que hablamos al inicio del Containerfile? Lo usamos en --from= para indicarle a Podman que queremos copiar algo de la imagen de construcción a la imagen actual, en este caso queremos copiar del build-stage el binario localizado en /app/build/greybackend al directorio que definimos en WORKDIR, en este caso: /deno.

  1. EXPOSE 3000

Aquí indicamos que puerto del contenedor queremos exponer. Dependerá de tu backend y de como hayas configurado tu aplicación.

  1. Los ARG:

Las líneas:

ARG GREYBACKEND_PORT=3000
ARG PRIVATE_NTFY_URL="https://ntfy.notfify.com/"
ARG PRIVATE_NTFY_CHANNEL="changeme"

Sirven para definir variables de entorno que queremos cambiar más adelante. La diferencia es que, si usamos ENV en lugar de ARG, no podremos mutar los valores de dichas variables cuando ejecutemos el contenedor desde el CLI de Podman. La idea es que marques con ARG las variables cuyo valor quieres reemplazar ya en un entorno de producción.

  1. CMD [ "/deno/greybackend' ]

Finalmente, con CMD the drapery falls. Con esta instrucción Podman ejecutará nuestro binario apenas inicie nuestro contenedor. En realidad CMD se usa para especificar el comando predeterminado que se va a ejecutar cuando inciemos un contenedor con la imagen que vamos a construir.

Se que pude haber usado ENTRYPOINT, sin embargo, con ENTRYPOINTno podría sobreescribir el punto de entrada del contenedor de manera trivial, cosa que, me apena decir 😅 es por motivos de Bug Driven Development. Esto es útil en caso de que quieras distribuir tus contenedores para que se inicien de la misma manera y no deseas que tus usuarios los inicien con un comando distinto.

¿Qué hueva no? Podría ir más a fondo con los Containerfile, pero como ya dije al menos 3 veces. Todo depende de la implementación. Puedes usar esa copia de mi Containerfile para tus propósitos maquiavélicos.

Automatizando la liberación de imágenes con GitHub Actions 🐱🐙

¿Tu proyecto tiene un repositorio en GitHub? Perfecto. Podemos crearle pipelines para automatizar la construcción y publicación de las imágenes de Podman usando las acciones que nos da Red Hat. En este caso yo usé tres imágenes, cortesía de Red Hat y OpenShift:

  1. Para construir la imagen de Podman con Buildah
  2. Para iniciar sesión en el GHCR (GitHub Container Registry)
  3. Para publicar las imágenes generadas en los pipelines.

Arreglando las porquerías de Microsoft (otra vez) 🙄

🫠
Es probable que te topes con este problema si desplegaste tu imagen de Podman a mano. Desconozco porque habría de configurar todo para después tener el mismo resultado pero automatizado. Such is MS.

Por alguna razón de que desconozco, la interfaz de GitHub está cada vez más fragmentada en funcionalidades. Los proyectos ahora no son por repositorio y los paquetes funcionan de forma similar. Similar a como funcionan los proyectos, ahora los paquetes de GitHub pertenecen a tu perfil y no a un repositorio en particular. Hay que ensuciarse las manos en las opciones ocultas de GitHub o en su documentación.

Problemas de diseño de interfaz similares ya han ocurrido, cuando, quienes sean los que estén a cargo de la UI de GitHub, decidieron actualizar el feed de inicio, llevando a muchos usuarios a expresar descontento por qué dicho feed les mostraba repositorios que no eran de su interés...

¿Suena familiar?

Antes de automatizar el flujo de trabajo vamos a necesitar darle permiso a las GitHub Actions para escribir/subir imágenes de Podman a nuestro repositorio:

  1. Ve a tus paquetes publicados y da clic en el contenedor/título del paquete publicado en GitHub. Esto lo puedes ver en tu página de perfil:
En: https://github.com/TuPerfilDeGitHub?tab=packages
  1. En la parte derecha de la pantalla encontrarás la siguiente columna de detalles. Deberás hacer clic en la opción "Package Settings":
  1. Localiza la sección "Actions repository access" y haz clic en el botón "Add Repository":
Me preocuparía mucho por tu visión si no encuentras esta sección
  1. Busca o el repositorio al que pertenezca tu imágen de Podman:
Usa la barra de búsqueda para localizar tu repositorio.
  1. Cambia los permisos asignados de "Read" a "Write":
El emoji de ✅ te indicará si los permisos se actualizaron correctamente.

Puedes volver a lanzar tus workflows en la pestaña "Actions" de tu proyecto o esperar a la siguiente vez que necesites hacer un commit.

Sé que critico mucho a Microsoft y que simplemente Podría dejar de usar GitHub o sus invenciones completamente. Lo cierto es que, si decidiera dejar de usar lo que ellos hacen en lugar de lo que "compran/importan" seguiría siendo lo mismo 😆

Escribiendo los pipelines ⚙️📦 ➡️ 🦭

Luego de barrer y trapear la waska que tiene Microsoft por interfaz, llegó el momento de escribir nuestros pipelines.

Aquí te dejo el archivo que yo utilicé para mis pipelines:

name: Deno CI/CD

on:
  push:
    branches: ["master"]
  pull_request:
    branches: ["master"]

permissions:
  contents: read
  packages: write

env:
  REGISTRY: ghcr.io
  IMAGE_TAG: latest
  IMAGE_NAME: ventgrey/greybackend

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Setup repo
        uses: actions/checkout@v4

      - name: Setup Deno
        uses: denoland/setup-deno@v1
        with:
          deno-version: v1.x

      - name: Verify formatting
        run: deno fmt --check

      - name: Run linter
        run: deno lint
        
      - name: Run tests
        run: deno test -A
        
  build-image-test:
    name: Test build image
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Build with Buildah
        uses: redhat-actions/buildah-build@v2
        with:
          image: ghcr.io/${{ env.IMAGE_NAME }}
          tags: ${{ env.IMAGE_TAG }}
          containerfiles: |
            ./Containerfile
  build-and-push-image:
    name: Build and Push Image to GitHub Container Registry
    runs-on: ubuntu-latest
    needs: build-image-test
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Login to GitHub Container Registry
      - name: Build with Buildah
        uses: redhat-actions/buildah-build@v2
        with:
          image: ${{ env.IMAGE_NAME }}
          tags: ${{ env.IMAGE_TAG }}
          containerfiles: |
            ./Containerfile
      - name: Login to ghcr.io
        uses: redhat-actions/podman-login@v1
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Push to GitHub Container Registry
        id: push-to-ghcr
        uses: redhat-actions/push-to-registry@v2
        with:
          image: ${{ env.IMAGE_NAME }}
          tags: ${{ env.IMAGE_TAG }}
          registry: ${{ env.REGISTRY }}
      - name: Print image url
        run: |
          echo "Image pushed to ${{ steps.push-to-ghcr.outputs.registry-paths }}"

Puedes descargarlo aquí para una mayor conveniencia:

Vamos a ver cómo funciona. No lo haré paso por paso como hice con el Containerfile, en su lugar me iré por secciones:

Metadatos y disparadores

name: Deno CI/CD

on:
  push:
    branches: ["master"]
  pull_request:
    branches: ["master"]

El nombre del pipeline es Deno CI/CD, esto solo funciona como una etiqueta para que nosotros recordemos el nombre del workflow en caso de tener que inspeccionarlo en el futuro. Dicho pipeline se dispara cuando se realiza un push o un pull request en la rama master de nuestro repositorio.

Permisos del pipeline/workflow

permissions:
  contents: read
  packages: write

Aquí establecemos los permisos para leer el contenido del repositorio y para escribir en los paquetes de GitHub.

Variables de entorno

env:
  REGISTRY: ghcr.io
  IMAGE_TAG: latest
  IMAGE_NAME: ventgrey/greybackend

Definimos variables de entorno que, en este caso usaremos simplemente como variables dentro de nuestro YAML. Estas variables se utilizan en varios lugares dentro del pipeline y, para evitar errores al momento de escribirlas, es mejor definirlas una vez y solo llamarlas

  • REGISTRY: Es la URL del registro de contenedores donde se almacenará la imagen del contenedor. en este caso y como dijimos antes, estamos usando el GitHub Container Registry (ghcr.io)
  • IMAGE_TAG: Es la etiqueta que se le asignará a la imagen del contenedor. Como aquí no tengo ningún respeto por el versionado semántico, siempre voy a usar la etiqueta latest para proporcionar la versión más reciente que se construya. Tu puedes generarla por fecha o por sumatoria.
  • IMAGE_NAME: Es el nombre que se le asignará a la imagen del contenedor. En este caso, el nombre de la imagen es ventgrey/greybackend

Los "jobs" 🐡

La siguiente sección son los trabajos o tareas que se ejecutarán en orden en el pipeline. Cada uno tiene ciertos atributos que exploraremos de uno por uno:

Test 🧪

Este trabajo se encarga de probar nuestro código de Deno usando los comandos que nos ofrece la interfaz de línea de comandos:

  test:
    runs-on: ubuntu-latest
    steps:
      - name: Setup repo
        uses: actions/checkout@v4

      - name: Setup Deno
        uses: denoland/setup-deno@v1
        with:
          deno-version: v1.x

      - name: Verify formatting
        run: deno fmt --check

      - name: Run linter
        run: deno lint
        
      - name: Run tests
        run: deno test -A

Lo único importante a destacar en este trabajo es que lo estamos ejecutando en la última versión de Ubuntu y que, estamos probando nuestro código con la última versión 1.x estable de Deno.

Build Image Test 🧪📦⚙️

Es una buena idea probar si nuestra imagen del contenedor se va a construir de forma correcta antes de subirla al GHCR

  build-image-test:
    name: Test build image
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Build with Buildah
        uses: redhat-actions/buildah-build@v2
        with:
          image: ghcr.io/${{ env.IMAGE_NAME }}
          tags: ${{ env.IMAGE_TAG }}
          containerfiles: |
            ./Containerfile

Algo que destacar en esta sección del pipeline es que estamos usando la llave needs para indicarle a GitHub que, para ejecutar este paso, es necesario que el paso anterior (test) se haya completado de forma satisfactoria. Si hubiese un error en las pruebas unitarias de nuestro programa, este paso no se ejecutaría, evitando así desperdiciar los minutos de ejecución gratis que nos da GitHub.

Otra cosa a destacar es que aquí ya estamos haciendo uso de las variables de entorno que definimos al inicio del archivo del pipeline. También, casi olvido mencionar que estamos usando la acción de Red Hat para constuir una imagen de Podman con buildah. Es importante indicarle donde se encuentra el Containerfile que deseamos usar como instrucciones para la imagen a construir.

Build and push image 📦🦭 ⬆️

El paso más largo del pipeline, repite las instrucciones anteriores, con la dependencia de build-image-test para ejecutarse. De este modo, evitamos subir una imagen malhecha al registro de contenedores en caso de que ocurran errores en las pruebas de creación de la imagen:

  build-and-push-image:
    name: Build and Push Image to GitHub Container Registry
    runs-on: ubuntu-latest
    needs: build-image-test
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Build with Buildah
        uses: redhat-actions/buildah-build@v2
        with:
          image: ${{ env.IMAGE_NAME }}
          tags: ${{ env.IMAGE_TAG }}
          containerfiles: |
            ./Containerfile
      - name: Login to ghcr.io
        uses: redhat-actions/podman-login@v1
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Push to GitHub Container Registry
        id: push-to-ghcr
        uses: redhat-actions/push-to-registry@v2
        with:
          image: ${{ env.IMAGE_NAME }}
          tags: ${{ env.IMAGE_TAG }}
          registry: ${{ env.REGISTRY }}
      - name: Print image url
        run: |
          echo "Image pushed to ${{ steps.push-to-ghcr.outputs.registry-paths }}"

Este es el paso final de nuestro pipeline. Aquí hay varias cosas que debo destacar:

  1. Usamos la acción podman-login que mencionamos arriba para iniciar sesión en el GHCR con Podman.
  2. Volvemos a constuir la imagen usando buildah-build con el mismo Containerfile que indicamos en el paso anterior
  3. Usamos la acción push-to-registry para subir la imagen al GHCR luego de construirla satisfactoriamente.
  4. Finalmente imprimimos la URL de la imagen constuida y publicada con los detalles del paso anterior.

Como puedes ver, YAML es horrible, pero es fácil agarrarle el truco una vez entiendes cómo funcionan las GitHub actions.

Comprobando nuestros workflows 👀

Si entras a la pestaña "Actions" de tu repositorio, puedes explorar los pasos del workflow uno por uno y observar sus dependencias:

Vamos a explorar el último workflow, lo que buscamos es el paso que definimos donde se imprime la URL de nuestra imagen publicada:

Bingo

¡Genial! Ahora si, podemos hacer podman pull a lo desgraciado. Bueno, no así de visceral, pero al menos ya tenemos un build automatizado para actualizar nuestra imagen de Podman.

Tips para el entorno de ejecución 🖥️🌎

Si vas a usar tus imágenes publicadas en el GHCR, te doy los siguientes consejos para que no tengas problemas en llevar tu aplicación a producción:

  1. Prueba primero usando la orden podman run sin la bandera -d, depura la salida de tu contenedor antes de desplegar la versión "definitiva" a producción.
  2. Si ya tienes tu contenedor en ejecución dentro de un entorno productivo, usa la integración de podman con systemd y con k8s sabiamente
    1. Recuerda que puedes generar servicios a partir de tus contenedores. Aprovecha el entorno rootless de podman y genera dichos servicios en un usuario sin privilegios dentro de ~/.config/systemd/user con podman generate system "contenedor" > ~/.config/systemd/user/contenedor.service
    2. Lo mismo para generar tus archivos para Pods de kubernetes con podman generate kube "contenedor > contenedor-pod.yml
  3. Usa podman secret y sus variaciones para almacenar los valores que no deseas exponer en la terminal.
En el entorno productivo LAN only 😋

Cuando te salen los experimentos, la vida empieza a sonar como "Meet Joe Black" por unas horas 😅

Conclusión ✍️

Deno ha llamado mucho mi atención en los últimos días. Cuando salió admito que emití más de un par de opiniones equivocadas al respecto. Lo cierto es que toda la tecnología, así como las críticas que se le hacen, podrán superar todas las pruebas que les pongas, pero hay una sola que no podrán anticipar y es la del paso del tiempo. Me alegra saber que estaba equivocado y que el lema de Deno de "Uncomplicate JavaScript" es bastante cierto.

Con los conocimientos que adquiriste aquí te invito a hacer tus propios proyectos con Deno, explora el runtime y sus capacidades, alguna buena idea podría surgirte con suficiente inspiración. Creo yo que, lo que acabo de mostrar aquí es útil para back-ends de alta disponibilidad, donde deseas entregar versiones lo más consistente que te sea posible entregar a través de tus servidores.

¿Te animas a probarlo? ¿Tienes dudas? Me gustaría leer tus comentarios en la red social donde encontraste este artículo 😄.

Entiendo que cambié mucho las palabras pipeline y workflow. Pido una disculpa por ello, es la maldita fuerza del hábito. Trataré de corregir mis errores en artículos futuros.

Canción triste del día 🍂

They used to be friends… and now they simply wiped each other out.