Docker para Principiantes: Contenedores Simplificados

· 12 min de lectura

Tabla de Contenidos

¿Qué es Docker?

Docker es una plataforma que te permite empaquetar aplicaciones y sus dependencias en contenedores ligeros y portátiles. Piensa en un contenedor como una pequeña caja autónoma que contiene todo lo que tu aplicación necesita para ejecutarse: código, tiempo de ejecución, bibliotecas y herramientas del sistema. Se acabaron los problemas de "funciona en mi máquina".

Antes de Docker, desplegar software significaba instalar manualmente dependencias, configurar servidores y esperar que nada entrara en conflicto. Pasabas horas configurando versiones de Python, paquetes de Node.js, controladores de bases de datos y bibliotecas del sistema. Luego lo hacías todo de nuevo en servidores de staging y producción, rezando para que las configuraciones coincidieran.

Docker elimina este caos asegurando que tu aplicación se ejecute de manera idéntica en todas partes: tu portátil, la máquina de un compañero, servidores de staging o producción. El contenedor se convierte en la unidad de despliegue, no solo el código de la aplicación.

Docker se ha convertido en la herramienta estándar para el desarrollo de software moderno. Ya sea que estés construyendo microservicios, configurando pipelines de CI/CD, o simplemente quieras un entorno de desarrollo consistente, Docker lo hace posible con una sobrecarga mínima. Empresas como Netflix, Spotify y PayPal ejecutan millones de contenedores en producción todos los días.

Consejo rápido: Docker no es solo para despliegues en producción. Muchos desarrolladores lo usan para evitar saturar su máquina local con diferentes versiones de lenguajes, bases de datos y herramientas. ¿Necesitas PostgreSQL para un proyecto y MySQL para otro? Ejecuta ambos en contenedores sin conflictos.

Contenedores vs Máquinas Virtuales

Los contenedores y las máquinas virtuales proporcionan aislamiento, pero funcionan de manera muy diferente internamente. Entender esta diferencia es crucial para apreciar por qué los contenedores se han vuelto tan populares.

Las Máquinas Virtuales ejecutan un sistema operativo completo con su propio kernel sobre un hipervisor. Cada VM necesita su propia instalación de SO, consumiendo gigabytes de espacio en disco y memoria significativa. Los tiempos de arranque se miden en minutos. Si ejecutas tres VMs, estás ejecutando tres sistemas operativos completos simultáneamente.

Los Contenedores comparten el kernel del SO anfitrión y solo empaquetan la capa de aplicación. Tienen megabytes de tamaño (no gigabytes), se inician en segundos (no minutos), y puedes ejecutar docenas en una sola máquina sin problemas.

# Enfoque VM: Cada aplicación obtiene un SO completo
App A → SO Invitado → Hipervisor → SO Anfitrión → Hardware
App B → SO Invitado → Hipervisor → SO Anfitrión → Hardware

# Enfoque Contenedor: Las aplicaciones comparten el kernel
App A → Runtime de Contenedor → SO Anfitrión → Hardware
App B → Runtime de Contenedor → SO Anfitrión → Hardware

Esta arquitectura ligera hace que los contenedores sean ideales para microservicios, donde podrías ejecutar cientos de servicios pequeños en lugar de una aplicación monolítica. La eficiencia de recursos es asombrosa: un servidor que ejecuta 10 VMs podría ejecutar cómodamente 100 contenedores.

Característica Máquinas Virtuales Contenedores
Tiempo de Inicio Minutos Segundos
Espacio en Disco Gigabytes (SO completo) Megabytes (solo capa de aplicación)
Rendimiento Casi nativo Nativo (sin sobrecarga de hipervisor)
Aislamiento Completo (kernel separado) Nivel de proceso (kernel compartido)
Portabilidad Limitada (dependiente del hipervisor) Alta (se ejecuta donde Docker se ejecute)
Uso de Recursos Pesado Ligero

Dicho esto, las VMs no están obsoletas. Proporcionan un aislamiento más fuerte ya que cada VM tiene su propio kernel. Para cargas de trabajo críticas de seguridad o cuando necesitas ejecutar diferentes sistemas operativos, las VMs siguen siendo la mejor opción. Muchas organizaciones usan ambas: VMs para aislamiento de infraestructura y contenedores para despliegue de aplicaciones.

