gen[IA]ll Tutor
DEVOPS-FUNDAMENTALS

Containers

Actualizado en octubre de 2026 · 24 min de lectura

Contenedores

Resumen

Un contenedor es un entorno aislado para ejecutar una aplicación con sus dependencias. Comparte el kernel del host y separa procesos, red y archivos con mecanismos del sistema operativo, lo que lo hace más ligero que una máquina virtual, con otro límite de aislamiento. Las imágenes se distribuyen mediante registros y los orquestadores coordinan réplicas.

Material de estudio independiente; sin afiliación con Linux Foundation, Docker ni la CNCF.

Objetivos de aprendizaje

  • Explicar cómo se aísla un contenedor y qué comparte con el host.
  • Diferenciar imagen, contenedor y capa de escritura.
  • Comparar contenedores con máquinas virtuales.
  • Describir el papel de un registro y de una referencia de imagen.
  • Reconocer cuándo un orquestador aporta valor.

Prerrequisitos y conexiones

Conviene haber leído devops-basics y git-concepts. Los contenedores se apoyan en conceptos de procesos, permisos y redes de Linux, y las imágenes suelen construirse desde un repositorio versionado que alimenta una pipeline de CI/CD.

Qué es un contenedor

Un contenedor es un proceso —o grupo de procesos— con vistas aisladas del sistema. Los namespaces de Linux separan identificadores de procesos, interfaces de red, puntos de montaje y nombres de host. Los cgroups limitan y contabilizan CPU, memoria y otros recursos. Las capacidades y perfiles de seguridad restringen operaciones privilegiadas. Así se obtiene un entorno coherente sin arrancar un sistema operativo completo por instancia.

La imagen es la plantilla de solo lectura; el contenedor es una instancia en ejecución. Al crearlo se añade una capa de escritura sobre la imagen. Si el contenedor se elimina, la capa se pierde; los datos que deben sobrevivir se guardan en volúmenes o servicios externos.

Los contenedores están pensados para procesos sin estado y ciclos cortos. Reiniciar un contenedor debe ser normal. Esto encaja con la infraestructura inmutable: se reemplaza una instancia por otra construida desde la misma imagen.

Sistema de capas en unión y caché de construcción

Las capas se apilan mediante un sistema de archivos en unión: cada capa añade, modifica o elimina archivos sobre la anterior, y la vista combinada es lo que el contenedor ve. La escritura no modifica la imagen: ocurre en la capa superior del contenedor. Por eso dos contenedores de la misma imagen comparten todas las capas de solo lectura y solo la capa de escritura es propia.

La caché de construcción se evalúa instrucción por instrucción: si la definición y sus entradas no cambian, el constructor reutiliza la capa resultante; al primer cambio, todo lo posterior se reconstruye. De ahí que ordenar los pasos importe tanto como escribirlos. Instalar dependencias es lento y cambiar código es rápido, así que mantener separadas esas fases preserva el trabajo costoso. Si el orden queda invertido, cada ajuste en el código invalida la instalación y la construcción se alarga sin necesidad.

Los archivos temporales y resultados de compilación que no deben viajar en la imagen se excluyen con un archivo de ignoración del constructor, y docker build --no-cache obliga a reconstruir cuando la caché ya no es fiable. docker history muestra qué instrucción produjo cada capa y cuánto pesa, lo que ayuda a detectar capas inesperadamente grandes.

Imágenes, capas y Dockerfile

Una imagen se construye a partir de un Dockerfile u otro constructor compatible con OCI. Cada instrucción relevante añade una capa: sistema base, dependencias, código y arranque. Las capas se reutilizan y se almacenan en caché, lo que acelera construcciones cuando no cambia lo anterior. Copiar primero las dependencias y después el código evita reconstruirlas en cada cambio.

Una etiqueta (tag) es un nombre móvil que apunta a una imagen, como 1.4 o latest. Un digest identifica el contenido de forma inmutable. En producción conviene fijar versiones o digests para que un despliegue no cambie porque alguien reescribió la etiqueta. latest es una convención, no una garantía de novedad.

El Dockerfile también define usuario, directorio de trabajo, variables y comando de arranque. Ejecutar sin privilegios, usar una base mínima y evitar secretos reduce la superficie de ataque. Las construcciones multi-stage compilan en una etapa y copian solo el resultado a una imagen final más pequeña.

Redes, puertos y volúmenes

Por defecto, un contenedor comparte la red con otros en una red puente creada por el motor, con traducción de puertos hacia el host. La notación -p 8080:80 publica el puerto 80 del contenedor en el 8080 del host; el orden —host primero, contenedor después— es una causa habitual de confusión. Publicar un puerto expone el servicio fuera de la red interna; no publicarlo mantiene el tráfico entre contenedores de la misma red.

