Desplegando una aplicación de Astro con Vue.js y Tailwind en AWS App Runner

Recientemente trabajé en un proyecto en el que tuve que desplegar un sitio web hecho con Astro, Tailwind y Vue.js. Este proyecto era para uso interno y se podría decir que era un prototipo más que algo que saldría a producción, por lo que no vi caso en montar algo complejo con Amplify, ECS, Lambdas o Lambda@Edge, como se puede encontrar en otras guías de internet.

Por si no conoces AWS App Runner, es un servicio completamente administrado que simplifica el despliegue y la ejecución de aplicaciones basadas en contenedores. Con App Runner, puedes tomar tu código o imagen de contenedor, construir, desplegar y escalar tu aplicación sin tener que preocuparte por la infraestructura subyacente.

Entre sus ventajas destacan:

  • Facilidad para aplicaciones containerizadas: App Runner está diseñado para desplegar aplicaciones server-side y basadas en contenedores de forma directa, sin requerir configuraciones complejas.

  • Despliegue y escalado automático: Permite configurar despliegues automáticos y escalar la aplicación en función de la carga de tráfico, lo cual resulta ideal para proyectos que puedan experimentar picos de usuarios.

  • Facilidad en la configuración: Si bien se puede utilizar para aplicaciones productivas con gran volumen de tráfico, si solo quieres prototipar o mostrar avances de tu aplicación, App Runner te permite tener tu aplicación corriendo en minutos, con CI/CD y toda la cosa.

Bueno, vamos directo al grano. Estos son los pasos que necesitarás seguir para desplegar tu aplicación con AWS App Runner.

Inicialización del Proyecto

El primer paso es inicializar tu proyecto de Astro. Puedes configurarlo según las necesidades de tu aplicación. En mi caso, lo inicialicé con Tailwind y Vue.js mediante el siguiente comando:

npm create astro@latest -- --add vue --add tailwind

Una vez que el proyecto esté inicializado, al ejecutar npm run dev deberías poder ver la página de bienvenida de Astro.

A partir de este punto, omito la parte en la que modificas o agregas los componentes necesarios, ya que esta guía se centra en el despliegue del proyecto en App Runner.

Instalación y Configuración del Adaptador

Hasta el momento de escribir esta guía, no hay un adaptador que se pueda utilizar para desplegar en App Runner como sí lo hay para AWS Amplify, ECS, Lambdas o Lambda@Edge. La mayoría de estos adaptadores funcionan con el SDK de AWS y, si bien funcionan de maravilla, por lo general son algo lentos.

En este caso, utilizaremos el adaptador nativo @astrojs/node en modo standalone y, para el despliegue, utilizaremos el proceso CI/CD que nos proporciona App Runner para detectar los cambios de nuestra rama main y realizar los despliegues.

Si aún no has instalado el adaptador @astrojs/node, ejecútalo en la raíz del proyecto:

npx astro add node

Este comando instalará las dependencias necesarias y modificará el archivo astro.config.mjs. Al finalizar, deberías ver algo similar a lo siguiente:

// @ts-check
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
import node from "@astrojs/node"; // <-- new
import vue from '@astrojs/vue';

// https://astro.build/config
export default defineConfig({
    vite: {
        plugins: [tailwindcss()]
    },
    adapter: node(), // <-- new
    integrations: [
        vue()
    ]
});

Si no aparecen estas líneas, agrégalas manualmente. Luego, para especificar el modo del adaptador, modifica la configuración de la siguiente manera:

export default defineConfig({
    vite: {
        plugins: [tailwindcss()]
    },
    adapter: node({
        mode: 'standalone', // <-- new
    }),
    integrations: [
        vue()
    ]
});
Para producción con alto tráfico, quizá te convenga utilizar el adaptador en modo middleware combinado con un servidor más robusto (por ejemplo, Nginx). Quizás en otra ocasión exploremos esa alternativa para aplicaciones más robustas.

Configuración de Variables de Entorno (Opcional)

Si tu aplicación utiliza variables de entorno, puedes definir un esquema en la configuración de Astro. Para ello, agrega el parámetro env a la función defineConfig:

import { defineConfig, envField } from 'astro/config'; // <-- new

export default defineConfig({
    // ... otras configuraciones
    env: { // <-- new
        schema: {
            API_HOST: envField.string({ context: "server", access: "public", optional: false }),
            API_KEY_VALUE: envField.string({ context: "server", access: "secret", optional: false }),
            // ... otras variables
        }
    }
});

