Computación en la nube
Cloud-native y Twelve-Factor App: qué implica cada factor para un contenedor
Intermedio11 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
La metodología twelve-factor sigue siendo la descripción útil más breve de una aplicación que un orquestador puede ejecutar. Esta guía traduce cada factor en decisiones concretas para un servicio Spring Boot en contenedores, muestra el código de configuración y de ciclo de vida que las implementa y, después, repasa las cuatro áreas —secretos, observabilidad, salud y resiliencia— en las que una checklist de 2011 ya no basta.
Lo que aprenderás
- Explicar cada uno de los doce factores: lo que prohíbe además de lo que exige
- Externalizar correctamente la configuración y los secretos de un servicio en contenedores
- Hacer que un proceso sea stateless, desechable y se pueda matar en cualquier momento sin riesgo
- Tratar los logs como un flujo de eventos y dejar de escribir en ficheros dentro de los contenedores
- Identificar dónde se queda corto el modelo twelve-factor y qué añadir junto a él
La metodología twelve-factor la publicaron en 2011 ingenieros de una empresa de platform-as-a-service que habían visto a miles de aplicaciones prosperar o venirse abajo en su infraestructura. Es una lista de las propiedades que necesita una aplicación para que algo que no sea una persona pueda arrancarla, pararla, replicarla y moverla. Quince años después las plataformas han cambiado por completo y la lista apenas ha envejecido, porque la pregunta de fondo sigue siendo la misma: ¿puede un orquestador ejecutar más copias, matar una al azar y moverlas a otra máquina sin que nadie lo note?
Esta guía toma cada factor y se pregunta qué significa en concreto para un servicio en contenedores —los ejemplos suponen una aplicación Spring Boot sobre Kubernetes, pero nada de lo que sigue depende de un framework—. El último tercio se dedica a lo que el documento original no cubre, porque un servicio que cumple los doce factores y nada más no está listo para producción en 2026.
Código base, dependencias, configuración
I. Código base — un único código base bajo control de versiones, muchos despliegues. Una aplicación corresponde a un repositorio. El mismo commit se despliega en staging y en producción; lo que cambia entre ellos es la configuración, nunca el código. Dos aplicaciones que comparten código base son un monolito distribuido en ciernes; una aplicación repartida en dos repositorios no puede publicarse de forma atómica. El código compartido va en una librería con su propio número de versión.
II. Dependencias — decláralas y aíslalas. Cada dependencia figura explícitamente con su versión y no se presupone que haya nada instalado en el host. En la práctica esto significa un fichero de build que fija versiones, un lock file o metadatos de verificación de dependencias en el repositorio junto a él, y una imagen de contenedor que lleva consigo el runtime en lugar de esperar encontrarlo. La parte del aislamiento importa tanto como la de la declaración: un build que solo funciona porque cierta herramienta está instalada en la máquina del desarrollador fallará en el pipeline, y una imagen que hereda latest de una imagen base no es reproducible.
III. Config — guarda la configuración en el entorno. La prueba que propone el documento original sigue siendo la mejor: ¿podrías publicar ahora mismo el repositorio como código abierto sin filtrar ninguna credencial? La configuración es todo lo que varía entre despliegues: URL de base de datos, credenciales, feature flags, la dirección de un servicio upstream, niveles de log. No incluye lo que es igual en todas partes, como las tablas de enrutamiento internas, que son código.
En un servicio Spring Boot esto significa apoyarse en el orden estándar de resolución de propiedades y dejar que la plataforma aporte los valores:
# application.yaml — defaults only, no environment values, no secrets
spring:
application:
name: orders-api
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: none
server:
shutdown: graceful
management:
endpoints:
web:
exposure:
include: health,info,prometheus
Evita la trampa de codificar los entornos en el artefacto. Un perfil llamado prod dentro de la imagen significa que la imagen sabe en qué entorno está, y la propiedad de «un solo build, muchos despliegues» desaparece. Los perfiles son aceptables para variantes de comportamiento (kubernetes, local); los valores específicos de cada entorno van fuera.
Servicios de respaldo, build/release/run, procesos
IV. Servicios de respaldo — trátalos como recursos conectados. Una base de datos, una caché, un broker de mensajes, un relay SMTP y una API de terceros son todos lo mismo: algo accesible en una URL con credenciales. Cambiar un PostgreSQL gestionado por ti por uno gestionado por el proveedor debería ser un cambio de configuración y nada más. El corolario es que tu código no debe tener casos especiales según si una dependencia es local o no, ni incorporar supuestos como una ruta del sistema de ficheros compartida con el host de la base de datos.
V. Build, release, run — separa las etapas estrictamente. El build convierte el código fuente en un artefacto inmutable (una imagen de contenedor identificada por su digest). La release combina ese artefacto con un conjunto concreto de configuración y recibe un identificador propio. El run ejecuta una release. De ahí salen dos reglas: las releases son inmutables —nunca se parchea un contenedor en ejecución, se crea una release nueva— y cada release es direccionable de forma individual, que es lo que convierte el rollback en una operación de una línea. Editar un fichero dentro de un pod en ejecución rompe esto, y además el cambio desaparece en el siguiente reinicio.
VI. Procesos — ejecuta la aplicación como uno o varios procesos sin estado y share-nothing. Todo lo que un proceso guarda en memoria o en disco local debe tratarse como una caché que puede desaparecer en cualquier momento. Los culpables habituales son el estado de sesión HTTP, los contadores de rate limiting en memoria, los ficheros subidos que se escriben en un directorio local y las tareas programadas que suponen que se ejecuta exactamente una instancia.
Las soluciones son bien conocidas: guarda el estado de sesión en Redis o convierte la sesión en un token firmado, usa un almacén compartido para los contadores, escribe las subidas en object storage y coordina el trabajo programado mediante un lock distribuido o un planificador que garantice una única ejecución. Las sticky sessions son un parche, no una solución: impiden un reparto de carga equilibrado y convierten el desalojo de un pod en un cierre de sesión visible para el usuario.
Vinculación de puertos, concurrencia, desechabilidad
VII. Vinculación de puertos — expón los servicios mediante un puerto. La aplicación es autocontenida y escucha en el puerto que se le indica; no se despliega dentro de un servidor de aplicaciones externo que aporte el runtime HTTP. El servidor embebido de Spring Boot hace exactamente esto, y por eso el modelo de JAR ejecutable desplazó al despliegue de WAR para cargas en la nube. Un proceso twelve-factor también puede ser un servicio de respaldo para otro proceso, accesible en un host y un puerto.
VIII. Concurrencia — escala horizontalmente mediante el modelo de procesos. Se escala ejecutando más procesos, no haciendo crecer uno. Los distintos tipos de carga se convierten en tipos de proceso distintos que escalan de forma independiente: un tipo web dimensionado según la tasa de peticiones, un tipo worker dimensionado según la profundidad de la cola, y nunca ambos dentro de un mismo despliegue compartiendo un pool de threads. El escalado vertical tiene un techo y no mejora la disponibilidad; el horizontal mejora ambas cosas, siempre que se cumpla el factor VI.
IX. Desechabilidad — maximiza la robustez con arranques rápidos y apagados ordenados. Es el factor que más se incumple en la práctica, y trata por completo del comportamiento del ciclo de vida.
El arranque rápido importa porque limita la velocidad con la que la plataforma puede reaccionar a la carga o replanificar tras la caída de un nodo. Una inicialización lenta del contexto, el precalentamiento anticipado del pool de conexiones y el escaneo del classpath lo alargan; class-data sharing, la compilación ahead-of-time y recortar la autoconfiguración son las palancas habituales.
El apagado ordenado importa todavía más. Cuando se detiene un contenedor, la plataforma envía SIGTERM, espera un periodo de gracia y después envía SIGKILL. La aplicación debe dejar de aceptar trabajo nuevo, terminar lo que tiene entre manos, cerrar conexiones y salir antes de que venza el periodo de gracia. En Kubernetes hay una sutileza de orden que causa mucha confusión: el borrado del pod y la retirada de sus endpoints ocurren a la vez, así que un pod puede recibir peticiones durante un breve intervalo después de SIGTERM. Un sleep corto en el pre-stop da tiempo a la capa de enrutamiento para ponerse al día.
spec:
terminationGracePeriodSeconds: 45
containers:
- name: orders-api
image: registry.example.com/orders-api@sha256:9f2c...
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8080 }
periodSeconds: 5
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
periodSeconds: 10
failureThreshold: 3
Este factor también exige que los workers resistan una muerte repentina. Si un job extraído de una cola se pierde cuando el proceso muere, hay que configurar el visibility timeout y la semántica de reentrega de la cola, y el handler debe ser idempotente, porque la reentrega va a ocurrir.
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/orders-api.jar app.jar
USER 1000
EXPOSE 8080
# exec form: the JVM becomes PID 1 and receives SIGTERM directly
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]
La forma shell de ENTRYPOINT envuelve el proceso en /bin/sh, que no reenvía señales; la JVM nunca ve SIGTERM y muere de golpe al final del periodo de gracia. Es un error de una sola palabra que desactiva en silencio el apagado ordenado, y es muy frecuente. Hay más detalles sobre el empaquetado de contenedores en fundamentos de Docker.
Paridad, logs, procesos de administración
X. Paridad dev/prod — mantén desarrollo, staging y producción lo más parecidos posible. El original nombra tres brechas: tiempo (cuánto pasa entre escribir el código y ejecutarlo en producción), personas (si quien lo escribe lo despliega) y herramientas (si los servicios de respaldo difieren). Sustituir el motor de producción por una base de datos en memoria es la brecha de herramientas clásica, y oculta todo lo interesante: diferencias de dialecto, comportamiento de los niveles de aislamiento, uso de índices. Los contenedores lo resolvieron en gran medida: ejecuta el motor real en local y usa la misma imagen en todas partes.
XI. Logs — trata los logs como flujos de eventos. La aplicación escribe en la salida estándar, sin buffer, y no sabe nada de ficheros, rotación ni destinos. La plataforma recoge el flujo y lo envía a un sistema de agregación de logs. Escribir en un fichero dentro de un contenedor significa que los logs desaparecen con el contenedor y ocupan la capa de escritura hasta llenarla.
Emite JSON estructurado con identificadores de correlación para que el agregador pueda indexar campos en lugar de parsear texto:
{"timestamp":"2026-09-07T09:14:22.118Z","level":"WARN","service":"orders-api",
"traceId":"4bf92f3577b34da6a3ce929d0e0e4736","spanId":"00f067aa0ba902b7",
"message":"payment authorisation retried","orderId":"A-91245","attempt":2}
XII. Procesos de administración — ejecuta las tareas puntuales como procesos puntuales. Las migraciones de base de datos, las correcciones de datos y la generación de informes se ejecutan contra la misma release, desde el mismo código base y con la misma configuración, como un job en la plataforma y no como una sesión SSH en un host. Una herramienta de migración de esquemas invocada al arrancar o como init container cumple con esto; un documento con sentencias SQL que alguien ejecuta a mano no, porque no está versionado, revisado ni es repetible.
Lo que los doce factores no cubren
Cumplir los doce consigue que una aplicación se orqueste de forma fiable. No la hace operable. Hay cuatro carencias lo bastante importantes como para tratarlas como factores adicionales.
Los secretos no son simple configuración. El factor III mete en el mismo saco un feature flag y una contraseña de base de datos, y eso no ha envejecido bien. Los secretos necesitan rotación sin redespliegue, un registro de auditoría de los accesos, cifrado en reposo con una clave que controles y un radio de impacto limitado a una sola carga de trabajo. El patrón que cumple todo eso: la carga de trabajo se autentica ante la plataforma con una identidad que no ha tenido que almacenar —un rol de IAM o una managed identity— y obtiene los secretos de un almacén gestionado al arrancar, refrescándolos periódicamente. No hay nada sensible en la imagen, el manifiesto, el repositorio ni el bloque de entorno.
La observabilidad no son solo logs. El factor XI cubre una de las tres señales. Un servicio operable también emite métricas (tasa, errores, duración, saturación, más contadores de negocio) y trazas distribuidas cuyo contexto se propaga entre servicios. OpenTelemetry es el estándar neutral respecto a proveedores para las tres y la opción por defecto en la práctica; la configuración en la aplicación se explica en Actuator y observabilidad.
La salud no es un simple booleano. La plataforma necesita al menos tres respuestas distintas: ¿está vivo el proceso y merece la pena conservarlo (liveness)?, ¿puede servir tráfico ahora mismo (readiness)?, ¿ha terminado su lento arranque inicial (startup)? Confundir liveness con readiness es el origen de un modo de fallo bien conocido: una dependencia temporalmente caída hace que todas las réplicas se maten y reinicien en bucle, y una degradación parcial se convierte en una caída total. La liveness solo debe comprobar el propio proceso.
Resiliencia y contratos de API. Timeouts, reintentos con jitter, circuit breakers, backpressure y rate limits son propiedades de arquitectura que la metodología nunca menciona, como tampoco la evolución de API compatible hacia atrás ni las migraciones de esquema que se pueden ejecutar sin riesgo mientras conviven la release antigua y la nueva. El patrón de migración expand-then-contract —añadir una columna nullable, desplegar código que escribe en ambas, hacer el backfill, desplegar código que lee la columna nueva, eliminar la antigua— es lo que hace realmente seguro el rollback instantáneo del factor V.
La definición de cloud-native de la CNCF recoge la ambición más amplia: sistemas débilmente acoplados, resilientes, gestionables y observables, combinados con una automatización que permite a los ingenieros hacer cambios de alto impacto con frecuencia y de forma predecible. Los doce factores son la manera en que un proceso individual se gana su sitio en un sistema así; son el requisito de entrada, no el diseño completo, y por eso conviven con las preocupaciones más amplias de la arquitectura de microservicios.
Una auditoría rápida para un servicio existente
Hazte estas preguntas sobre un servicio que ya operes. Cada «no» es un ticket concreto.
- ¿Podría hacerse público hoy el repositorio sin filtrar ninguna credencial?
- ¿Se identifica el artefacto desplegado con un digest inmutable, y es el mismo digest en staging y en producción?
- Si hay dos réplicas y una muere a mitad de una petición, ¿pierde algún usuario datos o su sesión?
- ¿Termina el proceso limpiamente dentro del periodo de gracia tras
SIGTERM, completando las peticiones en curso? - ¿Escribe el contenedor en disco local algo que no sea una caché genuina?
- ¿Van los logs a la salida estándar, estructurados y con un identificador de traza?
- ¿Comprueban la liveness y la readiness cosas realmente distintas?
- ¿Se puede rotar un secreto sin reconstruir la imagen?
- ¿Se ejecutan las migraciones como un job versionado contra la misma release, y son compatibles con la release anterior?
- ¿Es el arranque lo bastante rápido como para que el autoscaling reaccione antes de que haya pasado el pico de tráfico?
Ideas clave
- Los doce factores describen el contrato entre una aplicación y un orquestador: un único código base, dependencias declaradas, configuración externalizada, servicios de respaldo conectados y una separación estricta entre build, release y run.
- La ausencia de estado y la desechabilidad soportan la mayor parte del peso práctico: nada de estado de sesión local, nada de ficheros locales, arranque rápido y un apagado ordenado que de verdad recibe
SIGTERM. - La configuración va fuera del artefacto para que una única imagen inmutable se ejecute sin cambios en todos los entornos; los perfiles de build con nombre de entorno rompen esa propiedad sin que se note.
- Los logs son un flujo estructurado y sin buffer hacia la salida estándar, que la plataforma enruta; nunca ficheros dentro de un contenedor.
- La metodología es una base, no una definición completa: añade secretos gestionados con identidad de plataforma, observabilidad completa, señales de salud diferenciadas y estrategias explícitas de resiliencia y migración.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Está obsoleta la metodología twelve-factor?
- Los factores en sí han envejecido bien porque describen el contrato entre una aplicación y un orquestador, y ese contrato no ha cambiado. Lo que ha cambiado es el alcance: la metodología apenas dice nada sobre gestión de secretos, tracing distribuido, sondas de salud, reintentos, descubrimiento de servicios o autoscaling, que hoy son lo mínimo exigible. Trátala como una base necesaria, no como una definición completa de cloud-native.
- ¿Cómo encajan los secretos en el factor de configuración si las variables de entorno son arriesgadas?
- Lo que el factor exige de verdad es que la configuración viva fuera del código y varíe según el despliegue, no que tenga que ser una variable de entorno. Las variables de entorno se filtran en volcados de memoria, listados de procesos, procesos hijos y líneas de log, y no se pueden rotar sin reiniciar. Obtén los secretos al arrancar desde un almacén de secretos gestionado usando la propia identidad de plataforma de la carga de trabajo, o deja que la plataforma los proyecte en un fichero montado, y cachéalos con un intervalo de refresco.
- ¿Puede una base de datos o un broker de mensajes ser twelve-factor?
- No, y no le hace falta. La metodología describe procesos de aplicación sin estado; los sistemas con estado incumplen deliberadamente varios factores, en particular la desechabilidad y la concurrencia share-nothing. En una arquitectura twelve-factor, esos sistemas son servicios de respaldo conectados por URL. Ejecutarlos tú mismo sobre un orquestador es posible con operators y volume claims, pero entonces estás operando una base de datos, no escribiendo una aplicación twelve-factor.
- ¿Qué significa la paridad dev/prod cuando producción tiene cuarenta servicios?
- Significa cerrar las brechas que importan: las mismas tecnologías de servicios de respaldo y las mismas versiones mayores, la misma imagen de contenedor, el mismo mecanismo de configuración, y horas en lugar de semanas entre escribir el código y ejecutarlo en producción. No significa que cada desarrollador ejecute todo el parque en local, algo inviable a partir de unos pocos servicios. La respuesta habitual: contenedores para las dependencias inmediatas más un entorno de integración compartido, barato y efímero.