Docker Swarm: clúster de contenedores, comandos avanzados y buenas prácticas

Clúster de Docker Swarm con tres nodos managers y las siete tareas de la aplicación repartidas entre ellos sobre la red overlay

Docker Swarm es el modo de clúster que viene integrado en el propio motor de Docker: une varios servidores en uno solo, reparte los contenedores entre ellos y los vuelve a levantar en otro nodo si uno cae. Esta guía muestra cómo montar un clúster de tres nodos, desplegar una aplicación real con réplicas, actualizarla sin cortar el servicio y mantener el clúster. Cierra con los comandos avanzados de Docker que se usan en el diagnóstico diario y con las buenas prácticas que conviene aplicar antes de llevar contenedores a producción.

Si su duda es si necesita un clúster, empiece por la comparación con Kubernetes. En cambio, si ya lo tiene decidido, vaya al despliegue del stack y a la tabla de comandos avanzados.

Dónde encaja esta guía

Es la tercera y última guía construida sobre la misma aplicación, el servicio interno inventario. En la primera se midieron sus imágenes: 238,1 MB comprimidos. Después, la segunda la describió en un archivo y calculó su presupuesto: 2 CPU y 1.536 MiB. Aquí ese archivo se despliega en tres servidores, con réplicas, y las cifras cambian de escala.

  1. Docker — qué es, instalación, primer despliegue y comandos esenciales.
  2. Docker Compose — la aplicación completa en un archivo, con redes, volúmenes y secretos.
  3. Docker Swarm — está leyéndola: el clúster de tres nodos, los comandos avanzados y las buenas prácticas.

Qué es Docker Swarm y cuándo tiene sentido

Compose gobierna un servidor, así que cuando ese servidor se apaga, la aplicación se apaga con él. Swarm da el paso siguiente: varios servidores con Docker se agrupan en un clúster, y en lugar de contenedores sueltos se declaran servicios con un número de réplicas. El clúster se encarga de que ese número se cumpla siempre, aunque falle un nodo o se reinicie un contenedor.

No hay que instalar nada aparte: el modo swarm forma parte de Docker Engine desde hace años y se activa con una orden. Además, entiende el mismo formato de archivo que Compose, con una sección deploy para las réplicas y las actualizaciones. Por eso es el camino natural para quien ya trabaja con Compose y necesita tolerancia a fallos sin montar una plataforma nueva.

Sobre su futuro, un dato útil para decidir: Mirantis, que mantiene el proyecto, anunció en julio de 2025 soporte para Swarm hasta al menos 2030 dentro de su plataforma empresarial.

Docker Swarm o Kubernetes

En realidad, es la pregunta que aparece en cuanto se habla de clústeres. La respuesta honesta depende del tamaño del problema y, sobre todo, del equipo que lo va a operar.

CriterioDocker SwarmKubernetes
InstalaciónViene en el motor: docker swarm initUna distribución aparte o un servicio gestionado en la nube
FormatoEl mismo archivo de Compose, con deployManifiestos propios: Deployment, Service, Ingress y otros
Curva de aprendizajeDías, si ya se usa ComposeSemanas o meses
Almacenamiento con estadoLimitado: volúmenes locales o complementosMaduro, con volúmenes persistentes y controladores de almacenamiento
EcosistemaReducidoMuy amplio
Candidato naturalPocas aplicaciones y un equipo pequeñoMuchas aplicaciones y un equipo dedicado a la plataforma

Para una empresa mediana con un puñado de servicios internos, Swarm suele cubrir lo que hace falta con una fracción del trabajo de operación. Kubernetes empieza a compensar cuando hay decenas de aplicaciones y un equipo que se dedica solo a la plataforma.

Arquitectura de un clúster de Docker

Para empezar, un clúster tiene dos papeles. Los managers guardan el estado del clúster y deciden dónde corre cada tarea; los workers solo ejecutan contenedores. Además, un manager también puede ejecutar contenedores, y en clústeres pequeños es lo habitual.