En redes definidas por el usuario, los contenedores se resuelven por nombre, lo que permite que un servicio apunte a otro sin fijar direcciones cambiantes. Ese mecanismo simplifica la configuración, pero exige que la definición de la red también esté versionada: una red creada a mano en una máquina no aparece en otra.

El almacenamiento tiene tres caminos. Los volúmenes nombrados los administra el motor, sobreviven a la eliminación del contenedor y sirven para datos compartidos entre instancias. Los montajes de enlace apuntan a una ruta del host, útiles para desarrollo, con el riesgo de permisos y de depender de la máquina. Los sistemas de archivos temporales permiten escritura volátil sin persistencia. Los datos que deben sobrevivir van a un volumen o a un servicio externo; la capa de escritura no es un almacén.

Contenedores frente a máquinas virtuales

Una máquina virtual incluye un sistema operativo invitado completo y se ejecuta sobre un hipervisor. Ese límite ofrece aislamiento más fuerte y permite kernels distintos, a cambio de más recursos y arranques más lentos. Un contenedor comparte el kernel: arranca rápido y permite mayor densidad, pero una vulnerabilidad del kernel o una configuración privilegiada puede afectar al host.

La elección depende del requisito. Para muchas réplicas de un servicio sin estado, un contenedor suele ser eficiente. Para sistemas operativos distintos, cargas con aislamiento fuerte o software que necesita un kernel específico, una máquina virtual puede ser mejor. Ambos modelos se combinan: contenedores dentro de máquinas virtuales.

CriterioContenedorMáquina virtual
KernelComparte el del hostKernel invitado propio
ArranqueSegundos o menosMás lento
AislamientoProceso y sistema operativoHipervisor
DensidadAltaMenor

Aislamiento y endurecimiento en ejecución

Namespaces y cgroups forman la base, pero la defensa en ejecución se completa con perfiles de filtrado de llamadas al sistema (seccomp), módulos de seguridad obligatorios como AppArmor o SELinux, y capacidades: en lugar de otorgar permisos de root completos, se retiran los innecesarios y se conservan solo los imprescindibles para el proceso. Ejecutar como usuario sin privilegios reduce el impacto si el código tiene una vulnerabilidad.

Dos refuerzos frecuentes limitan el daño: un sistema de archivos de solo lectura, con escritura únicamente en los directorios que la necesitan —montados aparte—, y la negativa a que los procesos obtengan privilegios superiores a los del propietario. Los límites de memoria y CPU evitan que una instancia saturada arrastre al nodo; superar el límite de memoria suele terminar en la terminación del proceso, no en una degradación elegante.

Un fallo clásico de diseño es dejar un proceso huérfano como inicial: si el comando principal no recoge los estados de sus hijos, estos se acumulan como zombis. Otro problema aparece con las señales: una forma de comando que pasa por un intérprete intermedio impide que una orden de detención llegue al proceso real, con el resultado de cortes bruscos en lugar de apagados ordenados. El arranque de la imagen debe reflejar cómo responde de verdad la aplicación a las señales.

Registros y distribución de imágenes

Un registro (registry) almacena y distribuye imágenes. Puede ser público o privado y aplicar autenticación y control de acceso. docker push publica una imagen y docker pull la descarga. Los registros corporativos permiten controlar qué imágenes usa una organización.

Una referencia combina registro, repositorio y etiqueta o digest: registry.example.com/equipo/api:1.4. Si se omite el registro, se asume Docker Hub; si se omite la etiqueta, se usa latest. La especificación de distribución de OCI define el intercambio de imágenes y artefactos.

El registro no ejecuta contenedores: guarda y sirve artefactos. Escanear y firmar imágenes son prácticas de cadena de suministro que lo complementan.

Referencias, digests y promoción de imágenes

Una referencia con etiqueta es cómoda para las personas y ambigua para la automatización: la misma etiqueta puede apuntar a contenidos distintos con el tiempo. El digest —una identificación derivada del contenido del manifiesto— no cambia si alguien vuelve a publicar la etiqueta. Descargar con imagen@sha256:... fija exactamente los bits que se probaron.

La promoción entre entornos aprovecha esa inmutabilidad: se construye una vez, se escanea, se firma y se publica en el registro; cada entorno posterior consume el mismo digest. Volver a construir en el paso siguiente introduce diferencias silenciosas —compilador, dependencias resueltas al momento— y rompe la trazabilidad entre lo probado y lo ejecutado.

