cover

Deja Docker un rato: entiende qué es un container ejecutando uno a mano

author photo

Héctorbliss

@hectorbliss


Parte 1 de la serie De Docker a Sandboxes: el rabbit hole del aislamiento. }Aaprende, paso a paso, qué hay por debajo de docker run.


El momento incómodo

Llevo años usando Docker. Tengo Dockerfiles en todos mis proyectos, entiendo compose, he debuggeado builds que fallan en CI, sé cuándo usar multi-stage builds. Por cualquier métrica profesional razonable, sé Docker.

Hasta que alguien me preguntó: "¿Pero qué es un container, exactamente?"

Y me descubrí contestando cosas como "es una VM ligera" (mentira), "es un proceso aislado" (cierto pero vacío), "es un namespace con cosas" (ok, ¿cuáles?, ¿cómo?, ¿por qué?). Me di cuenta de que tenía un agujero del tamaño de una camioneta en mi stack mental.

Así que decidí cerrarlo. Esta serie documenta ese viaje — desde "qué chingados es un PID namespace" hasta "entiendo por qué Docker Sandboxes usa microVMs en vez de containers normales". Si tú también usas Docker a diario pero sientes que es una caja negra, bienvenido.

Meta del post de hoy: al terminar vas a crear un "container" funcional en tu terminal sin tocar Docker ni una sola vez. Treinta minutos. Copiar, pegar, observar.


Requisitos

  • Windows 11 con WSL2 + Ubuntu, o cualquier Linux moderno, o una Mac con una VM Linux encima (UTM, Multipass, Lima).
  • Nada de Docker para este post. A propósito.

Si estás en Windows, abre PowerShell y entra a Ubuntu:

El prompt debe verse así: tu_usuario@maquina:~$. Ese $ al final es tu seña de que estás en Linux de verdad. Si ves PS C:\...>, estás en PowerShell — no va a funcionar nada de lo que sigue.


Parte 1 — ¿Qué es un PID, y por qué debería importarte?

Todo proceso corriendo en Linux tiene un PID (Process ID), un número único. Pruébalo:

Vas a ver una columna PID con números. Cada fila es un proceso vivo en tu sistema. Tu shell también tiene uno:

Eso te da el PID de tu bash actual.

El PID 1 es especial. Es el primer proceso que arranca cuando Linux bootea — el "init" del sistema, normalmente systemd en distros modernas. Es el padre conceptual de todos los demás procesos. Si matas PID 1, el sistema se apaga.

Te dice quién es PID 1 en tu WSL.

Retén esta idea: en Linux "normal", solo hay un PID 1, y ese es el jefe de todo. Guarda esto porque en cinco minutos vas a ser PID 1 de una realidad paralela.


Parte 2 — La paranoia del kernel: namespaces

Un namespace es un mecanismo del kernel Linux que le miente a un proceso sobre qué puede ver del sistema. La analogía que más me sirvió: imagínate una oficina de 100 personas. Normalmente todos se ven entre sí. Pero si pones paredes de vidrio polarizado alrededor de un grupo de 3, esos 3 creen estar solos en la oficina. Siguen físicamente ahí, comparten el aire acondicionado y los baños, pero su percepción es otra.

El kernel hace exactamente eso, pero con procesos, redes, filesystems, hostnames, usuarios y más.

Hoy existen 8 tipos de namespaces en Linux. Puedes verlos tú mismo:

Esto te devuelve algo como:

Cada archivo representa un namespace al que tu shell pertenece ahora mismo. No son copias — son punteros al namespace real en el kernel. Si otro proceso comparte el mismo pid namespace que tú, ven los mismos procesos que tú ves.

Un container de Docker es, en esencia, un proceso con 8 namespaces distintos a los del host. Eso es todo. No es magia, no es virtualización, no hay un "Linux adentro de Linux". Es el mismo kernel mintiéndole a un proceso sobre qué puede ver. Vamos a probarlo.


Parte 3 — Tu primer container manual (sin Docker)

Instalemos herramientas básicas primero:

Ahora el comando mágico:

Vamos desglosándolo porque aquí es donde empieza todo:

  • unshare es una syscall (envuelta en un comando) que le dice al kernel "crea nuevos namespaces para el proceso que voy a lanzar".
  • --pid crea un nuevo PID namespace.
  • --fork hace fork antes del exec (necesario porque el proceso que ejecuta unshare no puede él mismo cambiar de PID namespace, tiene que hacerlo un hijo).
  • --mount-proc remonta /proc para que refleje el nuevo PID namespace. Si no haces esto, ps te sigue mostrando los procesos del host aunque estés aislado.
  • bash es el programa que arranca dentro del nuevo namespace.