Conceptos Básicos de Docker

Docker tiene algunos conceptos clave que necesitas entender antes de sumergirte en comandos y Dockerfiles. Estos bloques de construcción forman la base de cómo funciona Docker.

Imágenes

Una imagen de Docker es una plantilla de solo lectura que contiene el código de tu aplicación, tiempo de ejecución, bibliotecas y dependencias. Piensa en ella como una instantánea o plano. Las imágenes se construyen a partir de instrucciones en un Dockerfile y se almacenan en registros como Docker Hub.

Las imágenes están compuestas de capas. Cada instrucción en un Dockerfile crea una nueva capa. Docker almacena en caché estas capas, así que si reconstruyes una imagen y solo cambió la última capa, Docker reutiliza las capas en caché. Esto hace que las construcciones sean increíblemente rápidas.

Contenedores

Un contenedor es una instancia en ejecución de una imagen. Puedes crear múltiples contenedores desde la misma imagen, y cada uno se ejecuta de forma aislada. Cuando detienes un contenedor, cualquier cambio realizado dentro de él se pierde a menos que lo guardes explícitamente o uses volúmenes.

Los contenedores son efímeros por diseño. Esta inmutabilidad es una característica, no un error: asegura consistencia y hace que el escalado sea trivial.

Dockerfile

Un Dockerfile es un archivo de texto que contiene instrucciones para construir una imagen de Docker. Especifica la imagen base, copia tu código, instala dependencias y define cómo ejecutar tu aplicación. Profundizaremos en los Dockerfiles en una sección posterior.

Registro de Docker

Un registro es un sistema de almacenamiento y distribución para imágenes de Docker. Docker Hub es el registro público predeterminado, alojando millones de imágenes. También puedes ejecutar registros privados para aplicaciones propietarias. Cuando ejecutas docker pull nginx, Docker descarga la imagen nginx desde Docker Hub.

Volúmenes

Los volúmenes son el mecanismo de Docker para persistir datos. Dado que los contenedores son efímeros, cualquier dato escrito dentro de un contenedor desaparece cuando se detiene. Los volúmenes te permiten almacenar datos fuera del sistema de archivos del contenedor, sobreviviendo a reinicios y eliminaciones de contenedores.

Consejo profesional: Usa nuestro Generador de Comandos Docker para crear rápidamente comandos Docker complejos sin memorizar todas las banderas y opciones.

Comandos Esenciales de Docker

Recorramos los comandos de Docker que usarás diariamente. Estos cubren todo el ciclo de vida del contenedor desde descargar imágenes hasta limpiar recursos.

Trabajando con Imágenes

# Descargar una imagen desde Docker Hub
docker pull ubuntu:22.04

# Listar todas las imágenes locales
docker images

# Eliminar una imagen
docker rmi ubuntu:22.04

# Construir una imagen desde un Dockerfile
docker build -t myapp:1.0 .

# Etiquetar una imagen para subirla a un registro
docker tag myapp:1.0 username/myapp:1.0

# Subir una imagen a un registro
docker push username/myapp:1.0

Ejecutando Contenedores

# Ejecutar un contenedor en primer plano
docker run ubuntu:22.04 echo "Hola Docker"

# Ejecutar un contenedor en modo separado (segundo plano)
docker run -d nginx

# Ejecutar con un nombre personalizado
docker run -d --name my-nginx nginx

# Ejecutar con mapeo de puertos (anfitrión:contenedor)
docker run -d -p 8080:80 nginx

# Ejecutar con variables de entorno
docker run -d -e POSTGRES_PASSWORD=secret postgres

# Ejecutar con un montaje de volumen
docker run -d -v /host/path:/container/path nginx

# Ejecutar interactivamente con un shell
docker run -it ubuntu:22.04 /bin/bash

Gestionando Contenedores

# Listar contenedores en ejecución
docker ps

# Listar todos los contenedores (incluyendo detenidos)
docker ps -a

# Detener un contenedor
docker stop my-nginx

# Iniciar un contenedor detenido
docker start my-nginx

# Reiniciar un contenedor
docker restart my-nginx

# Eliminar un contenedor
docker rm my-nginx