Los managers se ponen de acuerdo con el algoritmo Raft, que exige mayoría para tomar cualquier decisión. De ahí sale la regla más importante del diseño, recogida en la guía de administración: el número de managers debe ser impar.

ManagersMayoría necesariaCaídas que tolera
110
321
532
743

Por ejemplo, cuatro managers toleran lo mismo que tres, una caída, así que el cuarto no aporta nada. Por otro lado, si se pierde la mayoría, los contenedores siguen corriendo, pero el clúster deja de aceptar cambios: no se puede desplegar, escalar ni recuperar tareas caídas hasta restablecerla.

Los puertos que hay que abrir entre nodos

PuertoProtocoloPara qué
2377TCPComunicación con los managers y entre ellos
7946TCP y UDPDescubrimiento de nodos de las redes overlay
4789UDPTráfico de las redes overlay (VXLAN)
Protocolo IP 50ESPSolo si las redes overlay van cifradas

Sin embargo, el puerto 4789 merece un aviso explícito. VXLAN no autentica, así que la documentación pide abrirlo solo en una red de confianza y nunca en el firewall perimetral. Por eso lo sensato es que los nodos del clúster compartan una VLAN propia, como las que se diseñan en la guía del firewall de Proxmox.

Crear el clúster de Docker Swarm paso a paso

El clúster de la serie son tres máquinas virtuales con Docker Engine instalado como en la primera guía: docker-01, docker-02 y docker-03, cada una con 2 vCPU y 4 GiB. Es decir, 6 vCPU y 12 GiB en total. Pueden vivir en un clúster de virtualización como el de la guía de Proxmox, siempre que cada una quede en un servidor físico distinto: tres nodos de Swarm sobre el mismo anfitrión caen juntos.

# En docker-01: crear el clúster
docker swarm init --advertise-addr 192.168.20.11

# Obtener la orden de unión como manager
docker swarm join-token manager

# En docker-02 y docker-03: pegar esa orden
docker swarm join --token SWMTKN-1-xxxx 192.168.20.11:2377

# De vuelta en cualquier manager
docker node ls
docker node update --label-add datos=si docker-01

Los tres nodos entran como managers, así que el clúster tolera la caída de uno y cualquiera de ellos puede administrarlo. Por otro lado, la etiqueta datos=si marca el nodo donde vivirá la base de datos; su motivo se explica más abajo. Por último, el token de unión es una credencial: quien lo tenga puede meter un nodo en el clúster. Si se filtra, se rota con docker swarm join-token --rotate manager.

Del archivo de Compose al stack de Docker Swarm

En el clúster, una aplicación de varios servicios se llama stack, y se despliega con el mismo formato de archivo de la segunda guía. Sin embargo, cambian cuatro cosas, y conviene conocerlas antes de copiar un archivo que funcionaba en un solo servidor.

  • No se construye. La clave build se ignora, porque cada nodo descarga la imagen por su cuenta. La imagen de la aplicación tiene que estar en un registro: Docker Hub privado, Azure Container Registry o uno propio.
  • No hay orden de arranque. depends_on no tiene efecto, así que cada servicio debe tolerar que los demás aún no estén listos y reintentar.
  • Los archivos se reparten. Un archivo del disco local no existe en los otros nodos. La configuración de nginx pasa a ser un config del clúster.
  • El reinicio lo gobierna deploy. La política de reinicio y las actualizaciones se declaran en restart_policy y update_config.

A continuación, el archivo del stack, validado con docker stack config. Como se ve, los cuatro servicios son los mismos, con los mismos límites de recursos.

