Qué es un buen despliegue
Desplegar es llevar un software desde el repositorio hasta el lugar donde lo usan personas reales. La señal de que se hace bien es que resulta aburrido: repetible, automatizado, observable y con una vuelta atrás que se ha probado antes de necesitarla. Casi todo lo que sigue persigue esa propiedad.
Hay dos ideas que organizan el resto. La primera: el artefacto es inmutable; no se parchea un servidor en marcha, se sustituye por uno nuevo construido desde cero. La segunda: la configuración se separa del artefacto, de modo que la misma imagen sirve en todos los entornos.
Contenedores: qué son y qué no son
Base común
El aislamiento del contenedor se basa en namespaces y cgroups del núcleo.
Una máquina virtual emula un ordenador completo, con su propio núcleo, sobre un hipervisor. Un contenedor es un proceso normal del sistema operativo anfitrión al que se le restringe lo que ve y lo que puede consumir. Dos mecanismos del núcleo Linux lo hacen posible, a nivel conceptual:
- Namespaces: dan al proceso su propia vista de los recursos: su árbol de procesos, su red, su sistema de ficheros, sus usuarios. Dentro del contenedor parece estar solo en el sistema.
- cgroups (grupos de control): limitan y contabilizan cuánta CPU, memoria y disco puede usar.
| Aspecto | Máquina virtual | Contenedor |
|---|---|---|
| Aislamiento | Fuerte: núcleo propio | Más débil: comparte el núcleo del anfitrión |
| Arranque | Decenas de segundos o minutos | Segundos o menos |
| Tamaño | Gigabytes | De megabytes a unos cientos de megabytes |
| Densidad | Pocas por servidor | Muchos por servidor |
| Sistema operativo distinto del anfitrión | Sí | No (comparte el núcleo) |
| Caso típico | Aislar inquilinos no confiables, otros sistemas operativos | Empaquetar y escalar aplicaciones propias |
Imágenes y capas
Una imagen es una plantilla de solo lectura formada por capas apiladas. Cada instrucción del Dockerfile que modifica ficheros crea una capa. Un contenedor es una instancia en ejecución de una imagen, con una fina capa de escritura efímera encima.
Consecuencias prácticas:
- Las capas se cachean y comparten: si no cambia una capa ni las anteriores, no se reconstruye ni se vuelve a descargar.
- Por eso se ordena el Dockerfile de lo que menos cambia a lo que más cambia: sistema base, dependencias, código.
- Borrar un fichero en una capa posterior no reduce el tamaño: sigue en la capa anterior. Hay que evitar crearlo, o usar multi-stage.
- Todo lo escrito en la capa del contenedor se pierde al destruirlo. Los datos persistentes van en volúmenes o en servicios externos.
Un Dockerfile bien hecho
Se copia con COPY --from; el resto se descarta.
La construcción en varias etapas separa lo que se necesita para compilar de lo que se necesita para ejecutar. El ejemplo es una aplicación Node, pero la estructura se aplica a cualquier lenguaje.
# Etapa 1: dependencias y build
FROM node:22-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
# Etapa 2: imagen final mínima
FROM node:22-slim AS runtime
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/package.json ./package.json
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD node dist/healthcheck.js
CMD ["node", "dist/server.js"]El fichero .dockerignore evita enviar al build lo que no debe estar en la imagen:
.git
node_modules
.env
.env.*
dist
coverage
*.log
Dockerfile
docker-compose*.ymlBuenas prácticas
| Práctica | Por qué |
|---|---|
| Imagen base mínima con versión fijada (o digest) | Menos superficie de ataque y builds reproducibles |
| Varias etapas | La imagen final no lleva compiladores ni herramientas de desarrollo |
Usuario sin privilegios (USER) | Un compromiso del proceso no da root dentro del contenedor |
| Copiar primero los ficheros de dependencias | Aprovecha la caché de capas |
.dockerignore | Builds más rápidos y sin secretos accidentales |
| Un proceso por contenedor | Se escala, reinicia y supervisa de forma independiente |
Forma exec en CMD (lista JSON) | El proceso recibe las señales de parada |
| Sin secretos en la imagen | Las capas se pueden inspeccionar |
| Escanear la imagen en el pipeline | Detecta vulnerabilidades conocidas en la base y las dependencias |
| Sistema de ficheros de solo lectura cuando se pueda | Impide modificar la imagen en ejecución |
Docker Compose: varios servicios en un fichero
Compose describe una aplicación formada por varios contenedores, sus redes y sus volúmenes. Es ideal para entornos de desarrollo, pruebas de integración y despliegues pequeños en un único servidor.
services:
web:
image: registro.ejemplo.com/equipo/web:1.4.2
ports:
- "8080:3000"
environment:
DATABASE_URL: postgres://app@db:5432/app
env_file:
- .env
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "dist/healthcheck.js"]
interval: 30s
timeout: 3s
retries: 3
db:
image: postgres:16
volumes:
- datos:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
retries: 5
volumes:
datos:Compose no sustituye a un orquestador: no reparte contenedores entre varias máquinas ni repone nodos caídos. Cuando se necesita alta disponibilidad real entre servidores, entra Kubernetes u otro orquestador.
Registros y etiquetado
El registro es el almacén de imágenes (Docker Hub, GitHub Container Registry, registros de los proveedores cloud o uno propio). Es el punto de encuentro entre el pipeline, que publica, y los entornos, que descargan.
- Etiquetar con la versión semántica y con el hash corto del commit, para saber siempre qué código contiene.
- Considerar las etiquetas como móviles por naturaleza: la única referencia inmutable es el digest (resumen criptográfico). Desplegar por digest garantiza que se ejecuta exactamente lo probado.
- Evitar
latesten producción: no dice qué versión es ni permite volver atrás con precisión. - Definir una política de retención para no acumular imágenes viejas, conservando las que estén desplegadas y las necesarias para volver atrás.
- Usar registros privados con credenciales de solo lectura en los entornos y de escritura solo en el pipeline.
Orquestación: los conceptos de Kubernetes
Gestiona los pods y las actualizaciones.
Réplicas
Configuración y salud
Un orquestador decide en qué máquina corre cada contenedor, lo repone si falla, lo escala y conecta los servicios entre sí. Kubernetes funciona de forma declarativa: se describe el estado deseado y un bucle de control trabaja continuamente para igualar el real a ese estado.
| Objeto | Función |
|---|---|
| Pod | La unidad mínima: uno o varios contenedores que comparten red y almacenamiento. Es efímero |
| Deployment | Mantiene un número de réplicas de un pod y gestiona las actualizaciones progresivas y la vuelta atrás |
| Service | Dirección y nombre estables delante de un conjunto de pods, con balanceo de carga |
| Ingress | Entrada del tráfico HTTP externo: enrutado por dominio o ruta y terminación TLS |
| ConfigMap | Configuración no sensible, separada de la imagen |
| Secret | Datos sensibles; por defecto solo codificados, por lo que conviene cifrado en reposo y control de acceso estricto |
| Namespace | Partición lógica del clúster para separar equipos o entornos |
| StatefulSet y volúmenes persistentes | Para cargas con estado e identidad estable |
| Job y CronJob | Tareas puntuales y programadas |
| HorizontalPodAutoscaler | Ajusta las réplicas según la carga |
Sondas (probes)
- Liveness: indica si el proceso está atascado; si falla, se reinicia el contenedor.
- Readiness: indica si puede recibir tráfico; si falla, se retira del servicio sin reiniciarlo.
- Startup: da margen a las aplicaciones de arranque lento antes de aplicar las otras.
Además, conviene declarar peticiones y límites de recursos (CPU y memoria), un presupuesto de interrupciones (PodDisruptionBudget) para no quedarse sin réplicas durante mantenimientos, y apagado ordenado que atienda la señal de parada y termine las peticiones en curso.
Los doce factores
La metodología de las aplicaciones de doce factores resume cómo diseñar software que se despliega bien en entornos modernos.
| Factor | Idea |
|---|---|
| Código base | Un repositorio, muchos despliegues |
| Dependencias | Declaradas y aisladas, nunca implícitas |
| Configuración | En el entorno, no en el código |
| Servicios de apoyo | Bases de datos y colas como recursos enlazables por configuración |
| Build, release, run | Etapas separadas; el release combina build y configuración |
| Procesos | Sin estado y sin compartir nada; el estado vive en servicios externos |
| Vinculación de puertos | La app expone su propio servicio |
| Concurrencia | Se escala añadiendo procesos |
| Desechabilidad | Arranque rápido y parada limpia |
| Paridad dev y prod | Entornos lo más parecidos posible |
| Logs | Un flujo de eventos a la salida estándar, no ficheros gestionados por la app |
| Procesos administrativos | Tareas puntuales (migraciones) con el mismo código y entorno |
Configuración y secretos
La configuración es todo lo que varía entre entornos: URLs, credenciales, interruptores. Se inyecta al arrancar, por variables de entorno o ficheros montados, nunca dentro de la imagen.
- Las variables de entorno son simples, pero pueden filtrarse en volcados y en herramientas de diagnóstico.
- Los gestores de secretos (Vault, los de los proveedores cloud, secretos cifrados en Git con herramientas como SOPS) añaden control de acceso, auditoría y rotación.
- Los secretos se rotan y se pueden revocar sin reconstruir la imagen.
- Distinguir configuración (puede ser pública) de secreto (no).
- Validar la configuración al arrancar y fallar de inmediato si falta algo, en lugar de descubrirlo con la primera petición.
Estrategias de despliegue
Con corte
Sin corte
Con exposición controlada
| Estrategia | Cómo funciona | Ventaja | Compromiso |
|---|---|---|---|
| Recreate | Se detiene la versión vieja y se arranca la nueva | Simple; sin dos versiones a la vez | Hay corte de servicio |
| Rolling | Se sustituyen las instancias poco a poco | Sin corte, sin duplicar infraestructura | Conviven dos versiones; la vuelta atrás es lenta |
| Blue-green | Dos entornos completos; se conmuta el tráfico | Cambio y vuelta atrás casi instantáneos | Coste de duplicar entorno; datos compartidos delicados |
| Canary | Una pequeña fracción del tráfico va a la versión nueva y se amplía si las métricas son buenas | Limita el daño de un error | Exige buenas métricas y enrutado fino |
| Shadow | La versión nueva recibe una copia del tráfico real, sin que su respuesta llegue al usuario | Prueba con carga real sin riesgo visible | Hay que evitar efectos secundarios duplicados (pagos, correos) |
| Feature flags | El código se despliega apagado y se activa por usuario o porcentaje | Separa desplegar de liberar; apagado inmediato | Los flags se acumulan y hay que retirarlos |
Se apunta al entorno activo.
La decisión depende de lo que cuesta fallar y de lo que cuesta protegerse. Cuanta más sofisticación, más infraestructura y observabilidad hacen falta para sacarle partido.
Migraciones de base de datos sin corte
El código se puede sustituir en segundos; el esquema de datos, no. Durante un despliegue progresivo conviven versiones distintas del código sobre la misma base de datos, así que el esquema tiene que servir a ambas.
El patrón se llama expandir y contraer:
- Expandir: añadir lo nuevo (columna, tabla) de forma compatible con el código antiguo: opcional y sin quitar nada.
- Desplegar código que escribe en lo nuevo y en lo viejo, y lee de lo nuevo.
- Rellenar los datos históricos en lotes, sin bloquear tablas.
- Contraer: cuando ninguna versión desplegada usa lo antiguo, eliminarlo en un despliegue posterior.
Otras reglas: las migraciones van versionadas con el código; deben ser idempotentes; los cambios destructivos (borrar, renombrar) se dividen en varios pasos; se prueban contra una copia de datos realista; y se ejecutan como un paso aparte del arranque de la aplicación, no en cada réplica a la vez.
Vuelta atrás
La vuelta atrás es parte del diseño, no una improvisación. Para que sea posible:
- Los artefactos anteriores siguen disponibles en el registro.
- El despliegue de una versión anterior es un único comando o botón, ensayado.
- Los cambios de esquema son compatibles hacia atrás (de ahí expandir y contraer).
- Las banderas de funcionalidad permiten apagar un comportamiento sin desplegar.
- Se decide de antemano qué señal dispara la vuelta atrás: tasa de errores, latencia o fallo de una prueba de humo.
Revertir suele ser más rápido y seguro que arreglar hacia delante bajo presión, salvo cuando hay datos ya transformados que el código antiguo no entiende.
Dónde alojar
Tú gestionas el sistema
Tú gestionas la aplicación
Tú gestionas la función
| Modelo | Qué se gestiona | Cuándo encaja | Contrapartida |
|---|---|---|---|
| Bare metal | Todo, hasta el hardware | Rendimiento máximo o requisitos específicos | Máxima responsabilidad operativa |
| VPS | Sistema operativo y todo lo de encima | Proyectos pequeños, coste previsible | Parches y seguridad a cargo propio |
| IaaS | Máquinas virtuales, redes y discos bajo demanda | Control con elasticidad | Mucha configuración |
| PaaS | Solo la aplicación; la plataforma se ocupa del resto | Equipos pequeños, velocidad | Menos control; dependencia del proveedor |
| Contenedores gestionados | Imágenes; el proveedor lleva el orquestador | Escalado sin operar el clúster | Costes y límites del servicio |
| Serverless | Funciones; se paga por uso | Cargas irregulares o por eventos | Arranques en frío, límites de duración, dependencia del proveedor |
| Edge | Código ejecutado cerca del usuario | Latencia baja, lógica ligera | Entorno de ejecución restringido; datos distribuidos |
No hay una opción mejor en abstracto: se elige según tamaño del equipo, patrón de carga, requisitos de cumplimiento y tolerancia a la dependencia de un proveedor.
Un servicio en un servidor con systemd
En un servidor sin contenedores, systemd supervisa los procesos, los reinicia si caen y los arranca con el sistema.
[Unit]
Description=Servicio web
After=network-online.target
Wants=network-online.target
[Service]
User=app
Group=app
WorkingDirectory=/opt/app
EnvironmentFile=/etc/app/entorno
ExecStart=/opt/app/bin/servidor
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/app
[Install]
WantedBy=multi-user.targetCon systemctl enable --now se activa y arranca, y journalctl -u da los logs. Las opciones de endurecimiento (ProtectSystem, NoNewPrivileges) restringen lo que el proceso puede tocar, con un coste de configuración muy bajo.
Infraestructura como código
Revisión en pull request.
Se guarda de forma remota y con bloqueo.
La infraestructura (redes, servidores, bases de datos, permisos) se declara en ficheros versionados, se revisa en pull request y se aplica de forma automatizada.
| Herramienta | Enfoque |
|---|---|
| Terraform u OpenTofu | Declarativo: describe el estado final de los recursos; calcula un plan de cambios y lo aplica |
| Pulumi | Declarativo, usando lenguajes de programación generales |
| Ansible | Configuración de servidores por tareas idempotentes, sin agente, por SSH |
| Plantillas del proveedor | Equivalentes propios de cada nube |
Principios: revisar siempre el plan antes de aplicar; guardar el estado en un almacén remoto con bloqueo y cifrado, porque puede contener secretos; separar estados por entorno; detectar la deriva (cambios hechos a mano); y favorecer la inmutabilidad: ante un cambio, se crea un recurso nuevo y se descarta el viejo, en lugar de modificarlo en caliente. Un servidor al que cada persona ha ido haciendo retoques se vuelve irrepetible.
Copias de seguridad y recuperación ante desastres
El RPO decide la frecuencia de copia; el RTO, la complejidad de la recuperación.
- RPO (objetivo de punto de recuperación): cuántos datos se pueden perder, medido en tiempo. Determina cada cuánto hay que copiar.
- RTO (objetivo de tiempo de recuperación): cuánto se puede tardar en volver a operar. Determina cuánta automatización y redundancia hacen falta.
Reglas que se repiten en cualquier guía seria: la regla 3-2-1 (tres copias, en dos soportes distintos, una fuera del sitio); copias inmutables o aisladas, para que un ataque o un error no las borre también; cifrado de las copias; y, sobre todo, ensayar la restauración, porque una copia que nunca se ha restaurado es una hipótesis. Conviene documentar el procedimiento de recuperación y probarlo de forma periódica, incluido el caso de perder una región entera.
Aplicaciones móviles: la entrega a nivel conceptual
Desplegar una app móvil difiere de desplegar un servicio: el usuario controla cuándo actualiza y existe un intermediario, la tienda de aplicaciones.
- Cada versión pasa una revisión de la tienda, con plazos que no controla el equipo.
- Conviven versiones antiguas durante mucho tiempo, así que el servidor debe seguir sirviendo a clientes viejos: la API se versiona y se evita romper compatibilidad.
- Se usan canales de distribución (pruebas internas, pruebas cerradas, lanzamiento por fases) para exponer una versión a un porcentaje creciente de usuarios.
- Las banderas remotas y la configuración remota permiten cambiar comportamientos sin pasar por la tienda.
- Las firmas y certificados son críticos: perder la clave de firma puede impedir actualizar la app.
- Un mecanismo de actualización forzada permite retirar versiones con fallos graves o inseguras.
Lista de comprobación
- Hay un único artefacto inmutable por versión, identificado por digest.
- La imagen usa base mínima, multi-stage, usuario sin privilegios y
.dockerignore. - La configuración y los secretos se inyectan al arrancar; no hay nada sensible en la imagen.
- Hay sondas de liveness y readiness, y apagado ordenado.
- Los recursos (CPU y memoria) están dimensionados y limitados.
- La estrategia de despliegue encaja con el coste de un fallo.
- Las migraciones son compatibles con la versión anterior y la nueva.
- La vuelta atrás es un paso ensayado.
- La infraestructura está en código, con plan revisado y estado remoto.
- Las copias de seguridad tienen RPO y RTO definidos y la restauración se ha probado.
Antipatrones
- Servidores copo de nieve: configurados a mano e irrepetibles.
- Desplegar con
latesto con etiquetas que cambian. - Contenedor que ejecuta como root sin necesidad.
- Varias responsabilidades en un contenedor (aplicación, base de datos y cron juntos).
- Datos importantes dentro del contenedor, sin volumen ni servicio externo.
- Secretos en la imagen, en el repositorio o en el Dockerfile.
- Migraciones destructivas en el mismo despliegue que el código que las necesita.
- Orquestador por moda: usar Kubernetes para lo que cabe en un servidor.
- Copias de seguridad sin restauración probada.
- Entornos manuales no reproducibles, imposibles de recrear tras un desastre.
- Sin sondas de salud: el balanceador envía tráfico a instancias que no están listas.