Containerizar agentes de IA (para que no mueran al desplegar)

Cómo containerizar y operar agentes: config runtime, secretos, timeouts, health checks y rollouts. Incluye ejemplos en Python + JS.
En esta página
  1. Problema (funcionaba… hasta el despliegue)
  2. Por qué esto falla en producción
  3. Diagrama: lo que realmente despliegas
  4. Código real: entrypoint friendly container (Python + JS)
  5. Dockerfile (multi-stage, sin secretos)
  6. Fallo real (con números)
  7. Trade-offs
  8. Cuándo NO containerizar
  9. Checklist (copy-paste)
  10. Config segura por defecto (YAML)
  11. Implementar en OnceOnly (opcional)
  12. FAQ (3–5)
  13. Páginas relacionadas (3–6 links)

Problema (funcionaba… hasta el despliegue)

Agente en notebook: bien.

Agente desplegado: dolor real:

  • no tiene la red que asumías
  • OOMKill porque alguien activó “full trace logging”
  • storms de retries
  • secretos dentro de la imagen (no)

Containerizar no es “teatro de Dockerfile”. Es obligar al agente a comportarse como un servicio de verdad.

Por qué esto falla en producción

Los agentes son workloads incómodos:

  • bursty (picos de tráfico = picos de tokens)
  • I/O (tools) + cuelgues con timeouts
  • colas largas (p95 ok, p99 caos)

Si tu container no enforcea budgets/timeout, producción lo hará. Pero vía 504s, OOMKills y facturas.

Diagrama: lo que realmente despliegas

Código real: entrypoint friendly container (Python + JS)

Aburrido, pero sólido:

  • config por env
  • budgets/timeout enforced
  • health endpoint
PYTHON
import os
import time
from dataclasses import dataclass
from typing import Any, Dict


@dataclass(frozen=True)
class Budgets:
  max_steps: int
  max_tool_calls: int
  max_seconds: int


def load_budgets() -> Budgets:
  return Budgets(
      max_steps=int(os.getenv("AGENT_MAX_STEPS", "25")),
      max_tool_calls=int(os.getenv("AGENT_MAX_TOOL_CALLS", "12")),
      max_seconds=int(os.getenv("AGENT_MAX_SECONDS", "60")),
  )


def run_request(task: str, *, budgets: Budgets) -> Dict[str, Any]:
  t0 = time.time()
  steps = 0
  tool_calls = 0

  while True:
      steps += 1
      if steps > budgets.max_steps:
          return {"output": "", "stop_reason": "max_steps"}
      if tool_calls > budgets.max_tool_calls:
          return {"output": "", "stop_reason": "max_tool_calls"}
      if time.time() - t0 > budgets.max_seconds:
          return {"output": "", "stop_reason": "max_seconds"}

      return {"output": "ok", "stop_reason": "finish"}


def health() -> Dict[str, str]:
  return {"ok": "true"}
JAVASCRIPT
export function loadBudgets() {
return {
  maxSteps: Number(process.env.AGENT_MAX_STEPS ?? 25),
  maxToolCalls: Number(process.env.AGENT_MAX_TOOL_CALLS ?? 12),
  maxSeconds: Number(process.env.AGENT_MAX_SECONDS ?? 60),
};
}

export function runRequest(task, { budgets }) {
const t0 = Date.now();
let steps = 0;
let toolCalls = 0;

while (true) {
  steps += 1;
  if (steps > budgets.maxSteps) return { output: "", stop_reason: "max_steps" };
  if (toolCalls > budgets.maxToolCalls) return { output: "", stop_reason: "max_tool_calls" };
  if ((Date.now() - t0) / 1000 > budgets.maxSeconds) return { output: "", stop_reason: "max_seconds" };

  return { output: "ok", stop_reason: "finish" };
}
}

export function health() {
return { ok: true };
}

Dockerfile (multi-stage, sin secretos)

DOCKERFILE
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm","run","start"]

Fallo real (con números)

Desplegamos con “debug logging” activado por defecto. Guardaba resultados completos de tools en logs.