# Eliminar un contenedor en ejecución (forzar)
docker rm -f my-nginx

# Ver registros del contenedor
docker logs my-nginx

# Seguir registros en tiempo real
docker logs -f my-nginx

# Ejecutar un comando en un contenedor en ejecución
docker exec my-nginx ls /usr/share/nginx/html

# Abrir un shell en un contenedor en ejecución
docker exec -it my-nginx /bin/bash

# Ver uso de recursos del contenedor
docker stats

# Inspeccionar detalles del contenedor
docker inspect my-nginx

Comandos de Limpieza

# Eliminar todos los contenedores detenidos
docker container prune

# Eliminar imágenes no utilizadas
docker image prune

# Eliminar volúmenes no utilizados
docker volume prune

# Eliminar todo lo no utilizado (contenedores, imágenes, redes, volúmenes)
docker system prune -a

Las banderas -it merecen una mención especial. -i mantiene STDIN abierto (interactivo), y -t asigna un pseudo-TTY (terminal). Juntas, te permiten interactuar con el contenedor como una sesión de shell normal.

Consejo rápido: Usa docker ps -q para obtener solo los IDs de contenedores, lo cual es útil para scripting. Por ejemplo, docker stop $(docker ps -q) detiene todos los contenedores en ejecución.

Escribiendo Dockerfiles

Un Dockerfile es donde ocurre la magia. Es una receta para construir tu imagen de Docker, especificando exactamente qué va en tu contenedor. Desglosemos las instrucciones más importantes y las mejores prácticas.

Estructura Básica de Dockerfile

# Comenzar desde una imagen base
FROM node:18-alpine

# Establecer el directorio de trabajo
WORKDIR /app

# Copiar archivos de paquetes
COPY package*.json ./

# Instalar dependencias
RUN npm install

# Copiar código de la aplicación
COPY . .

# Exponer el puerto en el que se ejecuta tu aplicación
EXPOSE 3000

# Definir el comando para ejecutar tu aplicación
CMD ["node", "server.js"]

Instrucciones Clave de Dockerfile

FROM especifica la imagen base. Siempre usa etiquetas de versión específicas (como node:18-alpine) en lugar de latest para asegurar construcciones reproducibles. Las variantes Alpine son más pequeñas y más seguras.

WORKDIR establece el directorio de trabajo para instrucciones posteriores. Crea el directorio si no existe. Esto es más limpio que usar RUN cd /app.

COPY copia archivos desde tu máquina anfitriona a la imagen. La sintaxis es COPY origen destino. Usa COPY . . para copiar todo, pero ten cuidado con lo que incluyes (usa .dockerignore).

RUN ejecuta comandos durante el proceso de construcción. Cada instrucción RUN crea una nueva capa. Encadena comandos con && para reducir capas:

# Malo: Crea 3 capas
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# Bueno: Crea 1 capa
RUN apt-get update && \
    apt-get install -y curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

EXPOSE documenta en qué puerto escucha tu aplicación. No publica realmente el puerto: eso sucede con docker run -p.

CMD especifica el comando predeterminado para ejecutar cuando el contenedor se inicia. Usa el formato de array JSON (["ejecutable", "param1"]) para evitar problemas de procesamiento de shell.

ENTRYPOINT es similar a CMD pero más difícil de sobrescribir. Úsalo cuando quieras que tu contenedor se comporte como un ejecutable. Puedes combinar ENTRYPOINT y CMD para valores predeterminados flexibles.

Construcciones Multi-Etapa

Las construcciones multi-etapa te permiten usar múltiples declaraciones FROM en un Dockerfile. Esto es increíblemente poderoso para crear imágenes de producción pequeñas mientras mantienes las herramientas de construcción separadas.

# Etapa de construcción
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Etapa de producción
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]

La imagen de producción solo contiene el código compilado y las dependencias de tiempo de ejecución, no las herramientas de construcción. Esto puede reducir el tamaño de la imagen en un 70% o más.

Archivo .dockerignore

Crea un archivo .dockerignore para excluir archivos del contexto de construcción. Esto acelera las construcciones y reduce el tamaño de la imagen.

node_modules
npm-debug.log
.git
.env
*.md
.DS_Store
coverage
.vscode