DEV Community

Cover image for Sesiones remotas siempre disponibles para tus agentes de codigo

Sesiones remotas siempre disponibles para tus agentes de codigo

Una sesión de agente que sobrevive a cerrar la laptop, corriendo en una sola instancia EC2 chica que manejas desde el teléfono. Este post cubre tanto Claude Code como Kiro CLI, porque resuelven el problema de "alcanzar la sesión desde otro lado" de maneras completamente distintas, y esa diferencia decide la mayor parte de tu arquitectura.

TL;DR

Versión ingenua Después
Las credenciales viven en el volumen raíz de la instancia Secrets Manager
Sobrevive al reemplazo de la instancia no, re-login manual sí, sin atender (~2 min)
Tiempo para notar una caja muerta 6 días (a mano) 15 min (alarma)
Clone fallido al arrancar tragado por `\ \
Ciclo de vida acoplado a el stack de producción su propio stack

El costo es una instancia ARM on-demand chica más un volumen gp3 de 30 GB: revisa el precio de lista actual de tu región, pero es la parte más barata de todo esto.

Una instancia chica siempre encendida lo resuelve, con dos condiciones:
puedes alcanzar la sesión desde donde estés, y la caja regresa por su
cuenta cuando la infraestructura se mueve por debajo de ella. La segunda es de lo que este post trata en realidad.

Los dos modelos remotos

Esta es la bifurcación del camino, así que hazla bien antes de construir nada.

Claude Code tiene una sesión remota de primera mano. {% raw %}claude
remote-control
registra la sesión en curso con un punto de encuentro
hospedado, y la manejas desde claude.ai/code o la app móvil. La caja hace una conexión de salida nada más:

claude remote-control --name myapp-cloud --continue
Enter fullscreen mode Exit fullscreen mode

--continue reanuda la misma sesión a través de reinicios del proceso, así que un enlace en marcadores se queda estable cuando el servicio rebota.
Esto necesita un CLI reciente: la caja de este post corría la 2.1.211. Al tener éxito imprime:

Take this session with you and pick up right where you left off on any device.
Open the Code tab in the Claude mobile app, or visit claude.ai/code in a browser.
The session keeps running on this machine. Use your other devices as a remote control. Press Ctrl+C to stop.
Enter fullscreen mode Exit fullscreen mode

La consecuencia de seguridad es grande: sin puertos de entrada, sin SSH, sin listener público. El security group puede ser solo de salida.

Kiro CLI no tiene equivalente. Sus docs describen dos modos, ninguno de los cuales es una sesión remota:

  • Headless, para CI: kiro-cli chat --no-interactive "prompt", autenticado por KIRO_API_KEY. Los docs son explícitos en que "No es posible input del usuario a media sesión": un solo prompt, de principio a fin, sin reanudar.
  • Interactivo con persistencia local: las sesiones se auto-guardan por directorio y regresan con kiro-cli chat --resume (también --resume-picker, --resume-id <ID>, --list-sessions).

Así que para Kiro la sesión siempre encendida es algo que construyes:
mantén el proceso vivo en un multiplexor de terminal, y conéctate a él por SSM.

# en la caja, como el usuario de la app
tmux new-session -d -s agent 'kiro-cli chat'

# desde donde sea
aws ssm start-session --target "$INSTANCE_ID"
sudo -iu appuser tmux attach -t agent
Enter fullscreen mode Exit fullscreen mode

--resume es la red de seguridad más que el mecanismo: si el proceso se muere, la conversación sigue en disco, indexada por el directorio de trabajo.

Lectura práctica: en un teléfono, Claude Code gana de calle, una pestaña de navegador le gana a un shell de SSM móvil conectándose a tmux. El modelo de Kiro le queda mejor a una laptop o tablet con una terminal de verdad. El resto de este post aplica a los dos, porque las partes difíciles (credenciales, recuperación, monitoreo) son idénticas.

Construir el lado de Claude Code

Una unidad de systemd, y el proceso mismo es la sesión. Como la conexión es de salida, no se requiere nada más.

ExecStart=/home/appuser/.local/bin/claude remote-control --name myapp-cloud --continue
Restart=always
RestartSec=30
Enter fullscreen mode Exit fullscreen mode

La salud es lo que sea que diga systemd: systemctl is-active. Esa es toda la integración.

Construir el lado de Kiro

Kiro necesita que lo durable sea la terminal, no el proceso del agente, porque no hay sesión que re-registrar. Corre tmux bajo systemd y deja que el agente viva adentro:

[Unit]
Description=Kiro agent session (tmux)
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=0

[Service]
Type=forking
User=appuser
WorkingDirectory=/home/appuser/myapp
Environment=PATH=/home/appuser/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/tmux new-session -d -s agent 'kiro-cli chat --resume'
ExecStop=/usr/bin/tmux kill-session -t agent
RemainAfterExit=yes
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

--resume en ExecStart es deliberado: si la caja reinicia, la sesión nueva de tmux se reconecta a la conversación que ya está en disco para ese directorio en lugar de arrancar en frío.

Conéctate desde donde sea por SSM, todavía sin puertos de entrada:

aws ssm start-session --target "$INSTANCE_ID"
sudo -iu appuser tmux attach -t agent
Enter fullscreen mode Exit fullscreen mode

La salud significa algo distinto aquí, y este es el único lugar donde los dos caminos de verdad divergen. systemctl is-active sobre una unidad tmux de tipo forking te dice que tmux está vivo, no que el agente adentro lo esté. Pregúntale a tmux directo:

if sudo -u appuser tmux has-session -t agent 2>/dev/null; then
  PANES=$(sudo -u appuser tmux list-panes -t agent -F '#{pane_dead}' | grep -c '^0$')
  [ "$PANES" -gt 0 ] && heartbeat OK "tmux:$PANES" || fail "tmux session has no live pane"
else
  fail "tmux session missing"
fi
Enter fullscreen mode Exit fullscreen mode

#{pane_dead} es el detalle que vale la pena guardar. Una sesión de tmux cuyo único panel ya salió sigue respondiendo has-session con éxito, así que el chequeo ingenuo reporta sano una sesión con un agente muerto adentro, la misma clase de falso negativo que el timer inerte de más adelante.

La historia de credenciales de Kiro es más simple en un aspecto: la auth headless es una sola API key en KIRO_API_KEY, sin flujo de navegador y sin expiración de sesión, que cae directo en el mismo patrón de Secrets Manager. Vale la pena tenerla en la caja aunque manejes de forma interactiva, porque hace útil la caja para one-shots por script:

kiro-cli chat --no-interactive --trust-all-tools "run the test suite and summarise failures"
Enter fullscreen mode Exit fullscreen mode

Sé deliberado con --trust-all-tools en una máquina sin atender:
auto-aprueba cada tool call. --trust-tools=read,grep es el default más seguro para cualquier cosa agendada.

Si de plano prefieres no construir nada de esto para Kiro, AWS publica una muestra Kiro IDE Remote de un clic que pone el IDE completo en un escritorio remoto alcanzable desde un navegador, con Kiro CLI y el AWS CLI preinstalados. Más pesada que una instancia chica, y un perfil de costo distinto, pero se salta el armado.

El punto de partida, y qué tenía de malo

La caja original era una instancia EC2 definida dentro del stack del
backend de producción. El user-data instalaba el toolchain, clonaba el repo, escribía una unidad de systemd, y la dejaba deshabilitada a propósito: arrancar un agente sin autenticar nada más entra en crash-loop.
Un humano luego se metía por SSM una vez, iniciaba sesión de forma
interactiva, y habilitaba el servicio.

// Dentro del stack del backend de producción. Tres errores separados.
userData.addCommands(
  "sudo -u appuser bash -lc 'curl -fsSL https://example-agent-installer.sh | bash' || true",
  `sudo -u appuser bash -lc 'test -d ~/myapp || git clone https://github.com/acme/myapp.git ~/myapp' || true`,
  // ...archivo de unidad escrito aquí, a propósito sin habilitar...
  "systemctl daemon-reload"
);
Enter fullscreen mode Exit fullscreen mode

Eso funcionó por meses. Luego se movió el AMI.

La instancia usaba MachineImage.fromSsmParameter(...) para seguir la imagen actual de Ubuntu 24.04. Cuando ese parámetro resolvió un AMI más nuevo, CloudFormation vio cambiar el ImageId, lo cual es un reemplazo, no una actualización. Instancia nueva, volumen nuevo, y todo lo del volumen raíz viejo se fue.

El reemplazo arrancó y se veía bien. No lo estaba:

+ sudo -u ubuntu bash -lc 'test -d ~/myapp || git clone https://github.com/acme/myapp.git ~/myapp'
Cloning into '/home/ubuntu/myapp'...
fatal: could not read Username for 'https://github.com': No such device or address
+ true
Enter fullscreen mode Exit fullscreen mode

Un repo privado, clonado sobre https pelón, sin credenciales. || true
se lo tragó, cloud-init reportó un arranque limpio, y la falla era
invisible a menos que alguien leyera el log.

Las consecuencias se encadenaron. Sin repo, el WorkingDirectory de la unidad no existía, así que el servicio no podía arrancar aunque estuviera habilitado. No estaba habilitado, porque eso es un paso manual. Y el login del agente vivía solo en el volumen que se acababa de destruir:

$ systemctl status agent-remote-control
   Loaded: loaded (/etc/systemd/system/agent-remote-control.service; disabled; preset: enabled)
   Active: inactive (dead)
Enter fullscreen mode Exit fullscreen mode

Seis días después alguien fue a usarla.

Tres fallas independientes, una causa raíz: cada camino de recuperación requería un humano, y nada decía que se necesitaba un humano.

Ronda 1: las credenciales van en un almacén de secretos, no en un volumen

Si la caja no puede re-autenticarse sola, cada reemplazo es un outage. Dos secretos, sembrados una vez por cuenta en lugar de por instancia:

/myapp/agent-box-github-token        -> string de token pelón
/myapp/agent-box-agent-credentials   -> blob JSON de credenciales
Enter fullscreen mode Exit fullscreen mode

El script de bootstrap jala los dos, autentica, clona, y habilita el
servicio. Cada paso es idempotente, así que es seguro correrlo en cada arranque y en un timer.

log "1/5 GitHub auth"
if gh auth status >/dev/null 2>&1; then
  log "    already authenticated"
else
  GH_TOKEN_VALUE="$(secret "$GH_SECRET")"
  [ -n "$GH_TOKEN_VALUE" ] || fail "could not read $GH_SECRET"
  printf '%s' "$GH_TOKEN_VALUE" | gh auth login --with-token || fail "gh auth login failed"
  unset GH_TOKEN_VALUE
fi
gh auth setup-git >/dev/null 2>&1 || true
Enter fullscreen mode Exit fullscreen mode

Dos detalles no obvios:

  1. El script no se puede descargar. La jugada obvia es hacerle curl desde el repo en el user-data. Ese repo es privado, y el token que autorizaría la descarga es lo que el script instala. Circular. En su lugar, incrusta el script en el user-data: léelo en tiempo de synth y mételo en base64.

  2. El alcance de IAM es exactamente dos ARNs. La caja obtiene secretsmanager:GetSecretValue sobre sus propios secretos de bootstrap y nada más. No tiene por qué leer el password de la base de datos.

role.addToPolicy(new iam.PolicyStatement({
  sid: "ReadBootstrapSecrets",
  actions: ["secretsmanager:GetSecretValue"],
  resources: [
    `arn:aws:secretsmanager:${region}:${account}:secret:/myapp/agent-box-github-token-*`,
    `arn:aws:secretsmanager:${region}:${account}:secret:/myapp/agent-box-agent-credentials-*`,
  ],
}));
Enter fullscreen mode Exit fullscreen mode

Trampa 1: el blob de credenciales no es solo el token

Restaurar solo el token OAuth producía un servicio que arrancaba y se moría de inmediato:

Error: Unable to determine your organization for Remote Control eligibility. Run `claude auth login` to refresh your account information.
Enter fullscreen mode Exit fullscreen mode

La funcionalidad de remote-control necesita contexto de cuenta, los
identificadores de la cuenta y de la organización, junto con el token.
Incómodamente, en macOS estos viven en dos lugares distintos: el token en el keychain de login, el objeto de cuenta en el propio archivo de config del CLI. Una restauración que lee solo uno de los dos se ve completa y falla en runtime.

Guarda los dos en un secreto y escríbelos a sus dos destinos en la caja:

if sec.get("oauthAccount"):
    d["oauthAccount"] = sec["oauthAccount"]
if sec.get("organizationUuid"):
    d["organizationUuid"] = sec["organizationUuid"]
Enter fullscreen mode Exit fullscreen mode

Trampa 2: el diálogo de trust headless

Un CLI de agente pregunta si debería confiar en un workspace en el primer uso dentro de un directorio. Un servicio de systemd no tiene TTY y no puede contestar, así que entra en crash-loop en el prompt. Pre-configura la bandera:

e = d.setdefault("projects", {}).setdefault(workdir, {})
e["hasTrustDialogAccepted"] = True
e["hasCompletedProjectOnboarding"] = True
d["hasCompletedOnboarding"] = True
Enter fullscreen mode Exit fullscreen mode

En esta es fácil perder una hora, porque el texto del error es sobre trust y no dice nada de TTYs.

Trampa 3: un heredoc se come tu pipe en silencio

La primera versión del paso de contexto de cuenta hacía pipe de un secreto hacia Python mientras también usaba un heredoc para el script:

# ROTO
secret "$CLAUDE_SECRET" | python3 - "$WORKDIR" <<'TPY'
sec = json.load(sys.stdin)
Enter fullscreen mode Exit fullscreen mode

El heredoc es stdin. El pipe se descarta, y Python lee su propio código fuente como el payload JSON:

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Enter fullscreen mode Exit fullscreen mode

Escribe el secreto a un archivo mktemp con umask 077 y pasa la ruta como argumento.

Ronda 2: mantenerlo corriendo

Restart=always maneja el caso fácil: el proceso crasheó, arráncalo de nuevo.

[Unit]
Description=Agent remote-control session
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=0

[Service]
User=appuser
WorkingDirectory=/home/appuser/myapp
Environment=PATH=/home/appuser/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/appuser/.local/bin/claude remote-control --name myapp-cloud --continue
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

StartLimitIntervalSec=0 importa más de lo que parece. Por default systemd se rinde después de una ráfaga de fallas rápidas y estaciona la unidad en failed permanentemente. En una caja sin atender eso convierte un problema de red pasajero en un outage que dura hasta que alguien se da cuenta, que es justo el modo de falla que estamos diseñando para que no pase.

Pero Restart=always no puede ayudar con los estados que de verdad mataron esta caja: credenciales faltantes, un clone borrado, una unidad dejada deshabilitada. Esos necesitan algo fuera de la unidad. Como el script de bootstrap es idempotente, el watchdog es nada más el bootstrap en un timer:

[Unit]
Description=Watchdog for the agent session

[Timer]
OnBootSec=60
OnCalendar=*:0/5
AccuracySec=30s
Persistent=true

[Install]
WantedBy=timers.target
Enter fullscreen mode Exit fullscreen mode

Trampa 4: OnUnitActiveSec en una unidad que nunca ha corrido

El primer timer usaba OnUnitActiveSec=5min. Se instaló limpio, reportó habilitado, y nunca disparó: ese setting es relativo a la última activación de la unidad, y una unidad que nunca ha corrido no tiene siguiente disparo:

NEXT  LEFT  LAST                          PASSED     UNIT
-     -     Wed 2026-07-22 16:43:57 UTC   13ms ago   agent-bootstrap.timer
Enter fullscreen mode Exit fullscreen mode

NEXT es -. Un watchdog que está inerte en silencio es peor que ninguno, porque se lee como cubierto. Usa OnCalendar de reloj de pared en su lugar.

Ronda 3: la parte que de verdad lo habría atrapado

Todo lo de arriba acorta el outage. Nada de eso acorta los seis días, porque una recuperación de la que nunca te enteras es indistinguible de ninguna recuperación.

El watchdog ya corre cada cinco minutos y ya sabe si el servicio está sano.
Haz que lo diga, en voz alta, a algún lugar fuera de la caja:

heartbeat() {
  local status="$1" detail="${2:-}"
  local stream ts
  stream="$(cat /var/lib/cloud/data/instance-id 2>/dev/null || hostname)"
  ts="$(date +%s)000"
  aws logs create-log-stream --region "$REGION" --log-group-name "$LOG_GROUP" \
    --log-stream-name "$stream" >/dev/null 2>&1 || true
  aws logs put-log-events --region "$REGION" --log-group-name "$LOG_GROUP" \
    --log-stream-name "$stream" \
    --log-events "timestamp=$ts,message=AGENT_BOX_HEARTBEAT $status $detail" \
    >/dev/null 2>&1 || log "WARN: heartbeat not delivered"
}

fail() { log "ERROR: $*"; heartbeat DOWN "$*"; exit 1; }
Enter fullscreen mode Exit fullscreen mode

Un metric filter cuenta las líneas sanas, y la alarma dispara ante su
ausencia:

new logs.MetricFilter(this, "HeartbeatMetricFilter", {
  logGroup,
  metricNamespace: "myapp/AgentBox",
  metricName: "RemoteControlHeartbeat",
  filterPattern: logs.FilterPattern.literal('"AGENT_BOX_HEARTBEAT OK"'),
  metricValue: "1",
  defaultValue: 0,
});

new cw.Alarm(this, "RemoteControlDownAlarm", {
  alarmName: "myapp-agent-box-down",
  metric: new cw.Metric({
    namespace: "myapp/AgentBox",
    metricName: "RemoteControlHeartbeat",
    period: cdk.Duration.minutes(15),
    statistic: "Sum",
  }),
  threshold: 1,
  comparisonOperator: cw.ComparisonOperator.LESS_THAN_THRESHOLD,
  evaluationPeriods: 1,
  treatMissingData: cw.TreatMissingData.BREACHING,
}).addAlarmAction(new cwactions.SnsAction(alarmTopic));
Enter fullscreen mode Exit fullscreen mode

treatMissingData: BREACHING es todo el diseño. El instinto es alarmar sobre una línea de error, pero cada modo de falla en este postmortem produjo cero logs: una instancia que nunca arrancó, un rol sin permisos, un watchdog que nunca disparó. La ausencia es la señal. Esta es la única alarma de la cuenta que no debe caer por default en NOT_BREACHING.

Dos detalles de apoyo. El heartbeat dispara también en el camino de falla, así que una corrida rota es tan visible como una sana: la alarma se indexa en la ausencia de OK, no en la presencia de DOWN. Y activating cuenta como sano, ya que es el estado normal por unos segundos después de cualquier reinicio:

STATE="$(systemctl is-active agent-remote-control 2>/dev/null)"
case "$STATE" in
  active|activating) heartbeat OK "$STATE" ;;
  *) fail "service is $STATE" ;;
esac
Enter fullscreen mode Exit fullscreen mode

Cambia por el chequeo de tmux has-session + #{pane_dead} de antes para una caja de Kiro. Todo lo de aguas abajo (log group, metric filter, alarma, topic de SNS) es idéntico, porque la línea de heartbeat es el único contrato entre la caja y la alarma.

Ronda 4: deja de compartir ciclo de vida con producción

La caja original vivía en el stack del backend de producción. Eso es lo que la mató: un parámetro de AMI dentro de ese stack reemplazó la instancia. Cada deploy de producción también la detenía/arrancaba.

Una comodidad de desarrollo no tiene por qué compartir ciclo de vida con producción. Su propio stack, con un lookup de VPC en lugar de un export entre stacks, un export recrearía el acoplamiento que la separación existe para quitar:

export class AgentBoxStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: Props) {
    super(scope, id, props);

    const vpc = ec2.Vpc.fromLookup(this, "Vpc", { vpcName: "myapp-vpc" });

    const alarmTopic = new sns.Topic(this, "AgentBoxAlerts", {
      topicName: "myapp-agent-box-alerts",
    });
    alarmTopic.addSubscription(new snssub.EmailSubscription("ops@example.com"));

    new AgentBox(this, "AgentBox", {
      appName: "myapp",
      vpc,
      githubRepo: "acme/myapp",
      alarmTopic,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Su propio topic de SNS, también. Importar el topic de producción metería el stack de vuelta justo en el grafo de dependencias.

El movimiento destruye y recrea la instancia, y el orden no es opcional si algún recurso tiene un nombre físico fijo. El rol de IAM aquí lo tiene:

npx cdk deploy MyApp-Backend    # borra la caja vieja y su rol de nombre fijo
npx cdk deploy MyApp-AgentBox   # los crea de nuevo
Enter fullscreen mode Exit fullscreen mode

Al revés, el segundo deploy falla por un nombre de rol duplicado.

El resultado

La separación del stack destruyó la instancia y construyó una nueva desde cero, lo que reproduce la falla original exactamente. Esta vez nadie la tocó:

MyApp-AgentBox | 12/13 | CREATE_COMPLETE | AWS::EC2::Instance | AgentBox/Instance
MyApp-AgentBox | 13/13 | CREATE_COMPLETE | AWS::CloudFormation::Stack | MyApp-AgentBox
 ✅  MyApp-AgentBox
✨  Deployment time: 177.81s
Enter fullscreen mode Exit fullscreen mode

Como dos minutos después, sin atender:

[agent-box-bootstrap] 1/5 GitHub auth
[agent-box-bootstrap] 2/5 Repo clone
[agent-box-bootstrap] 3/5 Agent credentials
[agent-box-bootstrap] 4/5 Account context + workspace trust
[agent-box-bootstrap] 5/5 Enabling the always-on service
[agent-box-bootstrap] done (active)
systemd[1]: Finished agent-bootstrap.service.
systemd[1]: agent-bootstrap.service: Consumed 2.954s CPU time, 52.5M memory peak
Enter fullscreen mode Exit fullscreen mode

Y la alarma hizo su trabajo a lo largo de toda la ventana. Durante el hueco del arranque:

Threshold Crossed: no datapoints were received for 1 period and 1 missing
datapoint was treated as [Breaching].    ALARM
Enter fullscreen mode Exit fullscreen mode

luego, por su cuenta, en cuanto aterrizó el primer heartbeat:

Threshold Crossed: 1 datapoint [1.0 (22/07/26 20:40:00)] was not less than
the threshold (1.0).    OK
Enter fullscreen mode Exit fullscreen mode

Una alarma que dispara ante un outage real y se limpia sola sin ayuda es todo el entregable. La recuperación está padre; la alarma es lo que convierte seis días en quince minutos.

Lo que no ayudó

  • Fijar el AMI. Tentador, ya que el drift del AMI fue el gatillo. Pero solo cierra la única puerta: un reemplazo forzado por un cambio de tipo de instancia o una edición de user-data marcada como reemplazo borra el volumen igual, y heredas una imagen congelada sin parchar. Una vez que la caja se puede reconstruir sola, fijar compra muy poco.
  • Un volumen de datos persistente para el home. Una opción real, y sobrevive al reemplazo, pero vuelve las credenciales un copo de nieve otra vez. Una caja que se reconstruye desde un almacén de secretos es más fácil de razonar que una que depende de que un volumen sobreviva.
  • Mover la caja a su propio repo. Considerado y rechazado. La falla era clonar un repo privado sin credenciales; un repo separado no cambia eso, porque la caja de todos modos tiene que clonar el privado. Stack separado, mismo repo, esa fue la separación útil.

Lecciones

  1. Diseña la alarma antes que la recuperación. Un auto-arreglo del que nunca te enteras es infalsificable. Los seis días no los causó un camino de reparación faltante; los causó una señal faltante.
  2. Alarma sobre la ausencia, no sobre errores. La infraestructura muerta es callada. Cualquier monitor construido sobre hacer match a un string de error es ciego a las fallas totales que más importan.
  3. || true es una decisión de nunca enterarte. Va solo en pasos de verdad opcionales. En cualquier cosa de carga estructural convierte una falla ruidosa en una silenciosa, y un reporte de arranque limpio desde una máquina muerta es lo que hace que un outage dure días en lugar de minutos.
  4. Las credenciales en el volumen de una instancia son un outage esperando un gatillo. Todo lo que pueda ser reemplazado, lo será. Pon las credenciales donde un reemplazo las pueda alcanzar.
  5. Sabe cuáles cambios de config son reemplazos. Una edición de user-data es un detener/actualizar/arrancar sobre el mismo volumen; un cambio de ImageId lo destruye. La misma superficie de diff, un radio de impacto salvajemente distinto: lee el plan antes de desplegar.
  6. La infraestructura de conveniencia no va en el stack de producción. No por costo, no por orden, por radio de impacto. Fue un deploy de producción el que destruyó la caja.
  7. Un watchdog que nunca ha disparado no es un watchdog. Revisa el NEXT del timer. El monitoreo inerte es peor que ninguno, porque se cuenta como cobertura.

Top comments (0)