# inventario-stack.yaml
services:
  proxy:
    image: nginx:1.30-alpine
    ports:
      - "80:80"
    configs:
      - source: nginx_conf
        target: /etc/nginx/conf.d/default.conf
    networks: [frontend]
    deploy:
      replicas: 2
      resources:
        limits: { cpus: "0.25", memory: 128M }
      update_config: { parallelism: 1, delay: 10s }

  app:
    image: registro.ejemplo.local/inventario:${APP_VERSION:-1.0}
    environment:
      DB_HOST: db
      DB_NAME: inventario
      DB_USER: inventario
      DB_PASSWORD_FILE: /run/secrets/db_password
      REDIS_URL: redis://cache:6379/0
    secrets: [db_password]
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/salud')"]
      interval: 15s
      timeout: 3s
      retries: 3
      start_period: 10s
    networks: [frontend, backend]
    deploy:
      replicas: 3
      resources:
        limits: { cpus: "0.50", memory: 256M }
        reservations: { cpus: "0.25", memory: 128M }
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
        monitor: 30s
        failure_action: rollback
      rollback_config:
        parallelism: 1
      restart_policy:
        condition: on-failure
        max_attempts: 3

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: inventario
      POSTGRES_USER: inventario
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets: [db_password]
    volumes:
      - datos-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U inventario -d inventario"]
      interval: 10s
      timeout: 3s
      retries: 5
    networks: [backend]
    deploy:
      placement:
        constraints: [node.labels.datos == si]
      resources:
        limits: { cpus: "1.00", memory: 1024M }

  cache:
    image: redis:8-alpine
    command: ["redis-server", "--maxmemory", "96mb", "--maxmemory-policy", "allkeys-lru"]
    networks: [backend]
    deploy:
      resources:
        limits: { cpus: "0.25", memory: 128M }

networks:
  frontend:
    driver: overlay
  backend:
    driver: overlay
    internal: true

volumes:
  datos-db:

configs:
  nginx_conf:
    file: ./nginx.conf

secrets:
  db_password:
    file: ./secretos/db_password.txt

Publicar la imagen y desplegar el stack

# Subir la imagen de la aplicación al registro
docker tag inventario:1.0 registro.ejemplo.local/inventario:1.0
docker push registro.ejemplo.local/inventario:1.0

# Desplegar, pasando las credenciales del registro a los nodos
export APP_VERSION=1.0
docker stack deploy -c inventario-stack.yaml --with-registry-auth inventario

# Comprobar
docker stack services inventario
docker stack ps inventario

Ahora bien, ojo con la variable. A diferencia de Compose, docker stack deploy no lee el archivo .env: si no se exporta en la consola, la sustitución usa el valor por defecto sin avisar. Es un comportamiento conocido desde hace años, recogido en el registro de incidencias del proyecto, y la causa de muchos despliegues que «no cogieron la versión nueva».

Además, los secretos cambian de naturaleza en el clúster. Se guardan cifrados en el registro Raft de los managers, se entregan solo a los nodos que ejecutan un servicio que los declara y se montan en memoria, en /run/secrets/, sin tocar nunca el disco del nodo.

Lo que ocupa el stack en el clúster de Docker Swarm

Con las réplicas, el presupuesto de la segunda guía se multiplica. Es decir, son los mismos límites por contenedor, pero ahora hay siete contenedores en lugar de cuatro.

ServicioRéplicasCPU por réplicaMemoria por réplicaCPU totalMemoria total
proxy20,25128 MiB0,50256 MiB
app30,50256 MiB1,50768 MiB
db11,001.024 MiB1,001.024 MiB
cache10,25128 MiB0,25128 MiB
Total7 tareas3,25 de 62.176 de 12.288 MiB

En consecuencia, los límites comprometen el 54 % de la CPU del clúster y el 18 % de su memoria. Así se ve un reparto posible de las siete tareas, con la base de datos fijada en docker-01 por su etiqueta:

NodoTareasCPUMemoriaDescarga de imágenes
docker-01db, proxy, app1,751.408 MiB117,2 + 26,1 + 55,8 = 199,1 MB
docker-02proxy, app0,75384 MiB26,1 + 55,8 = 81,9 MB
docker-03app, cache0,75384 MiB55,8 + 39,0 = 94,8 MB
Clúster73,252.176 MiB375,8 MB