Las referencias también admiten arquitecturas múltiples: un mismo nombre puede resolver a un conjunto de imágenes para procesadores distintos y el motor elige la compatible. Por eso conviene verificar qué contiene una referencia antes de asumir que todos los entornos reciben lo mismo, especialmente cuando se mezclan máquinas con arquitecturas diferentes.

Elección de la base y mantenimiento

La imagen base determina qué parches de seguridad dependen de terceros y qué herramientas quedan disponibles dentro del contenedor. Bases completas facilitan la depuración pero aumentan el tamaño y la superficie de actualización; bases mínimas reducen ambas cosas y trasladan a la propia construcción la responsabilidad de instalar lo necesario.

Actualizarse no es solo reconstruir cuando cambia el código: una vulnerabilidad en la base exige reconstruir aunque la aplicación sea idéntica. Por eso conviene tener trazabilidad de qué versión de la base produjo cada imagen desplegada y una rutina periódica de reconstrucción, no solo reaccionar al siguiente release de la aplicación.

También hay que decidir qué herramientas de diagnóstico viajan en la imagen de producción. Incluir un intérprete o utilidades de red puede acortar una incidencia; el mismo paquete amplía lo que un atacante encontraría si logra ejecutar código. El equilibrio depende del riesgo del servicio y de si existe otra vía —como un túnel de depuración temporal— para inspeccionar sin transportar esas herramientas por defecto.

Ciclo de vida y orquestación básica

docker run crea y arranca un contenedor. -d lo ejecuta en segundo plano, -p 8080:80 publica un puerto del host, -e define variables y -v monta un volumen o directorio. docker ps lista contenedores, docker logs muestra su salida, docker exec abre un proceso dentro y docker stop/docker rm los detienen y eliminan.

Con muchas réplicas aparecen preguntas que un orquestador resuelve: dónde ejecutar cada instancia, cómo reemplazar las que fallan, cómo repartir tráfico y cómo actualizar sin interrupción. Kubernetes mantiene el estado deseado desde su plano de control y ejecuta contenedores en los nodos. Un Pod agrupa contenedores que comparten red y almacenamiento; un Deployment gestiona réplicas; un Service ofrece una dirección estable.

El orquestador no elimina la necesidad de imágenes bien construidas ni de configuración externa. Añade capacidades y complejidad, por lo que su adopción debe responder a una necesidad real.

Orquestación: salud, recursos y configuración

Kubernetes distingue dos comprobaciones de salud con efectos opuestos: la de disponibilidad decide si una réplica recibe tráfico, mientras que la de vida decide si el proceso debe reiniciarse. Confundirlas produce reinicios continuos por un servicio que tarda en estar listo, o tráfico enviado a instancias que aún no pueden atenderlo.

Los recursos se declaran con solicitud y límite. La solicitud es lo que el plano de control reserva al programar la instancia; el límite es el techo que puede consumir. Sin límites, una réplica defectuosa puede acaparar el nodo; con límites mal calibrados, las instancias se terminan cuando la carga real supera la previsión.

La configuración llega desde objetos externos al contenedor: un mapa para valores no sensibles y un objeto reservado para secretos, ambos montados o inyectados como variables. Así la imagen permanece igual entre entornos. Las actualizaciones por fracciones —controlar cuántas réplicas pueden caer y cuántas pueden aparecer a la vez— permiten actualizar sin interrumpir el servicio, siempre que las comprobaciones de disponibilidad sean fiables.

Contenedor frente a proceso del host

Un contenedor es un proceso del host, pero con una envoltura que un servicio convencional no tiene: sistema de archivos empaquetado, red y recursos aislados, límites contabilizados y ciclo de vida administrado por el motor. Un proceso tradicional hereda las bibliotecas del sistema, comparte la red global y compite por recursos sin techo declarado.

La diferencia se nota en operación. Reiniciar, versionar y reproducir un contenedor depende de la imagen; hacerlo con un proceso depende de guías manuales y del estado acumulado de la máquina. Para una aplicación única en una máquina conocida, un servicio del sistema operativo puede ser suficiente y más simple. Cuando se necesitan muchas instancias idénticas, portabilidad entre máquinas o límites de recursos claros, la envoltura del contenedor paga su complejidad.

La distinción importa al depurar: docker logs y docker exec trabajan sobre el proceso real dentro del aislamiento, y un error de arranque suele mostrar que el comando inicial no corresponde al binario esperado, que faltan dependencias en la imagen o que el usuario carece de permisos sobre los archivos copiados.

Ejemplos prácticos

Un Dockerfile sencillo para un servicio Node.js:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]