Consulta la documentación oficial para más detalles.

Creación del Archivo de Configuración apprunner.yaml

Este archivo nos permitirá configurar App Runner, indicándole qué debe hacer en cada una de las etapas de despliegue.

App Runner cuenta con dos etapas de despliegue: build y run. En la etapa build, normalmente nos va a interesar construir nuestra aplicación; en el caso de Astro, instalar las dependencias y ejecutar el comando npm run build. Opcionalmente, también puedes ejecutar tus pruebas en caso de tenerlas o cualquier otro proceso que quieras realizar antes de que tu aplicación esté disponible para tus usuarios.

En la etapa run, normalmente nos va a interesar correr nuestra aplicación; en el caso de Astro, levantar el servidor integrado para que nuestra aplicación esté disponible.

Este es mi archivo de configuración básico para correr Astro en App Runner:

version: 1.0
runtime: nodejs18
build:
  commands:
    pre-build:
      - npm install
    build:
      - npm run build
  env:
    - name: NODE_ENV
      value: "development"
run:
  runtime-version: 18.20.5
  pre-run:
    - npm install --omit=dev
  command: node dist/server/entry.mjs
  network:
    port: 80
    env: PORT
  env:
    - name: NODE_ENV
      value: "production"
    - name: HOST
      value: "0.0.0.0"

Expicación de la configuración

  • version: Versión del archivo de configuración.

  • runtime: Entorno de ejecución; en este caso usamos Node.js 18 debido a que hasta ahora App Runner soporta hasta esa versión.

  • build: Define la etapa de construcción, donde normalmente se instalan las dependencias y se construye el proyecto.

  • run: Especifica la etapa de ejecución, indicando el comando para levantar el servidor, el puerto y las variables necesarias. En el caso de Astro, debemos agregar la variable HOST con el valor "0.0.0.0" para que el Health Check de App Runner funcione.

Consulta la documentación oficial para más detalles.

Variables de entorno

Si tu aplicación utiliza variables de entorno, debes incluirlas tanto en la sección build como en run:

build:
  env:
    - name: NODE_ENV
      value: "development"
    - name: API_HOST
      value: "https://api.myawsomewebsite.com"
    - name: API_KEY_NAME
      value: "API-Key"
run:
  env:
    - name: NODE_ENV
      value: "production"
    - name: HOST
      value: "0.0.0.0"
    - name: API_HOST
      value: "https://api.myawsomewebsite.com"
    - name: API_KEY_NAME
      value: "API-Key"

El archivo apprunner.yaml debe subirse al repositorio Git. Si manejas variables sensibles, utiliza AWS Secrets Manager o AWS Systems Manager Parameter Store. Por ejemplo:

run:
  env:
    - name: MY_VAR_EXAMPLE
      value: "example"
  secrets:
    - name: my-secret
      value-from: "arn:aws:secretsmanager:us-east-1:123456789012:secret:testingstackAppRunnerConstr-kJFXde2ULKbT-S7t8xR:username::"
    - name: my-parameter
      value-from: "arn:aws:ssm:us-east-1:123456789012:parameter/parameter-name"
    - name: my-parameter-only-name
      value-from: "parameter-name"
No uses directamente el ARN generado por AWS ya que puede incluir caracteres extra que harán que App Runner no encuentre el secreto. Recuerda que los secretos no se pueden utilizar en la etapa de construcción, solo en la de ejecución.

Por alguna razón, Astro no detecta correctamente las variables de entorno inyectadas por App Runner. La solución más sencilla que encontré es crear un script que extraiga las variables del sistema y las escriba en un archivo .env. Este es el script que utilizo:

#!/usr/bin/env bash

# Declarar un array asociativo con las variables y sus valores por defecto
declare -A default_variables=(
    [API_HOST]="https://api.myawsomewebsite.com"
    [API_KEY_VALUE]="something"
    # ... otras variables
)

# Obtener el directorio donde se encuentra el script
script_dir="$(dirname "$(realpath "$0")")"
env_file="$script_dir/.env"

# Crear o vaciar el archivo .env
> "$env_file"