Además, la última columna recupera un dato de la primera guía. Cada nodo descarga las imágenes de lo que ejecuta, así que el clúster baja 375,8 MB aunque las imágenes distintas sumen 238,1 MB. Por otra parte, la imagen de la aplicación está en los tres nodos. Si se hubiera construido sobre python:3.14 en lugar de la variante slim, cada nodo bajaría 368,6 MB más: 1.105,8 MB de más en cada despliegue, por el mismo programa.

Qué pasa si cae un nodo

Si cae docker-02 o docker-03, sus tareas se recrean en los nodos que quedan, en segundos y sin intervención. Caben de sobra, ya que son como mucho 384 MiB. Además, los dos managers restantes siguen formando mayoría, así que el clúster sigue aceptando órdenes.

En cambio, si cae docker-01, el proxy y la aplicación se recolocan igual, pero la base de datos no. Esto ocurre porque su volumen es local a ese nodo, y la restricción de ubicación impide levantarla en otro lugar con un disco vacío, que sería peor. Esta es, en definitiva, la lección más importante de Swarm: las réplicas protegen a los servicios sin estado, no a los datos. Para la base de datos hay tres salidas razonables:

  • Sacarla del clúster a una máquina virtual protegida por la alta disponibilidad de la plataforma de virtualización. Es la opción más sencilla y la que suele convenir a una pyme.
  • Almacenamiento compartido mediante un complemento de volúmenes, para que la base de datos pueda arrancar en cualquier nodo.
  • Aceptar la parada y tener copias probadas, con un tiempo de recuperación conocido y asumido por la dirección.

Actualizaciones sin corte en Docker Swarm

En este punto aparece la ventaja que Compose no puede dar. Con tres réplicas y order: start-first, el clúster arranca una réplica nueva, espera a que su prueba de salud responda y solo entonces retira una antigua. Después espera diez segundos y repite con la siguiente. Por lo tanto, en ningún momento hay menos de tres réplicas sanas atendiendo.

# Nueva versión: subir la imagen y actualizar solo ese servicio
docker push registro.ejemplo.local/inventario:1.1
docker service update --image registro.ejemplo.local/inventario:1.1 inventario_app

# Seguir el avance, réplica a réplica
docker service ps inventario_app

# Volver a la versión anterior a mano, si hiciera falta
docker service rollback inventario_app

Con failure_action: rollback y monitor: 30s, la vuelta atrás ocurre sola: si una réplica nueva falla durante sus primeros 30 segundos, el clúster detiene la actualización y restaura la versión anterior en todas. Sin monitor, que vale cero por defecto, un fallo que aparece a los diez segundos de arrancar no se detectaría como fallo de la actualización.

Mantenimiento de un clúster de Docker Swarm

En la práctica, tres operaciones cubren casi toda la vida de un clúster: vaciar un nodo para trabajar en él, repartir la carga cuando vuelve y respaldar el estado de los managers.

# Vaciar un nodo antes de parchearlo o reiniciarlo
docker node update --availability drain docker-02
# ... mantenimiento ...
docker node update --availability active docker-02

# Swarm no reequilibra solo al volver: forzar un nuevo reparto
docker service update --force inventario_app

# Respaldo del estado del clúster, en un manager
sudo systemctl stop docker
sudo tar czf swarm-$(hostname)-$(date +%F).tar.gz -C /var/lib/docker swarm
sudo systemctl start docker

Por cierto, el segundo comando responde a una duda frecuente. Cuando un nodo vuelve tras un mantenimiento, Swarm no le devuelve tareas por sí mismo: las deja donde las recolocó. Por eso, forzar la actualización del servicio las redistribuye. Por otro lado, la copia de /var/lib/docker/swarm contiene el estado y las claves de cifrado del clúster; sin ellas no se puede restaurar. Conviene hacerla en un manager y tratarla como un secreto.