La construcción y ejecución local pueden ser:

docker build -t demo:1.0 .
docker run --rm -p 3000:3000 -e NODE_ENV=production demo:1.0

El contenedor publica el puerto 3000, recibe configuración por variable de entorno y elimina su capa de escritura al terminar. Los datos persistentes deberían montarse en un volumen.

Ejemplo práctico: imagen mínima y datos persistentes

Una etapa de compilación produce un binario; la etapa final solo lo copia:

FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app

FROM alpine:3.20
RUN adduser -D app
USER app
COPY --from=build /out/app /usr/local/bin/app
ENTRYPOINT ["/usr/local/bin/app"]

La imagen final no contiene el compilador ni el código fuente: solo el binario y una base pequeña. El resultado es menor y con menos superficie que parchear. La caché conserva la descarga de dependencias mientras go.mod y go.sum no cambien.

Para los datos:

docker volume create informes
docker run --rm -p 8080:8080 -v informes:/datos informes:2.0

El volumen informes sobrevive a --rm: la capa de escritura desaparece con el contenedor, pero los archivos escritos en /datos permanecen. Eliminar el volumen es un paso explícito y distinto, lo que obliga a decidir cuándo se destruyen los datos de verdad.

Errores frecuentes

  • Usar solo latest. Un despliegue puede cambiar de imagen sin que cambie la referencia; fijar versión o digest da reproducibilidad.
  • Incluir secretos en la imagen. Quedan en las capas y pueden extraerse; se inyectan en ejecución desde un gestor de secretos.
  • Guardar estado dentro del contenedor. La capa de escritura es efímera; los datos importantes van a un volumen o servicio externo.
  • Ejecutar como root por comodidad. Aumenta el impacto de una vulnerabilidad; conviene un usuario sin privilegios.
  • Publicar el socket de Docker en un contenedor. Otorga control efectivo sobre el host y debe evitarse salvo en casos muy controlados.
  • Declarar límites de recursos solo en producción. Si nunca se probaron, la primera carga real revela el ajuste en el peor momento.
  • Dar por buena la salud de un contenedor solo porque está en ejecución. Un proceso vivo puede no servir tráfico; la comprobación de disponibilidad lo distingue.
  • Ignorar el orden de los pasos en el Dockerfile. Un cambio de código invalida instalaciones costosas si la caché se rompe antes de tiempo.

Puntos clave para el examen

  • El contenedor comparte el kernel del host y se aísla con namespaces, cgroups y controles de seguridad.
  • La imagen es inmutable y por capas; el contenedor añade una capa de escritura efímera.
  • Las máquinas virtuales ofrecen un límite de aislamiento distinto y más peso por instancia.
  • El registro almacena y distribuye imágenes; no las ejecuta.
  • Kubernetes aporta programación, reconciliación y escalado cuando hay muchas réplicas.
  • Las capas se comparten entre contenedores de la misma imagen; la escritura vive solo en la capa superior.
  • El digest fija el contenido exacto de una imagen; la etiqueta solo nombra un puntero movible.
  • Los volúmenes sobreviven a la eliminación del contenedor; la capa de escritura no.
  • En orquestación, disponibilidad y vida son comprobaciones distintas con consecuencias opuestas.
  • Una imagen mínima reduce superficie de parcheo y acelera su distribución.

Recursos externos

Autoevaluación

  1. ¿Qué comparte un contenedor con el host? Respuesta: El kernel del sistema operativo. Justificación: El aislamiento se logra con namespaces, cgroups y controles del kernel, no con un kernel invitado.

  2. ¿Qué ocurre con los datos escritos en la capa de un contenedor al eliminarlo? Respuesta: Se pierden, salvo que estén en un volumen o en un servicio externo. Justificación: La capa de escritura pertenece al contenedor y se elimina con él.

  3. ¿Cuándo es más adecuada una máquina virtual que un contenedor? Respuesta: Cuando se necesita un kernel distinto o un aislamiento más fuerte a nivel de hipervisor. Justificación: El contenedor comparte el kernel y no puede ejecutar otro sistema operativo completo.

  4. ¿Cuál es la diferencia entre una etiqueta y un digest? Respuesta: La etiqueta es un nombre que puede reasignarse; el digest identifica el contenido de forma inmutable. Justificación: Fijar el digest garantiza que se usa exactamente la misma imagen.

  5. ¿Qué aporta un orquestador como Kubernetes? Respuesta: Mantener el estado deseado, programar réplicas, reemplazar las que fallan y ofrecer red estable. Justificación: Ejecutar muchos contenedores manualmente no escala ni se recupera solo.