El prompt cambia (posiblemente a root@...). Ahora, dentro:

Observa con atención: solo ves 2-3 procesos. Tu bash es PID 1. Eres el init del sistema. Eres systemd (bueno, no, pero tu shell cree que sí).

Ahora abre una segunda ventana de Ubuntu. En Windows Terminal: Ctrl+Shift+T y selecciona Ubuntu. En esa ventana nueva:

Ves cientos de procesos. Son los de verdad. Los mismos procesos que tu primer shell no puede ver. Son la misma máquina, el mismo kernel, el mismo hardware — solo hay una pared de vidrio polarizado entre ambas ventanas.

Para remachar el clavo, en la segunda ventana busca tu shell "container":

Encontrarás tu bash "aislado" corriendo con algún PID alto, digamos 12345. Desde afuera es solo un proceso más. Desde adentro, es el rey del universo.

Sal del shell aislado con exit y regresa a la realidad.


Parte 4 — Combinar namespaces: más ilusión

Aislar PIDs solo es interesante pero no impresionante. Vamos a capas. Agarrémonos un shell con tres namespaces distintos al host: PID, UTS (hostname) y mount (filesystem mounts).

Dentro:

El hostname cambió. Ahora crea un filesystem que solo existe aquí:

Sin salir, vete a la segunda ventana y corre:

Vas a ver dos cosas:

  1. El hostname real del sistema no cambió. Sigue siendo el tuyo de siempre.
  2. /tmp/secreto no existe o está vacío. El mount -t tmpfs que hiciste vive solo dentro del mount namespace hijo.

Regresa al primer shell, sal con exit, y verifica que el hostname volvió al original y que /tmp/secreto ya no tiene tu archivo. Todo lo que hiciste dentro se evaporó, porque el namespace se destruyó al salir.


¿Y Docker, entonces?

Acabas de hacer, a mano y con cuatro comandos, tres cuartas partes de lo que Docker hace cuando corres docker run. Literalmente. Docker agrega los otros 5 namespaces (network, IPC, user, cgroup, time), configura cgroups para limitar recursos, monta un filesystem desde una imagen OCI, aplica seccomp y AppArmor, y te da un CLI bonito. Pero el corazón del aislamiento es lo que acabas de tocar con las manos.

Cuando tu próximo container falle con "Cannot allocate memory" — es un cgroup. Cuando docker exec no te deja ver cierto proceso — es un PID namespace. Cuando tu container no llega a la red del host — es un network namespace. Ya no es magia.


Lo que viene en la serie

Este post es la parte 1. En los próximos vamos a:

  • Parte 2: Network namespaces — crear una red virtual aislada, veth pairs, por qué docker0 existe.
  • Parte 3: cgroups v2 — límites de RAM, CPU, I/O. Cómo OOM-killear un proceso a propósito (y por qué eso importa).
  • Parte 4: Capabilities, seccomp, AppArmor — las tres capas de defense-in-depth que separan un container seguro de un container puerta-abierta.
  • Parte 5: Construir un mini container runtime en ~200 líneas de Go. Sin dependencias, sin Docker, solo syscalls.
  • Parte 6 en adelante: OCI runtimes (runc, gVisor, Kata), escapes de containers famosos, microVMs con Firecracker, y finalmente Docker Sandboxes.

El objetivo al final de la serie: que entiendas por qué microVMs como Firecracker existen, qué problema resuelven que los namespaces no, y cómo construirías tú mismo una plataforma tipo E2B o Trigger.dev.


Checkpoint: ¿entendiste esta parte?

Si puedes responder estas tres sin googlear, vas bien:

  1. ¿Por qué --mount-proc es necesario en unshare --pid? ¿Qué pasa si lo omites?
  2. ¿Qué diferencia hay entre el PID de tu bash dentro del namespace y afuera?
  3. Si montas un tmpfs en un mount namespace y sales de él, ¿qué pasa con los archivos? ¿Se borran, se persisten, o ninguno de los dos exactamente?

Si alguna te traba, vuelve al punto donde se explica. Si las tres te salen — felicidades, ya entendiste más de cómo funciona Docker por debajo que el 90% de developers que lo usan diario.

Nos vemos en la Parte 2.


¿Te sirvió este post? Si sientes que algo quedó oscuro, cuéntamelo en comentarios — esta serie se escribe conforme aprendo, y los bloqueos tuyos probablemente son los míos también. $$

meta cover

Retrospectiva del 2022 y planes para el 2023 en Fixtergeek

Checa este otro Post

meta cover

Cómo añadir Cloudflare Turnstile a tus formularios con React Router Framework

Checa este otro Post

¡Nuevo curso!

Animaciones web con React + Motion 🧙🏻