Por último, si alguna vez se pierde la mayoría de managers sin remedio, la salida es docker swarm init --force-new-cluster en el manager que sobrevive. Esa orden lo convierte en un clúster de un solo manager con el estado que tenía, y después se vuelven a unir los demás nodos.

Comandos avanzados de Docker

A partir de aquí, estos comandos sirven igual en un servidor con Compose que en el clúster. Son los que separan buscar a ojo en una pantalla llena de texto de obtener exactamente el dato que hace falta.

Filtrar y dar formato a la salida

Para empezar, casi todos los comandos de listado aceptan --filter para elegir filas y --format para elegir columnas con plantillas de Go. Con ellos, la salida pasa de ser algo que se lee a algo que se puede procesar.

# Tabla a medida
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

# Solo los contenedores que no están sanos
docker ps --filter health=unhealthy

# Un dato concreto de un objeto
docker inspect -f '{{.State.Health.Status}}' inventario-app-1
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' inventario-app-1

# Todo el objeto en JSON, para procesarlo con jq
docker inspect inventario_app | jq '.[0].Spec.TaskTemplate.Resources'

Diagnóstico: registros, eventos y consumo

# Solo lo reciente, sin leer un registro de semanas
docker logs --since 30m --tail 200 inventario-app-1
docker service logs -f --since 10m inventario_app

# Qué contenedores murieron en la última hora
docker events --since 1h --filter type=container --filter event=die

# Consumo en una sola lectura, sin quedarse en pantalla
docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}'

# Por qué una réplica no arranca: el error completo
docker service ps --no-trunc inventario_app

De hecho, el último comando resuelve buena parte de las consultas de un clúster. Cuando una tarea no arranca, la columna de error explica el motivo exacto: imagen inexistente, credenciales del registro, restricción de ubicación imposible o falta de memoria reservable.

Limpieza con criterio

# Qué ocupa cada cosa, con detalle
docker system df -v

# Imágenes sin usar de más de 30 días
docker image prune -a --filter "until=720h"

# Caché de construcción de más de una semana
docker builder prune --filter "until=168h"

El filtro until es la diferencia entre limpiar y arrasar. Además, conserva lo reciente, que es justo lo que haría falta para volver a la versión anterior.

Administrar varios servidores desde un equipo

Por su parte, los contextos guardan la conexión a otros motores. Con ellos, el mismo cliente administra el portátil, un servidor remoto por SSH o el clúster, sin abrir sesiones.

docker context create docker-01 --docker "host=ssh://admin@docker-01"
docker context create docker-02 --docker "host=ssh://admin@docker-02"
docker context create docker-03 --docker "host=ssh://admin@docker-03"

docker --context docker-01 node ls
docker context use docker-01

Imágenes para Intel y ARM a la vez

Si el clúster mezcla procesadores, la imagen propia tiene que existir para ambas arquitecturas, igual que las oficiales. buildx construye las dos variantes y las publica bajo la misma etiqueta. Después, cada nodo descarga la suya, como se explica en la guía de AMD64 vs ARM64.

docker buildx build --platform linux/amd64,linux/arm64 \
  -t registro.ejemplo.local/inventario:1.1 --push .

Del clúster a un informe

Sin embargo, docker stats solo ve los contenedores del motor al que se conecta. Con los contextos, una sola línea recorre los tres nodos y deja el consumo en un CSV, con el nodo como primera columna.

for n in docker-01 docker-02 docker-03; do
  docker --context $n stats --no-stream \
    --format "$n;{{.Name}};{{.CPUPerc}};{{.MemUsage}};{{.MemPerc}}"
done > consumo-$(date +%F).csv

docker service ls --format '{{.Name}};{{.Replicas}};{{.Image}}' > servicios-$(date +%F).csv

Así, ese archivo es un origen de datos como cualquier otro. Con Power Query se limpia y se acumula mes a mes, y con Power BI se convierte en un tablero de capacidad: memoria comprometida por nodo, réplicas por servicio y la evolución del consumo real frente a los límites. Es la misma idea que cierra la serie de Proxmox, aplicada a contenedores.

