Introducción
Separar correctamente aplicaciones dentro de Docker significa decidir qué responsabilidades deben ejecutarse en contenedores distintos y cuáles pueden permanecer juntas sin introducir complejidad innecesaria. La regla popular de “un proceso por contenedor” puede servir como orientación inicial, pero aplicada literalmente resulta demasiado simple. Lo importante no es contar procesos, sino identificar unidades que tengan un ciclo de vida, una función y unas necesidades operativas suficientemente independientes.
Una aplicación web puede necesitar un servidor HTTP, una base de datos, una caché, procesos en segundo plano y tareas programadas. Agruparlo todo en un único contenedor puede parecer cómodo al principio, pero dificulta actualizar, escalar, diagnosticar y recuperar cada componente. En el extremo contrario, dividir una aplicación pequeña en una docena de contenedores sin una necesidad real crea redes, dependencias, imágenes y procedimientos que aumentan el trabajo administrativo.
El objetivo de una buena separación no es imitar una arquitectura de microservicios. Es conseguir que cada contenedor tenga una responsabilidad comprensible, pueda sustituirse de forma previsible y mantenga sus dependencias explícitas. La división debe reducir acoplamiento operativo sin fragmentar artificialmente la aplicación.
Este artículo explica cómo tomar esa decisión utilizando criterios prácticos: responsabilidad funcional, ciclo de vida, persistencia, actualizaciones, escalado, consumo de recursos, seguridad, disponibilidad y diagnóstico. La separación entre proyectos completos se desarrolla en cómo estructurar un entorno Docker para varios servicios independientes. Aquí la pregunta es más concreta: una vez elegida una aplicación, ¿qué partes deberían convertirse en contenedores separados?
Índice
- Qué significa separar una aplicación en Docker
- Por qué “un proceso por contenedor” no es una regla absoluta
- Separar por responsabilidad funcional
- Separar componentes con distinto ciclo de vida
- Separar cuando las actualizaciones son independientes
- Separar cuando un componente necesita escalar por separado
- Separar cargas con necesidades de recursos diferentes
- Separar aplicación y datos persistentes
- Cuándo separar la base de datos
- Separar workers y procesos en segundo plano
- Separar tareas programadas
- Separar proxy y aplicación
- Mantener dependencias explícitas
- Diseñar la comunicación entre contenedores
- Separar configuración y secretos
- Separar por límites de seguridad
- Facilitar logs y diagnóstico
- Comprobar cada responsabilidad de forma independiente
- Cuándo no merece la pena separar más
- Contenerizar un monolito sin desmontarlo artificialmente
- Ejemplo práctico de división de una aplicación
- Cómo dividir un contenedor demasiado grande
- Errores frecuentes
- Procedimiento para decidir la separación
- Lista de comprobación
- Preguntas frecuentes
- Conclusión
Qué significa separar una aplicación en Docker
Separar una aplicación no significa dividir su código necesariamente. Significa decidir qué componentes se ejecutan como unidades independientes dentro de contenedores distintos.
Por ejemplo, una aplicación puede estar formada por:
- frontend web;
- backend;
- base de datos;
- caché;
- worker de tareas;
- servicio de colas;
- tarea programada;
- proxy inverso.
No todas las aplicaciones necesitan todas estas piezas ni todas deben separarse siempre. La pregunta útil es si cada componente tiene suficiente independencia operativa para justificar un contenedor propio.
La separación es una decisión operativa
Dos procesos pueden formar parte del mismo producto y, aun así, ser mejores candidatos a contenedores distintos si se actualizan, reinician o escalan de forma diferente.
Por qué “un proceso por contenedor” no es una regla absoluta
Docker suele asociarse a la idea de ejecutar un único proceso principal dentro de cada contenedor. Esa recomendación ayuda a mantener responsabilidades claras, pero no debe interpretarse como “un PID y nada más”.
Una aplicación puede lanzar procesos auxiliares internos legítimos. Un servidor web puede crear varios workers. Una base de datos puede utilizar múltiples procesos. Lo importante es que todos formen parte de la misma responsabilidad operativa.
La pregunta correcta
En lugar de preguntar “¿cuántos procesos hay?”, conviene preguntar:
- ¿cumplen la misma función?
- ¿deben arrancar y parar juntos?
- ¿se actualizan juntos?
- ¿tienen el mismo ciclo de vida?
- ¿necesitan escalar juntos?
- ¿comparten los mismos permisos?
Si las respuestas divergen, probablemente existen responsabilidades que conviene separar.
Separar por responsabilidad funcional
El criterio más claro es la responsabilidad. Cada contenedor debería poder describirse con una función concreta.
Ejemplos:
- web: servir peticiones HTTP;
- db: almacenar datos relacionales;
- cache: mantener datos temporales;
- worker: procesar trabajos asíncronos;
- proxy: gestionar entrada HTTP y TLS.
Un contenedor descrito como “web + base de datos + cron + backup + utilidades” ya mezcla responsabilidades con ciclos de vida diferentes.
Una función clara facilita la operación
Si un contenedor falla, su nombre y propósito deberían permitir saber qué capacidad se ha perdido. Esa claridad mejora monitorización, documentación y recuperación.
Separar componentes con distinto ciclo de vida
Dos componentes deberían separarse cuando uno necesita reiniciarse, actualizarse o reemplazarse sin afectar al otro.
Una aplicación web y una base de datos son un ejemplo típico. La web puede actualizarse con frecuencia, mientras la base mantiene datos durante años.
Preguntas sobre ciclo de vida
- ¿se despliegan al mismo tiempo?
- ¿se actualizan con la misma frecuencia?
- ¿puede uno reiniciarse sin el otro?
- ¿uno conserva estado y el otro es reemplazable?
- ¿tienen procedimientos de copia distintos?
Cuantas más diferencias existan, más sentido tiene separarlos.
Separar cuando las actualizaciones son independientes
La separación permite cambiar una imagen sin reconstruir todo el conjunto.
Supongamos que una aplicación utiliza:
web: versión 4.2
postgres: versión 17
redis: versión 7
Cada componente tiene su propio ritmo de actualizaciones. Mantenerlos separados evita empaquetar versiones diferentes dentro de una imagen única y facilita probar cada cambio.
Menos superficie por actualización
Si solo cambia la aplicación web, no debería ser necesario recrear la base de datos ni la caché.
Este enfoque se relaciona con cómo actualizar servicios Docker minimizando interrupciones, donde la independencia de componentes facilita pruebas y reversión.
Separar cuando un componente necesita escalar por separado
Dos componentes que crecen a ritmos distintos son buenos candidatos a contenedores diferentes.
Una aplicación puede necesitar tres instancias web pero una sola base de datos. Si ambos viven juntos, cada réplica web duplicaría innecesariamente la base.
Escalado independiente
La separación permite:
- aumentar workers sin duplicar frontend;
- replicar servicios web;
- mantener una única cola;
- asignar más recursos solo a la base de datos;
- mover una carga pesada a otro host.
La planificación de capacidad se desarrolla en cómo planificar el crecimiento de una infraestructura Docker.
Separar cargas con necesidades de recursos diferentes
Una misma aplicación puede contener componentes con perfiles muy distintos.
Por ejemplo:
- frontend: CPU moderada y poca memoria;
- worker: CPU elevada en determinados momentos;
- base de datos: mucha memoria e I/O;
- caché: memoria alta pero poco disco.
Separarlos permite establecer límites y reservas con más precisión.
Aislar cargas intensivas
Un proceso de conversión de vídeo, generación de informes o análisis puede saturar CPU sin necesidad de afectar al servidor web.
El diagnóstico de estas situaciones se desarrolla en cómo detectar problemas de rendimiento en Docker.
Separar aplicación y datos persistentes
Un principio fundamental de Docker es tratar los contenedores de aplicación como elementos reemplazables y mantener el estado importante fuera de su capa efímera.
Eso no significa que los datos deban vivir necesariamente en otro contenedor, pero sí que deben tener un ciclo de vida independiente.
Datos que suelen necesitar persistencia
- bases de datos;
- archivos subidos;
- repositorios;
- documentos;
- configuración mutable;
- colas persistentes cuando sean necesarias.
La gestión de este almacenamiento se desarrolla en cómo gestionar correctamente los volúmenes Docker.
Cuándo separar la base de datos
La base de datos suele ser uno de los primeros componentes que merece un contenedor propio.
Razones principales
- tiene persistencia;
- necesita copias específicas;
- se actualiza de forma diferente;
- consume recursos distintos;
- puede necesitar mantenimiento propio;
- su pérdida tiene impacto diferente al de la aplicación.
Separar no significa externalizar
La base puede seguir en el mismo host y dentro del mismo proyecto Compose. Tener un contenedor propio ya permite separar su ciclo de vida del servidor web.
Evitar la base embebida por comodidad
Empaquetar una base completa dentro de la misma imagen que la aplicación hace más difícil proteger datos, cambiar versiones y entender qué debe persistir.
Separar workers y procesos en segundo plano
Muchas aplicaciones ejecutan tareas que no deberían bloquear una petición web: envío de correos, generación de documentos, importaciones, procesamiento de archivos o cálculos.
Un worker separado puede consumir la misma imagen de aplicación pero ejecutar un comando diferente.
services:
web:
image: miapp:1.0
command: ./iniciar-web
worker:
image: miapp:1.0
command: ./iniciar-worker
Ventajas
- escalar workers sin escalar web;
- reiniciar tareas sin interrumpir usuarios;
- asignar recursos distintos;
- separar logs;
- detectar fallos específicos.
Separar tareas programadas
Las tareas periódicas merecen analizarse por separado porque su ciclo de vida puede ser distinto del servicio principal.
Algunos ejemplos:
- limpieza de temporales;
- generación de informes;
- sincronización;
- importaciones;
- procesos nocturnos.
Evitar cron oculto dentro de un contenedor complejo
Si una tarea importante depende de un daemon cron que nadie recuerda que existe dentro del contenedor web, su monitorización y recuperación serán más difíciles.
Cuando la tarea tenga importancia operativa puede ser preferible representarla de forma explícita.
Separar proxy y aplicación
El proxy inverso tiene una responsabilidad distinta: recibir tráfico, gestionar TLS, aplicar reglas de entrada y reenviar peticiones.
Separarlo de la aplicación permite:
- publicar varias aplicaciones con un mismo proxy;
- renovar certificados sin reconstruir la app;
- aplicar configuración de entrada común;
- mantener las aplicaciones sin puertos públicos directos.
La arquitectura de conectividad entre estas piezas se desarrolla en cómo diseñar redes Docker fáciles de mantener.
Mantener dependencias explícitas
Separar contenedores solo aporta claridad si también se documentan sus relaciones.
Una aplicación puede depender de:
- base de datos;
- caché;
- cola;
- servicio de archivos;
- API externa;
- servicio SMTP;
- identidad.
Separar no elimina el acoplamiento
Dos contenedores pueden estar físicamente separados y seguir fuertemente acoplados si uno no funciona sin el otro. La ventaja está en hacer esa dependencia visible y administrable.
No usar depends_on como sustituto de salud
El orden de creación no garantiza que una dependencia esté lista para aceptar trabajo. Las aplicaciones deberían gestionar reintentos y, cuando sea útil, healthchecks.
Diseñar la comunicación entre contenedores
Una vez separados los componentes, necesitan comunicarse mediante redes claras.
Un ejemplo sencillo:
web ---- red-app ---- db
|
+------ red-proxy ---- proxy
La base de datos no necesita conectarse a la red del proxy. Cada componente accede solo a las redes que necesita.
Usar nombres de servicio
Dentro de una red Docker es preferible utilizar nombres como db o cache en lugar de IP internas fijas.
Separar configuración y secretos
La separación de contenedores no debe terminar mezclando todas las credenciales en un único archivo sin control.
Cada servicio debería recibir únicamente la configuración que necesita.
Ejemplo conceptual
- web necesita credenciales de base de datos;
- db necesita su contraseña interna;
- worker puede necesitar también la cola;
- proxy no necesita conocer la contraseña de la base.
La configuración mínima reduce exposición y hace más comprensible el despliegue.
Separar por límites de seguridad
Un componente que requiere permisos especiales puede justificar un contenedor independiente.
Ejemplos:
- acceso a un dispositivo;
- montaje de un directorio sensible;
- acceso a una API privilegiada;
- capabilities adicionales;
- credenciales de administración.
Si toda la aplicación se ejecuta dentro del mismo contenedor privilegiado, cada proceso hereda una superficie de riesgo mayor.
Aplicar privilegios donde sean necesarios
Separar permite mantener la mayor parte de la aplicación con permisos reducidos y aislar la excepción.
Facilitar logs y diagnóstico
Un contenedor con muchas responsabilidades puede mezclar logs de servidor web, base de datos, cron, worker y utilidades.
Al separar componentes, cada flujo de logs tiene un contexto más claro.
Ventajas durante una incidencia
- identificar qué componente falla;
- filtrar eventos relevantes;
- comparar consumo por servicio;
- reiniciar solo la pieza afectada;
- crear alertas específicas.
La supervisión básica de estas señales se desarrolla en cómo monitorizar contenedores Docker de forma sencilla.
Comprobar cada responsabilidad de forma independiente
La separación facilita definir qué significa “estar sano” para cada componente.
Por ejemplo:
- web: responde un endpoint;
- db: acepta conexiones;
- worker: puede procesar trabajos;
- cache: responde al protocolo esperado.
Si todo vive en un único contenedor, una comprobación general puede indicar que está activo aunque una parte interna haya fallado.
Cuándo no merece la pena separar más
Separar también tiene un coste. Cada contenedor adicional introduce:
- una definición de servicio;
- otra imagen o comando;
- dependencias de red;
- logs propios;
- monitorización;
- actualizaciones;
- documentación.
Mantener juntas responsabilidades inseparables
Si varios procesos forman una única unidad técnica, siempre se despliegan juntos y no necesitan escalar ni mantenerse por separado, dividirlos puede no aportar valor.
No fragmentar por estética
Una arquitectura no mejora por tener más contenedores. Mejora cuando las fronteras reducen complejidad real.
Contenerizar un monolito sin desmontarlo artificialmente
Una aplicación monolítica puede ejecutarse perfectamente en Docker sin transformarse en microservicios.
Un patrón razonable puede ser:
app
db
cache
La aplicación sigue siendo un monolito, pero los componentes de infraestructura con ciclos de vida distintos están separados.
Contenerizar no obliga a rediseñar
Docker puede aportar reproducibilidad, aislamiento y facilidad de despliegue sin modificar la arquitectura interna del software.
La refactorización hacia servicios independientes debe responder a necesidades de desarrollo u operación, no al simple hecho de utilizar contenedores.
Ejemplo práctico de división de una aplicación
Supongamos una aplicación que originalmente ejecuta todo dentro de una única máquina:
Aplicación
├── servidor web
├── base de datos
├── worker
├── caché
└── tarea nocturna
Una separación razonable podría ser:
services:
web:
image: miapp:2.0
worker:
image: miapp:2.0
db:
image: postgres:17
cache:
image: redis:7
La tarea nocturna puede ejecutarse mediante un servicio específico o un mecanismo externo, dependiendo de su naturaleza.
Qué se consigue
- web y worker pueden escalar por separado;
- la base conserva su almacenamiento;
- Redis puede actualizarse independientemente;
- cada servicio tiene logs propios;
- pueden establecerse límites de recursos diferentes.
Qué no se ha hecho
No se ha convertido el código en microservicios. Web y worker pueden seguir usando exactamente la misma imagen y el mismo código base.
Cómo dividir un contenedor demasiado grande
Si ya existe un contenedor que ejecuta muchas responsabilidades, conviene separarlo de forma gradual.
Paso 1: inventariar procesos
Identifica qué se ejecuta, por qué y qué necesita.
Paso 2: localizar estado persistente
Antes de mover nada, determina qué datos deben conservarse.
Paso 3: elegir la primera separación
Empieza por el componente con ciclo de vida más claramente independiente, como la base de datos o un worker.
Paso 4: definir comunicación
Sustituye dependencias locales por nombres de servicio y redes Docker.
Paso 5: probar fuera de producción
Comprueba arranque, persistencia, rendimiento y recuperación.
Paso 6: separar una pieza cada vez
Evita transformar toda la arquitectura de una sola vez. La migración progresiva facilita detectar errores y volver atrás.
Errores frecuentes
Aplicar literalmente “un proceso por contenedor”
La responsabilidad operativa es un criterio más útil que el número exacto de procesos.
Crear un contenedor para cada función pequeña
Puede generar más complejidad que beneficio.
Meter base de datos y aplicación en la misma imagen
Mezcla persistencia, actualizaciones y recuperación.
Separar sin diseñar redes
Los componentes terminan conectados de forma improvisada.
Duplicar datos al escalar
Un componente persistente no debe replicarse accidentalmente junto a cada instancia web.
Usar contenedores separados pero compartir todos los secretos
La separación técnica pierde parte de su valor si los privilegios siguen siendo globales.
Crear microservicios sin necesidad
Docker no exige transformar un monolito.
No considerar el diagnóstico
Una buena frontera debe facilitar saber qué pieza está fallando.
No considerar la recuperación
Los componentes persistentes necesitan copias y procedimientos distintos.
Separar todo de golpe
Las migraciones graduales permiten validar cada frontera.
Procedimiento para decidir la separación
- Lista las responsabilidades. Identifica web, datos, workers, caché, proxy y tareas.
- Compara ciclos de vida. Pregunta qué puede actualizarse o reiniciarse por separado.
- Identifica persistencia. Los datos necesitan un ciclo distinto de la aplicación reemplazable.
- Compara necesidades de recursos. CPU, memoria e I/O pueden justificar fronteras.
- Revisa escalado. Separa aquello que pueda necesitar multiplicarse independientemente.
- Revisa permisos. Aísla componentes que necesiten privilegios especiales.
- Diseña dependencias. Define nombres, redes y servicios necesarios.
- Define healthchecks y logs. Cada responsabilidad debería poder observarse.
- Calcula el coste de separar. No añadas contenedores sin un beneficio operativo.
- Prueba la arquitectura. Arranque, parada, actualización, fallo y recuperación.
- Documenta la decisión. Explica por qué cada componente está separado o permanece junto.
Lista de comprobación
| Área | Comprobación |
|---|---|
| Responsabilidad | Cada contenedor tiene una función explicable |
| Ciclo de vida | Los componentes separados pueden cambiar independientemente |
| Actualización | No se recrean piezas innecesarias para actualizar una sola |
| Escalado | Las cargas que crecen por separado tienen fronteras claras |
| Recursos | Pueden aplicarse límites adecuados por componente |
| Persistencia | Los datos no dependen de la capa efímera de aplicación |
| Base de datos | Tiene almacenamiento y ciclo de vida propios |
| Workers | Las tareas asíncronas pueden gestionarse independientemente cuando aporta valor |
| Red | Las dependencias entre contenedores son explícitas |
| Secretos | Cada servicio recibe solo lo necesario |
| Seguridad | Los privilegios especiales están aislados |
| Logs | Los mensajes pueden atribuirse a una responsabilidad |
| Healthchecks | La salud puede comprobarse por componente |
| Complejidad | Cada separación aporta un beneficio real |
| Monolito | No se ha fragmentado código solo por usar Docker |
| Documentación | La razón de cada frontera queda registrada |
Preguntas frecuentes
¿Docker exige un único proceso por contenedor?
No como regla literal. Lo importante es que el contenedor represente una responsabilidad operativa clara. Un proceso principal puede crear varios procesos auxiliares legítimos.
¿Debo poner la base de datos en otro contenedor?
En muchos casos sí, porque tiene persistencia, copias, actualizaciones y necesidades de recursos diferentes. Puede seguir perteneciendo al mismo proyecto Docker Compose.
¿Separar contenedores significa usar microservicios?
No. Una aplicación monolítica puede utilizar un contenedor para la aplicación, otro para la base de datos y otro para la caché sin convertirse en una arquitectura de microservicios.
¿Web y worker pueden usar la misma imagen?
Sí. Pueden compartir código e imagen pero ejecutar comandos diferentes. Esto permite separar ciclos de ejecución y escalado sin duplicar el código base.
¿Cuándo merece la pena separar un worker?
Cuando realiza tareas asíncronas, consume recursos de forma distinta, necesita escalar independientemente o debe reiniciarse sin afectar al servicio web.
¿Es malo tener varios servicios en un mismo contenedor?
No automáticamente. Es problemático cuando mezclan responsabilidades con ciclos de vida, persistencia, permisos o necesidades de escalado diferentes.
¿Cuantos contenedores debería tener una aplicación?
No existe un número correcto. Debe tener los necesarios para representar responsabilidades útiles sin introducir fragmentación artificial.
¿Separar más mejora siempre la seguridad?
No. Puede ayudar a reducir privilegios y accesos, pero demasiada complejidad genera errores de configuración. La separación debe ser comprensible y acompañarse de permisos, redes y secretos adecuados.
¿Cómo sé si un contenedor es demasiado grande?
Una señal clara es que contiene componentes que se actualizan, escalan, reinician o recuperan de forma diferente y que no pueden gestionarse independientemente sin afectar a todo el conjunto.
¿Conviene separar tareas cron?
Cuando son importantes, pesadas o necesitan supervisión propia, sí puede aportar valor. Una tarea pequeña estrechamente ligada a la aplicación puede mantenerse de otra forma si sigue siendo visible y controlable.
¿Puedo dividir una aplicación existente poco a poco?
Sí, y suele ser preferible. Separar primero las responsabilidades más claras permite probar redes, persistencia y dependencias antes de continuar.
Conclusión
Separar correctamente aplicaciones dentro de Docker no consiste en crear un contenedor por cada proceso ni en fragmentar el software hasta convertirlo en microservicios. Consiste en identificar responsabilidades con ciclos de vida suficientemente distintos para administrarlas de forma independiente.
Base de datos, caché, workers, proxy y aplicación web suelen ofrecer ejemplos claros porque difieren en persistencia, escalado, actualización, consumo de recursos o seguridad. Separarlos puede mejorar diagnóstico, recuperación y mantenimiento.
La frontera adecuada es aquella que reduce acoplamiento operativo más de lo que aumenta la complejidad. Si una separación permite actualizar una pieza sin tocar las demás, escalar solo donde hace falta, aplicar permisos específicos o localizar un fallo con más rapidez, probablemente aporta valor.
En cambio, dividir componentes inseparables solo por seguir una regla teórica genera más redes, dependencias, logs y procedimientos sin mejorar realmente el sistema.
Una buena arquitectura Docker mantiene responsabilidades comprensibles, datos persistentes fuera de la capa efímera, dependencias explícitas y contenedores suficientemente independientes para ser reemplazables. Esa combinación permite que la plataforma evolucione sin convertir cada cambio en una reconstrucción general.
