Manual de Deploy — VEGA
Este manual explica paso a paso qué pasa cuando se deploya VEGA: desde que Coolify toma el commit de GitHub hasta que Jarvis responde el primer mensaje en Telegram. Está pensado para entender los logs, diagnosticar errores y saber qué toca cuando se quiere agregar algo nuevo.
Arquitectura general
VEGA corre completamente en un VPS Contabo dentro de contenedores Docker orquestados
por Coolify. Hay dos redes Docker aisladas: ide-internal (el IDE donde
se escribe código) y vega-internal (donde viven los agentes).
El código vive en GitHub. Cuando se hace git push, Coolify detecta
el nuevo commit, construye la imagen Docker y reinicia el contenedor vega-openclaw.
El contenedor arranca el entrypoint.sh, que configura todo y
luego lanza openclaw gateway.
Flujo completo de deploy
| # | Paso | Quién lo hace | Qué pasa |
|---|---|---|---|
| 1 | git push origin main | Vos / Jarvis | El código nuevo llega a GitHub |
| 2 | Webhook GitHub → Coolify | GitHub automático | Coolify recibe aviso de nuevo commit |
| 3 | docker build | Coolify | Construye la imagen capa por capa (usa caché) |
| 4 | docker stop + docker run | Coolify | Reemplaza el contenedor viejo por el nuevo |
| 5 | entrypoint.sh | El contenedor nuevo | 8 stages de configuración (ver más abajo) |
| 6 | exec openclaw gateway | entrypoint.sh | OpenClaw arranca y conecta a Telegram |
| 7 | Jarvis responde | OpenClaw + GPT-5.4 | El sistema está operativo |
📦 El Dockerfile — Capas de construcción
El Dockerfile está dividido en 5 capas ordenadas de la que cambia menos a la que cambia más. Docker cachea cada capa por separado: si una capa no cambió, la reutiliza sin reconstruir. Esto es lo que hace que los deploys normales sean rápidos (~30 segundos) en lugar de 8 minutos.
📐 Regla de oro del Dockerfile
Lo que cambia menos va arriba. Lo que cambia más va abajo. Una capa modificada invalida todas las capas que están debajo de ella.
Capa 1 — Sistema operativo y herramientas base
NUNCA cambia · Tiempo: ~3 min · Siempre cacheada después del primer build
FROM node:24-slim
RUN apt-get update && apt-get install -y \
git ca-certificates \
chromium fonts-liberation libatk-bridge2.0-0 ... \
&& rm -rf /var/lib/apt/lists/*
ENV CHROME_BIN=/usr/bin/chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
¿Qué instala y para qué?
| Paquete | Para qué sirve |
|---|---|
git | Jarvis puede hacer pull/push al repo desde dentro del contenedor |
ca-certificates | Certificados SSL para que HTTPS funcione (Supabase, Telegram, ElevenLabs) |
chromium | Browser headless para el tool de navegación web de Jarvis |
fonts-liberation + libatk + libgbm + ... | Dependencias de sistema que Chromium necesita para correr en modo headless |
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true le dice a Puppeteer que no descargue su propio Chromium porque ya instalamos el del sistema. Sin esto, el build fallaría o descargaría un Chromium duplicado.
Capa 2 — OpenClaw
Cambia con versiones de OpenClaw · Tiempo: ~4 min · Cacheada hasta el próximo update
RUN npm install --no-audit --no-fund openclaw@2026.4.11 && \
ln -s /app/node_modules/.bin/openclaw /usr/local/bin/openclaw
¿Qué es OpenClaw?
OpenClaw es el runtime de agentes. Es el framework que maneja todo lo que Jarvis necesita para existir: conectarse a Telegram, recibir mensajes, llamar al modelo de IA, ejecutar plugins, manejar sesiones y turnos de conversación. Sin OpenClaw, habría que programar todo eso desde cero.
El ln -s crea un symlink para que el comando openclaw
esté disponible en cualquier directorio del sistema (en lugar de tener que
escribir /app/node_modules/.bin/openclaw cada vez).
--no-audit --no-fund: desactiva chequeos de seguridad en red y mensajes de donaciones de npm para acelerar el install.
Capa 3 — grammY (SDK de Telegram)
Rara vez cambia · Tiempo: ~2 min · Cacheada casi siempre
RUN npm install --no-audit --no-fund \
grammy \
@grammyjs/runner \
@grammyjs/transformer-throttler \
@grammyjs/conversations \
@grammyjs/menu \
@grammyjs/hydrate \
@grammyjs/parse-mode \
@grammyjs/ratelimiter
¿Qué es grammY y por qué está separado?
grammY es la librería de Node.js para bots de Telegram. OpenClaw la usa internamente para conectarse a la API de Telegram. Está separada en su propia capa porque es un conjunto estable de paquetes que casi nunca necesita actualizarse.
| Plugin grammY | Funcionalidad |
|---|---|
grammy | Core: recibir y enviar mensajes, fotos, audio |
@grammyjs/runner | Procesar múltiples mensajes en paralelo sin bloquear |
@grammyjs/transformer-throttler | Respetar los rate limits de Telegram automáticamente |
@grammyjs/conversations | Manejar conversaciones con estado (flujos multi-turno) |
@grammyjs/menu | Botones interactivos en los mensajes (zona 🔴 aprobaciones) |
@grammyjs/hydrate | Agregar métodos helper a los objetos de mensaje |
@grammyjs/parse-mode | Markdown/HTML en respuestas (bold, code, links) |
@grammyjs/ratelimiter | Limitar mensajes por usuario para evitar spam |
Capa 4 — Dependencias de plugins
Cambia al agregar integraciones · Tiempo: ~1 min
RUN npm install --no-audit --no-fund \
redis \
@supabase/supabase-js
¿Para qué sirve cada una?
| Paquete | Usada por | Para qué |
|---|---|---|
redis |
Plugin vega-context |
Conectarse a Redis para leer/escribir el rolling context de cada sesión (memoria de corto plazo, TTL 24hs) |
@supabase/supabase-js |
Plugin vega-context |
Persistir conversaciones y snapshots de memoria en Supabase (memoria de largo plazo) |
Estas librerías están en la imagen base (en /app/node_modules/) y también
se instalan localmente en cada extensión durante el Stage 6b del entrypoint.
La razón: Node resuelve módulos locales primero — si la extensión tiene su
propio node_modules/ incompleto, ignoraría los de la imagen base.
Capa 5 — Entrypoint
Cambia frecuentemente · Tiempo: <1 segundo · Solo copia 1 archivo
COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 18789 ENTRYPOINT ["/entrypoint.sh"]
Esta capa solo copia el script de arranque. Es la más liviana — no descarga nada. Gracias a las capas separadas anteriores, cambiar el entrypoint no invalida ninguna otra capa y el rebuild tarda segundos.
EXPOSE 18789 documenta que el gateway de OpenClaw escucha en ese puerto
(aunque en producción el acceso es solo interno, mapeado a 127.0.0.1:18789).
ENTRYPOINT ["/entrypoint.sh"]: cuando el contenedor arranca,
lo primero que ejecuta es este script. No hay un CMD separado —
el entrypoint es el proceso principal (PID 1 via exec openclaw gateway).
🚀 El Entrypoint — 8 Stages de arranque
El entrypoint.sh es el script que corre cuando el contenedor arranca. Hace toda la preparación necesaria antes de lanzar OpenClaw. Está dividido en stages numerados para facilitar el diagnóstico: si algo falla, el log dice exactamente en qué stage y por qué.
⚠️ Por qué no usamos set -e
set -e termina el script ante cualquier error, incluso los opcionales.
Preferimos manejar errores manualmente con dos helpers:
exit_critical (falla fatal — aborta el boot) y
boot_error (falla no fatal — registra el error y continúa).
Así, si Redis no está disponible, Jarvis arranca igual sin memoria de sesión
en lugar de no arrancar.
mkdir -p "$CONFIG_DIR/agents" "$CONFIG_DIR/workspace" "$CONFIG_DIR/extensions" "$CONFIG_DIR/logs"
Todo lo que OpenClaw necesita vive en /root/.openclaw/. Este stage
crea las 4 carpetas fundamentales. Si falla (disco lleno, permisos rotos),
es un error crítico — no se puede continuar.
| Carpeta | Contenido |
|---|---|
/root/.openclaw/agents/ | Character files de cada agente (jarvis, financiero, inversiones, bienestar) |
/root/.openclaw/workspace/ | Archivos de identidad de Jarvis (SOUL.md, IDENTITY.md, USER.md, AGENTS.md) |
/root/.openclaw/extensions/ | Plugins externos (vega-context y futuros) |
/root/.openclaw/logs/ | Logs internos de OpenClaw |
( cd /app/vega/.openclaw/agents && cp -r . "$CONFIG_DIR/agents/" ) 2>/dev/null
Los agentes no están dentro de la imagen Docker — viven en el repositorio
(/app/vega/.openclaw/agents/) que se monta como volumen en el contenedor.
Este stage los copia al directorio que OpenClaw espera leer.
¿Por qué subshell + cd en lugar de cp -r path/*?
En sh (no bash), si el directorio está vacío, el glob *
se pasa literalmente como string y cp falla. El patrón
( cd src && cp -r . dest/ ) funciona incluso con directorios vacíos.
Si este stage falla, Jarvis arranca sin agentes — no puede responder con personalidad pero OpenClaw inicia igual.
( cd /app/vega/.openclaw/workspace && cp -r . "$CONFIG_DIR/workspace/" ) 2>/dev/null
El workspace contiene los archivos que definen quién es Jarvis:
| Archivo | Contenido |
|---|---|
SOUL.md | El system prompt real de Jarvis — su personalidad, valores, forma de hablar |
IDENTITY.md | Datos de identidad: nombre, rol, quién es Rodrigo y Maria |
USER.md | Contexto del usuario: gustos, proyectos, cómo Jarvis debe tratarte |
AGENTS.md | Mapa del swarm: qué agentes existen y cuándo delegar a cada uno |
( cd /app/vega/.openclaw/extensions && cp -r . "$CONFIG_DIR/extensions/" ) 2>/dev/null
OpenClaw tiene dos tipos de plugins:
| Tipo | Ejemplos | Confianza | Cómo se habilitan |
|---|---|---|---|
| Bundled | groq, elevenlabs, firecrawl | Auto-trusted | "enabled": true en plugins.entries |
| Externos | vega-context | Requiere allowlist | Debe estar en plugins.allow además de entries |
vega-context es el plugin que conecta OpenClaw con Redis y Supabase.
Lo desarrollamos nosotros — vive en .openclaw/extensions/vega-context/ del repo.
git config --global user.name "Jarvis Vega"
git config --global user.email "jarvisvegaia@gmail.com"
git config --global url."https://${VEGA_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"
git -C /app/vega pull --rebase origin main
Jarvis puede modificar archivos del repo (su character, skills, wikis) y pushearlos a GitHub sin intervención humana. Para eso necesita que git esté configurado con credenciales válidas.
safe.directory: git 2.35+ requiere que cada directorio sea marcado
como seguro explícitamente (medida de seguridad para evitar que procesos de
otros usuarios accedan a repos ajenos).
El pull --rebase al arrancar sincroniza el repo local con GitHub
por si hubo cambios entre el deploy anterior y este arranque. El stash
antes del pull protege cambios locales que Jarvis haya hecho.
Si VEGA_GITHUB_TOKEN no está seteado, este stage emite un warning
pero no falla — Jarvis arranca sin capacidad de push.
# Caso 1: token configurado en Coolify (recomendado) GATEWAY_TOKEN="$OPENCLAW_GATEWAY_TOKEN" # Caso 2: fallback — token aleatorio efímero GATEWAY_TOKEN=$(tr -dc 'a-f0-9' < /dev/urandom | head -c 64) if [ -z "$GATEWAY_TOKEN" ]; then exit_critical "No se pudo generar gateway token" fi
El gateway token es la contraseña que protege la API HTTP interna de OpenClaw (puerto 18789). Sin él, cualquiera en la red Docker podría enviarle instrucciones directas a los agentes.
| Escenario | Resultado |
|---|---|
OPENCLAW_GATEWAY_TOKEN seteado en Coolify | Se usa ese token — persiste entre restarts |
| No seteado | Se genera uno nuevo con /dev/urandom — se pierde al reiniciar |
/dev/urandom y no openssl rand
La imagen base node:24-slim no incluye OpenSSL. Intentar usarlo crashea el contenedor.
/dev/urandom es igual de seguro para este uso.
for ext_dir in "$CONFIG_DIR/extensions"/*/; do
if [ -f "$ext_dir/package.json" ]; then
npm_output=$(npm install --prefix "$ext_dir" --no-audit --no-fund 2>&1)
# muestra últimas 5 líneas del error si falla
fi
done
Los plugins externos (como vega-context) tienen sus propias dependencias
(redis, @supabase/supabase-js). Node.js resuelve módulos
locales primero: si la carpeta node_modules/ del plugin existe pero
está incompleta, ignorará los paquetes de la imagen base.
Este stage garantiza que cada extensión tenga sus deps actualizadas en cada arranque. Si falla, el log muestra las últimas 5 líneas del error de npm para poder diagnosticar.
cat > "$CONFIG_FILE" <<EOF
{ ... configuración completa con variables de entorno ... }
EOF
# Validación post-escritura
node -e "JSON.parse(require('fs').readFileSync('$CONFIG_FILE','utf8'))"
El archivo /root/.openclaw/openclaw.json es la configuración central
de OpenClaw — le dice al gateway quiénes son los agentes, cómo conectarse a Telegram,
qué plugins activar y cómo comportarse.
Las variables de entorno (${TELEGRAM_BOT_TOKEN}, ${GATEWAY_TOKEN}, etc.)
se inyectan dentro del JSON durante la generación. Por eso la validación con
node -e JSON.parse() es crítica: si una variable contiene comillas
o caracteres especiales, el JSON quedaría inválido y OpenClaw no arrancaría.
Cada variable tiene una criticidad:
| Estado | Significado | Consecuencia |
|---|---|---|
| ✅ OK | La variable está seteada | La funcionalidad asociada está activa |
| ⚠️ no definida | Variable opcional ausente | Esa funcionalidad está desactivada, pero Jarvis arranca |
| ❌ FALTA | Variable crítica ausente | Cuenta como boot_error — OpenClaw puede no funcionar |
⚙️ openclaw.json — Sección por sección
gateway
{
"gateway": {
"mode": "local",
"auth": {
"mode": "token",
"token": "${GATEWAY_TOKEN}"
}
}
}
El gateway es la API HTTP interna que recibe mensajes de los canales
(Telegram, futuros) y los distribuye a los agentes.
mode: "local" significa que corre en el mismo proceso que los agentes.
auth.mode: "token" requiere que todo cliente que llame a la API
presente el token correcto en el header.
agents
{
"agents": {
"defaults": {
"workspace": "/root/.openclaw/workspace",
"model": { "primary": "openai-codex/gpt-5.4" }
},
"list": [
{ "id": "jarvis", "default": true, "agentDir": "..." },
{ "id": "financiero", "agentDir": "..." },
{ "id": "inversiones", "agentDir": "..." },
{ "id": "bienestar", "agentDir": "..." }
]
}
}
| Campo | Qué hace |
|---|---|
defaults.workspace | Directorio compartido que todos los agentes pueden leer (SOUL.md, IDENTITY.md, etc.) |
defaults.model.primary | Modelo LLM por defecto para todos los agentes |
list[].id | Identificador único del agente — se usa en bindings y logs |
list[].default: true | Agente que responde si ningún binding coincide (Jarvis) |
list[].agentDir | Directorio con el character.md y skills del agente |
bindings
{
"bindings": [
{ "agentId": "jarvis", "match": { "channel": "telegram", "peer": { "kind": "group", "id": "-1003976110854:topic:1" } } },
{ "agentId": "financiero", "match": { "channel": "telegram", "peer": { "kind": "group", "id": "-1003976110854:topic:2" } } },
...
]
}
Los bindings son las reglas de routing: qué agente responde a qué mensaje. Cuando llega un mensaje de Telegram, OpenClaw busca el binding que coincida con el canal y el peer (grupo + topic) y le asigna el agente correspondiente.
| Topic ID | Agente | Rol |
|---|---|---|
| 1 | Jarvis | General — orquestador, temas variados |
| 2 | Financiero | Gastos, transacciones, presupuesto |
| 6 | Inversiones | Cartera, CEDEARs, MEP, portfolio |
| 7 | Bienestar | Salud, hábitos, sueño |
| 8 | Jarvis | Aprobaciones zona 🔴 |
El ID -1003976110854:topic:2 es el ID interno de Telegram del grupo (-1003976110854) combinado con el número de topic (2).
channels
{
"channels": {
"telegram": {
"enabled": true,
"botToken": "${TELEGRAM_BOT_TOKEN}",
"dmPolicy": "allowlist",
"allowFrom": ["601102393"],
"groups": {
"-1003976110854": {
"requireMention": false,
"allowFrom": ["601102393"]
}
}
}
}
}
| Campo | Qué hace |
|---|---|
botToken | Token del bot (@vega_jarvis_bot) — obtenido de @BotFather |
dmPolicy: "allowlist" | Solo usuarios en la lista pueden mandar DMs al bot |
allowFrom: ["601102393"] | Chat ID de Rodrigo — único autorizado (por ahora) |
requireMention: false | Jarvis responde sin necesidad de escribir @vega_jarvis_bot |
plugins
{
"plugins": {
"enabled": true,
"allow": ["vega-context"],
"slots": { "contextEngine": "vega-context" },
"entries": {
"groq": { "enabled": true },
"elevenlabs": { "enabled": true },
"firecrawl": { "enabled": true },
"vega-context": { "enabled": true }
}
}
}
allow los plugins externos (no-bundled).
Los plugins bundled (groq, elevenlabs, firecrawl) son auto-trusted y NO deben
ir en allow — ponerlos ahí rompe el canal de Telegram.
groq
Transcripción de audio a texto (STT). Cuando mandás un audio de voz a Jarvis, Groq lo transcribe con whisper-large-v3 antes de enviarlo al LLM.
elevenlabs
Texto a voz (TTS). Convierte las respuestas de Jarvis a audio con la voz personalizada. Solo respuestas cortas (≤280 chars).
firecrawl
Web fetch. Cuando le mandás una URL a Jarvis, Firecrawl extrae el contenido limpio (sin HTML) para que el LLM lo procese.
vega-context
Rolling context. Antes de cada respuesta lee el contexto de Redis. Después de cada respuesta lo actualiza en Redis + persiste en Supabase.
slots.contextEngine: "vega-context" le dice a OpenClaw que este plugin es el motor de contexto — se llama automáticamente en cada turno.
tools
{
"tools": {
"web": {
"search": { "enabled": true, "openaiCodex": { "enabled": true, "mode": "live" } },
"fetch": { "enabled": true, "provider": "firecrawl" }
},
"media": {
"audio": { "enabled": true, "language": "es", "models": [{ "provider": "groq", "model": "whisper-large-v3" }] },
"image": { "enabled": true }
}
}
}
| Tool | Capacidad que le da a Jarvis |
|---|---|
web.search vía Codex | Buscar en internet nativamente (GPT-5.4 tiene búsqueda integrada, sin API key extra) |
web.fetch vía Firecrawl | Leer el contenido de una URL y procesarlo |
media.audio vía Groq | Escuchar y transcribir mensajes de voz en español |
media.image | Recibir y procesar imágenes enviadas por Telegram |
messages.tts
{
"messages": {
"tts": {
"auto": "inbound",
"provider": "elevenlabs",
"maxTextLength": 280,
"providers": {
"elevenlabs": {
"voiceId": "3cBYXsbKACmcb0rHwMjm",
"modelId": "eleven_multilingual_v2",
"voiceSettings": {
"stability": 0.64,
"similarityBoost": 0.80,
"style": 0.12,
"useSpeakerBoost": true,
"speed": 1.18
}
}
}
}
}
}
| Parámetro | Valor | Qué hace |
|---|---|---|
auto: "inbound" | inbound | Solo convierte a voz las respuestas a mensajes de voz entrantes |
maxTextLength: 280 | 280 chars | Respuestas más largas no se convierten (evita audios interminables) |
voiceId | 3cBYXsbKACmcb0rHwMjm | Voz personalizada de Jarvis — creada en ElevenLabs (porteño ejecutivo) |
stability: 0.64 | — | Consistencia de la voz (más alto = más monótona) |
similarityBoost: 0.80 | — | Qué tan fiel al sample original (más alto = más parecida) |
style: 0.12 | — | Expresividad emocional (bajo = profesional, no dramatizado) |
speed: 1.18 | — | Velocidad de habla (1.0 = normal, 1.18 = levemente más rápido) |
useSpeakerBoost: true | — | Mejora la nitidez de la voz del speaker |
📋 Variables de entorno — Referencia completa
| Variable | Criticidad | Qué activa |
|---|---|---|
TELEGRAM_BOT_TOKEN |
CRÍTICA | Sin esto el bot no arranca. Se obtiene de @BotFather en Telegram. |
OPENCLAW_GATEWAY_TOKEN |
Recomendada | Seguridad de la API interna. Si no está, se genera uno efímero (se regenera en cada restart). |
GROQ_API_KEY |
Opcional | STT — transcripción de audios de voz. Sin esto Jarvis no entiende mensajes de voz. |
ELEVENLABS_API_KEY |
Opcional | TTS — respuestas de voz. Sin esto Jarvis responde solo con texto. |
FIRECRAWL_API_KEY |
Opcional | Web fetch limpio. Sin esto el fetch de URLs funciona con el fallback nativo (menos preciso). |
REDIS_URL |
Opcional | Memoria de corto plazo (rolling context TTL 24hs). Sin esto vega-context no puede guardar el contexto de sesión. |
SUPABASE_URL |
Opcional | Persistencia en DB. Sin esto las conversaciones no se guardan a largo plazo. |
SUPABASE_SERVICE_KEY |
Opcional | Necesaria junto con SUPABASE_URL. Es la clave con permisos de escritura (service role, bypasea RLS). |
VEGA_GITHUB_TOKEN |
Opcional | Permite a Jarvis hacer git push al repo desde dentro del contenedor (para actualizar sus propios archivos). |
🔴 Errores comunes y diagnóstico
❌ CRÍTICO — entrypoint abortado · Causa: openclaw.json inválido
Cuándo aparece: Una variable de entorno contiene comillas, saltos de línea u otros caracteres especiales que rompen el JSON.
Fix: Revisar en Coolify las env vars. Las más problemáticas son tokens que contienen " o \n. Regenerar el token si es necesario.
❌ ERROR STAGE [N]: npm install falló en vega-context
Cuándo aparece: Falla de red durante el Stage 6b, o package.json con un paquete inexistente.
Fix: Revisar las últimas 5 líneas del error que imprime el Stage 6b. Si es red, el próximo restart suele resolver. Si es paquete, revisar el package.json de la extensión.
⚠️ git pull --rebase falló — continuando con código local
Cuándo aparece: Conflicto de merge, red sin conectividad, o token de GitHub vencido.
Consecuencia: Jarvis arranca con el código del último deploy exitoso. No es crítico pero puede tener código desactualizado.
⚠️ Boot con N error(es) — OpenClaw arranca igual
Cuándo aparece: Uno o más stages no-críticos fallaron (Redis no disponible, agentes no encontrados, etc.).
Fix: Buscar arriba en los logs las líneas con ❌ ERROR STAGE para identificar exactamente qué falló.
Telegram inicializado pero Jarvis no responde
Cuándo aparece: El log muestra todo verde pero los mensajes en Telegram no tienen respuesta.
Causas posibles:
- El chat ID del que escribe no está en
allowFrom - El topic ID del mensaje no está en
bindings plugins.allowtiene plugins bundled (rompe la inicialización interna)- Bug de versión de OpenClaw (ocurrió con 2026.4.2 — actualizar a 2026.4.11+)
Diagnóstico: Revisar el log completo de OpenClaw en /tmp/openclaw/openclaw-YYYY-MM-DD.log dentro del contenedor.
🔄 Force deploy vs Deploy normal
Coolify cachea las capas del Dockerfile. Cuando una capa no cambió, la reutiliza sin reconstruir. Pero a veces se necesita forzar la reconstrucción desde cero.
| Qué cambió | Tipo de deploy | Tiempo estimado |
|---|---|---|
Solo entrypoint.sh |
Normal | ~30 segundos (solo capa 5) |
Archivos en .openclaw/ (agentes, extensions) |
Normal | <5 segundos (son volúmenes, no imagen) |
| Versión de OpenClaw en el Dockerfile | Force deploy | ~6-8 minutos (capas 2-4 se reconstruyen) |
Nuevo npm install en cualquier capa |
Force deploy | ~6-8 minutos |
Nuevo paquete en apt-get install |
Force deploy | ~10+ minutos (todas las capas) |