Buenas prácticas para Docker en producción

Por último, esta es la lista de comprobación que resume las tres guías. Ninguna práctica cuesta dinero; todas evitan un incidente concreto.

PrácticaQué evitaCómo
Fijar versionesActualizaciones sorpresa con latestnginx:1.30-alpine, nunca nginx a secas
Imágenes base pequeñasDescargas lentas y más componentes vulnerablesVariantes slim o alpine
Usuario sin privilegiosQue una intrusión en la aplicación sea root en el contenedorUSER en el Dockerfile
Límites de recursosQue un servicio agote el servidor enterodeploy.resources.limits
Pruebas de saludContenedores «arrancados» que no respondenhealthcheck real, no un proceso vivo
Secretos, no variablesContraseñas visibles con docker inspectsecrets y variables con sufijo _FILE
Registros que rotanDiscos llenos por la salida de los contenedoresControlador local en daemon.json
Puertos solo donde hace faltaServicios expuestos que ufw no filtraUn único proxy publicado y redes internas
No exponer el socketControl total del anfitrión desde un contenedorNo montar /var/run/docker.sock salvo en herramientas de confianza
Imágenes de origen fiableSoftware malicioso camufladoOficiales o de editores verificados
Copias fuera del servidorPerder los datos con el discoVolcado de la base de datos y copia externa
MonitoreoEnterarse de la caída por los usuariosSalud, consumo y disponibilidad vigilados

Además, tres de ellas enlazan con otras guías del blog. El riesgo de las imágenes de origen dudoso tiene precedente real: en 2022 apareció código malicioso en más de mil imágenes públicas. Las copias siguen el mismo criterio que la guía del respaldo inmutable. Y el monitoreo de un clúster es el de cualquier servidor, con las métricas que describe la guía de monitoreo de servidores.

Una práctica más, propia de la versión 29 del motor: la función Docker Content Trust desapareció del cliente. Quien firmaba imágenes con ella tiene que revisar su procedimiento antes de actualizar.

Preguntas frecuentes sobre Docker Swarm

¿Se puede empezar con un solo nodo?

Sí, y es una buena forma de aprender: docker swarm init en un servidor ya permite desplegar stacks, secretos y actualizaciones progresivas. Lo que no hay es tolerancia a fallos, que exige al menos tres managers. Por eso un clúster de dos nodos es la peor opción: si cae uno, se pierde la mayoría.

¿Existe una interfaz gráfica para Swarm?

El proyecto no trae una; sin embargo, existen herramientas web de terceros, como Portainer, que muestran nodos, servicios y registros. Son cómodas para consultar. Aun así, conviene que los cambios sigan entrando por el archivo del stack, para que quede constancia de cada uno.

¿Cuándo pasar de Swarm a Kubernetes?

Cuando el número de aplicaciones, de equipos o de requisitos de almacenamiento con estado supere lo que Swarm resuelve con sencillez. El archivo de Compose no se tira: es la mejor especificación de partida para escribir los manifiestos de Kubernetes.

Qué llevarse sobre Docker Swarm

Docker Swarm convierte el archivo de Compose en un servicio que sobrevive a la caída de un servidor, con el mismo formato y sin instalar nada aparte. La aplicación de la serie pasa de 4 contenedores a 7 tareas, de 1.536 a 2.176 MiB y de un servidor a tres, con actualizaciones sin corte y vuelta atrás automática. Su límite también quedó claro: las réplicas protegen a los servicios sin estado, y la base de datos necesita su propia estrategia.

En resumen, con esta guía se cierra la serie. Una imagen construida con cuidado en la primera, descrita en un archivo en la segunda y repartida en un clúster en la tercera. Si su empresa quiere los beneficios sin cargar con la operación, en KHARONTE administramos esa capa dentro del servicio de infraestructura de TI, y vigilamos su salud y su capacidad con el de monitoreo y operación.

Compartir este artículo

Últimas entradas

Escríbanos ahora