# Recorrer el array y escribir cada variable en el archivo .env
for key in "${!default_variables[@]}"; do
    # Si la variable de entorno existe, se utiliza; de lo contrario, se toma el valor por defecto
    value="${!key}"
    if [ -z "$value" ]; then
        value="${default_variables[$key]}"
    fi

    # Si el valor contiene espacios y no está entre comillas, se envuelve en comillas dobles
    if [[ "$value" =~ \  && ! "$value" =~ ^\".*\"$ ]]; then
        value="\"$value\""
    fi

    echo "$key=$value" >> "$env_file"
done

echo "Archivo .env generado en $env_file"
cat $env_file

Guarda este archivo en la raíz del proyecto como generate_env.sh y asígnale permisos de ejecución:

chmod +x generate_env.sh

Luego, asegúrate de llamar a este script en las etapas de build y run de tu archivo apprunner.yaml:

build:
  commands:
    pre-build:
        - ./generate_env.sh  # <-- nuevo: genera el archivo .env
      - npm install
  env:
    ...
run:
  ...
  pre-run:
      - ./generate_env.sh  # <-- nuevo: genera el archivo .env
    - npm install --omit=dev
  command: node dist/server/entry.mjs
  network:
    ...
  env:
    ...

Configuración en AWS App Runner

Con el proyecto configurado y el archivo apprunner.yaml listo, solo queda subir tu proyecto a GitHub o Bitbucket y configurar App Runner en la consola de AWS.

Estos son los pasos que necesitas seguir:

  1. Ingresa a App Runner y haz clic en “Crear servicio”.

  2. Selecciona “Repositorio de código fuente” y elige el proveedor (GitHub o Bitbucket).

  3. Selecciona la conexión, el repositorio y la rama. En caso de trabajar en un monorepo o que tu código fuente no se encuentre en la raíz del repositorio, especifica el directorio donde se encuentra tu proyecto de Astro (por ejemplo, “/” para la raíz).

  4. Configura el despliegue automático para que, cada vez que se haga push a la rama main (o la que hayas elegido), se inicie un nuevo despliegue.

  5. En la sección de compilación, selecciona “Usar un archivo de configuración” para que App Runner utilice el archivo apprunner.yaml que acabamos de crear.

  6. Define la cantidad de vCPU y memoria asignada. Para proyectos ligeros como prototipos o landings estaticas, 0.25 vCPU y 0.5GB pueden ser suficientes; puedes ajustar esta configuración según el uso que tu aplicación va a tener.

  7. App Runner viene con una configuración para el Auto Scaling por default, básicamente permite 100 peticiones en simultaneo por instancia y conforme este uso aumenta va incrementando de 1 instancia (el minímo) hasta 25 instancias (el máximo). Haciendo un calculo básico tenemos que esta configuración es capaz de responder a 2,500 peticiones al mismo tiempo. Puedes crear o modificar esta configuración para adaptarla a las necesidades de tu aplicación.

  8. App Runner hace una comprobación health check TCP con un timeout de 5 segundos. Si prefieres, puedes cambiar a HTTP y especificar una ruta (por ejemplo, /health).

  9. Ahora toca seleccionar un rol de instancia previamente creado que tenga permisos para acceder a los secretos (obligatorio si usas Secrets Manager o SSM).

    Asegúrate de que el rol tenga la siguiente relación de confianza:

     {
       "Version": "2012-10-17",
       "Statement": [
         {
           "Effect": "Allow",
           "Principal": {
             "Service": "tasks.apprunner.amazonaws.com"
           },
           "Action": "sts:AssumeRole"
         }
       ]
     }
    
  10. Si deseas que tu aplicación sea pública, puedes dejar la configuración de red por defecto. En caso de que quieras que tu aplicación sea privada, deberás configurar un punto de conexión privado y personalizar la VPC. En esta guía no abarcaremos este tema.

    Opcionalmente, habilita AWS WAF (ten en cuenta que puede generar costos adicionales) o configura AWS X-Ray para monitoreo.

  11. Revisa la configuración y haz clic en “Crear e implementar”. Si todo está correcto, verás cómo se despliega tu aplicación.

¡Y eso es todo! Hasta el momento, esta ha sido la forma más sencilla que he encontrado de desplegar aplicaciones con una experiencia similar a la que ofrece Vercel o Netlify. Si tienes alguna duda o presentas algún error siguiendo esta guía, no dudes en ponerte en contacto conmigo a través de Twitter en @yosoydev.

0
Subscribe to my newsletter

Read articles from Jesús Magallón directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jesús Magallón
Jesús Magallón