Impacto:

  • memoria sube → OOMKill
  • retries amplifican carga
  • ~12% failure rate
  • on-call: ~3 horas (logs enormes y aún así inútiles)

Arreglo:

  1. logging muestreado + redacción (/es/observability-monitoring/agent-logging)
  2. budgets enforced en runtime
  3. kill switch para desactivar tools caros durante incidentes

Trade-offs

  • Timeouts agresivos reducen colas y pueden bajar calidad.
  • Más logs ayuda debug y duele en coste/privacy.
  • Un container por agente = simple y caro. Compartir = más barato y más difícil.

Cuándo NO containerizar

Si no lo operas como servicio, no lo sobre-construyas. Pero si un usuario real lo puede disparar, ya es un servicio.

Checklist (copy-paste)

  • [ ] Budgets por env + enforced en runtime
  • [ ] Tool gateway con timeouts/retries/allowlists
  • [ ] Health + readiness checks
  • [ ] Secretos inyectados por plataforma (no baked)
  • [ ] Kill switch config
  • [ ] Logs estructurados + sampling; redacción PII por defecto

Config segura por defecto (YAML)

YAML
runtime:
  env:
    AGENT_MAX_STEPS: 25
    AGENT_MAX_TOOL_CALLS: 12
    AGENT_MAX_SECONDS: 60
tools:
  allowlist: ["search.read", "http.get"]
  timeouts_ms: { default: 8000 }
  retries: { max: 2, backoff_ms: [200, 800] }
observability:
  sampled_tool_results: true
  result_sample_rate: 0.01
rollout:
  canary_percent: 10
  rollback_on_error_rate: 0.05

Implementar en OnceOnly (opcional)

Implementar en OnceOnly
Budgets + defaults del tool gateway que sobreviven al despliegue.
Usar en OnceOnly
# onceonly-python: tool allowlist + governed tool call
import os
from onceonly import OnceOnly

client = OnceOnly(
    api_key=os.environ["ONCEONLY_API_KEY"],
    timeout=5.0,
    max_retries_429=2,
)

agent_id = "billing-agent"

client.gov.upsert_policy({
    "agent_id": agent_id,
    "allowed_tools": ["search.read", "http.get"],
    "max_actions_per_hour": 200,
    "max_spend_usd_per_day": 10.0,
})

res = client.ai.run_tool(
    agent_id=agent_id,
    tool="http.get",
    args={"url": "https://example.com/health"},
    spend_usd=0.001,
)
if not res.allowed:
    raise RuntimeError(res.policy_reason)

FAQ (3–5)

¿Debo meter prompts/modelos en la imagen?
Código sí, secretos no. Prompts versionados en repo. Modelos = config. Secretos = runtime only.
¿Fallo de despliegue más común?
Timeouts + retries que se multiplican. 504s y storms. Retries en un solo lugar y budgets capados.
¿Necesito Kubernetes?
No necesariamente. Necesitas budgets, observabilidad y rollback. Eso existe fuera de K8s.
¿Cómo hago rollback seguro?
Kill switch + imagen/prompt anterior listo. Rollback por error-rate y spikes de gasto.

No sabes si este es tu caso?

Disena tu agente ->
⏱️ 5 min de lecturaActualizado Mar, 2026Dificultad: ★★★
Integrado: control en producciónOnceOnly
Guardrails para agentes con tool-calling
Lleva este patrón a producción con gobernanza:
  • Presupuestos (pasos / topes de gasto)
  • Permisos de herramientas (allowlist / blocklist)
  • Kill switch y parada por incidente
  • Idempotencia y dedupe
  • Audit logs y trazabilidad
Mención integrada: OnceOnly es una capa de control para sistemas de agentes en producción.
Autor

Esta documentación está curada y mantenida por ingenieros que despliegan agentes de IA en producción.

El contenido es asistido por IA, con responsabilidad editorial humana sobre la exactitud, la claridad y la relevancia en producción.

Los patrones y las recomendaciones se basan en post-mortems, modos de fallo e incidentes operativos en sistemas desplegados, incluido durante el desarrollo y la operación de infraestructura de gobernanza para agentes en OnceOnly.