Docker desde Cero: Guía Definitiva de Comandos, Dockerfile y Volúmenes

Imagina poder ejecutar una aplicación exactamente igual en tu computadora, en la de tu equipo y en un servidor en producción, sin errores inesperados, sin conflictos de versiones y sin perder horas configurando el entorno.
Eso que durante años fue un dolor de cabeza para desarrolladores y empresas… Docker lo resolvió.

Docker es hoy una de las tecnologías más importantes del desarrollo moderno. Ha transformado la manera en que se construyen, prueban y despliegan aplicaciones, permitiendo trabajar con entornos reproducibles, ligeros y altamente portables. Por eso es una herramienta clave tanto para desarrolladores web, ingenieros de software, DevOps y empresas que buscan velocidad, estabilidad y escalabilidad.

En esta guía completa aprenderás Docker desde cero y con profundidad, con explicaciones claras, ejemplos prácticos y buenas prácticas reales. No importa si estás dando tus primeros pasos o si ya usas Docker y quieres dominarlo como un profesional: aquí encontrarás el conocimiento bien organizado y explicado de forma sencilla.

A lo largo del artículo descubrirás:

  • Qué es Docker y cómo funciona realmente
  • Los conceptos fundamentales que necesitas entender sin memorizar de más
  • Cómo instalarlo y usarlo paso a paso
  • Cómo crear tus propias imágenes y contenedores
  • Cómo trabajar con volúmenes, redes y Docker Compose
  • Mejores prácticas usadas en entornos profesionales
  • Casos reales de uso en desarrollo web y producción

Si alguna vez escuchaste frases como “en mi máquina sí funciona”, “el servidor está mal configurado” o “el entorno nos rompió la app”, Docker es la respuesta que estabas buscando.

Vamos a entenderlo, dominarlo y usarlo correctamente.
Empecemos!

Ver índice del contenido

1. ¿Qué es Docker?

Docker es una tecnología que permite crear, empaquetar y ejecutar aplicaciones dentro de contenedores, garantizando que se comporten exactamente igual en cualquier entorno: desarrollo, pruebas o producción.

Su mayor aporte es resolver uno de los problemas más antiguos y frustrantes del desarrollo de software:

“Funciona en mi computadora, pero no en el servidor.”

Con Docker, este problema desaparece. La razón es simple: Docker encapsula la aplicación junto con todas sus dependencias, librerías, configuraciones y versiones necesarias, creando un entorno completamente controlado y reproducible.

De esta forma, la aplicación no depende del sistema donde se ejecuta, sino de su propio contenedor. El resultado es un software más predecible, portable y confiable, que puede moverse sin cambios entre distintos equipos, servidores o servicios en la nube.

1.1 Definición de Docker

Docker es una plataforma de contenedores que permite ejecutar aplicaciones de forma ligera, portátil y consistente.

En términos simples:

  • Docker empaqueta una aplicación
  • Incluye sus dependencias, librerías y configuración
  • La ejecuta en un entorno aislado llamado contenedor

Ese contenedor puede moverse y ejecutarse sin cambios en:

  • Tu laptop
  • La computadora de otro desarrollador
  • Un servidor en la nube
  • Un entorno de producción

Docker no es un lenguaje de programación ni un sistema operativo.
Es una herramienta de virtualización a nivel de aplicación, mucho más eficiente que las soluciones tradicionales.

1.2 ¿Cómo funciona Docker internamente?

Para entender cómo funciona Docker internamente, primero hay que entender cómo funciona un sistema operativo y qué problema intenta resolver Docker.

Tradicionalmente, para aislar aplicaciones se usaban máquinas virtuales, cada una con su propio sistema operativo completo. Esto garantizaba aislamiento, pero a un costo alto en recursos y rendimiento. Docker adopta un enfoque diferente y mucho más eficiente.

Infografía horizontal titulada "Funcionamiento Simplificado de Docker" que ilustra el proceso en 5 pasos: 1) Desarrollador escribiendo un Dockerfile, 2) Construcción de la imagen, 3) Almacenamiento local o en la nube, 4) Ejecución de la imagen, y 5) Despliegue de tres contenedores aislados. En la parte inferior destaca los valores: Rápido, Predecible y Repetible.
Esquema visual del flujo de trabajo en Docker: una ruta rápida y predecible desde el código fuente hasta la ejecución aislada de contenedores.

Aislamiento sin un sistema operativo completo

Docker no crea sistemas operativos virtuales.
En su lugar, aprovecha características nativas del sistema operativo (principalmente de Linux) para aislar aplicaciones a nivel de procesos.

Docker utiliza mecanismos del kernel como:

  • Namespaces → aíslan procesos, red, usuarios y sistema de archivos
  • Cgroups (control groups) → limitan el uso de CPU, memoria y otros recursos
  • Sistema de archivos por capas → permite compartir componentes sin duplicarlos

Gracias a esto, cada contenedor se comporta como si fuera un sistema independiente, aunque en realidad comparte el kernel del sistema operativo anfitrión.

¿Qué significa esto en la práctica?

Cada contenedor Docker:

  • Se ejecuta como un proceso independiente en el sistema
  • Comparte el kernel del sistema operativo
  • Tiene su propio sistema de archivos
  • Tiene su propia red y configuración
  • Está aislado del resto de contenedores

Este enfoque hace que los contenedores:

  • Arranquen en segundos
  • Consuman muy pocos recursos
  • Sean mucho más eficientes que una máquina virtual

Por eso Docker es ligero, rápido y altamente escalable.

Componentes internos clave de Docker

Para que todo esto funcione, Docker se apoya en varios componentes fundamentales.

Docker Engine

Es el corazón de Docker.
Docker Engine es el servicio que se ejecuta en segundo plano y se encarga de:

  • Construir imágenes a partir de Dockerfiles
  • Crear y ejecutar contenedores
  • Gestionar redes y volúmenes
  • Comunicarse con el sistema operativo

Cuando ejecutas un comando como docker run, en realidad estás enviando instrucciones al Docker Engine.

Imágenes Docker

Una imagen Docker es una plantilla inmutable que contiene todo lo necesario para ejecutar una aplicación:

  • Un sistema base mínimo
  • La aplicación
  • Librerías y dependencias
  • Configuración inicial

Las imágenes se construyen en capas, lo que permite:

  • Reutilizar partes comunes
  • Reducir tamaño
  • Acelerar la creación de contenedores

Una imagen no se ejecuta por sí sola; necesita convertirse en un contenedor.

Contenedores

Un contenedor es una imagen en ejecución.

Cuando Docker crea un contenedor:

  • Usa una imagen como base
  • Agrega una capa de escritura
  • Ejecuta el proceso principal definido

Una misma imagen puede generar:

  • Un contenedor
  • Diez contenedores
  • Cientos de contenedores

Todos idénticos y aislados entre sí.

Dockerfile

El Dockerfile es un archivo de texto que describe paso a paso cómo construir una imagen.

En él se define:

  • Qué imagen base usar
  • Qué dependencias instalar
  • Qué archivos copiar
  • Qué comando ejecutar al iniciar el contenedor

Gracias al Dockerfile, la construcción de imágenes es:

  • Automática
  • Reproducible
  • Versionable

Flujo simplificado de funcionamiento de Docker

El funcionamiento general de Docker puede resumirse así:

  1. El desarrollador escribe un Dockerfile
  2. Docker construye una imagen a partir de ese archivo
  3. La imagen se almacena localmente o en un registro
  4. La imagen se ejecuta como uno o varios contenedores
  5. Los contenedores corren de forma aislada y controlada

Todo este proceso es rápido, predecible y repetible.

📌 Idea clave

Docker funciona porque no virtualiza sistemas operativos, sino que aísla aplicaciones usando el propio sistema operativo, logrando una eficiencia que antes no era posible.

1.3 Docker vs Máquinas Virtuales

Esta comparación es clave para entender por qué Docker revolucionó el desarrollo.

Máquinas Virtuales (VM)

  • Incluyen un sistema operativo completo
  • Son pesadas (varios GB)
  • Consumen muchos recursos
  • Arrancan lento
  • Cada VM es un sistema independiente

Docker (Contenedores)

  • Comparten el sistema operativo anfitrión
  • Son livianos (MB)
  • Consumen pocos recursos
  • Arrancan en segundos
  • Ejecutan solo lo necesario

Comparación clara

CaracterísticaDockerMáquina Virtual
TamañoMuy ligeroMuy pesado
ArranqueSegundosMinutos
Consumo de recursosBajoAlto
PortabilidadMuy altaMedia
Ideal paraAplicacionesSistemas completos

Docker no reemplaza a las máquinas virtuales, pero las complementa y, en muchos casos, las supera para desarrollo y despliegue de aplicaciones.

1.4 Ventajas de usar Docker

Docker no se ha convertido en un estándar por casualidad. Su adopción masiva se debe a una serie de ventajas prácticas que resuelven problemas reales en el desarrollo y despliegue de aplicaciones.

a. Portabilidad total:
Una aplicación Dockerizada se ejecuta exactamente igual en cualquier entorno, ya sea en desarrollo, pruebas o producción. Esto elimina las diferencias entre entornos y reduce significativamente los errores que aparecen al mover una aplicación de un sistema a otro.

b. Rapidez y eficiencia:
Docker utiliza contenedores ligeros que arrancan en cuestión de segundos. Al compartir el sistema operativo anfitrión, los contenedores consumen muchos menos recursos que las soluciones tradicionales, permitiendo un uso más eficiente de la CPU y la memoria.

c. Aislamiento de aplicaciones:
Cada aplicación se ejecuta en su propio entorno aislado. Esto significa que sus dependencias, configuraciones y procesos no interfieren con los de otras aplicaciones, haciendo posible ejecutar múltiples servicios en un mismo servidor sin conflictos.

d. Trabajo en equipo simplificado:
Docker facilita el trabajo colaborativo al asegurar que todos los desarrolladores utilicen el mismo entorno. De esta manera se evitan errores de configuración local y conflictos de versiones, y el entorno de ejecución queda claramente definido por el proyecto.

e. Escalabilidad sencilla:
Docker permite escalar aplicaciones de forma simple y controlada. Los contenedores pueden replicarse fácilmente para manejar mayor carga de trabajo, y su integración con herramientas de orquestación como Kubernetes permite administrar aplicaciones distribuidas de manera eficiente.

f. Ideal para DevOps y CI/CD:
Docker encaja de forma natural en flujos de trabajo DevOps. Permite automatizar procesos de integración continua y despliegue continuo, garantizando que el software se pruebe y se despliegue siempre en el mismo entorno, reduciendo errores y acelerando las entregas.

g. Uso profesional y empresarial:
Docker es utilizado por startups, empresas medianas y grandes corporaciones. Su adopción a nivel empresarial lo ha convertido en un estándar de la industria, con un ecosistema maduro, una comunidad amplia y herramientas de soporte profesional.

📌 En resumen, Docker permite desarrollar más rápido, con menos errores y con mayor control sobre los entornos.
Por eso se ha convertido en una herramienta imprescindible del desarrollo moderno.

2. Conceptos Básicos de Docker

Para entender Docker de verdad —y no solo usarlo «porque todos lo usan»— es clave dominar sus conceptos fundamentales. Docker introduce una nueva forma de empaquetar, distribuir y ejecutar aplicaciones, y todo gira alrededor de unas pocas ideas muy bien definidas.

En esta sección vamos a desarmar Docker pieza por pieza, con explicaciones claras y ejemplos mentales simples.

2.1 ¿Qué es un Contenedor (Container)?

Un contenedor es una instancia en ejecución de una aplicación, empaquetada junto con todo lo que necesita para funcionar correctamente.

En términos simples: un contenedor es una aplicación aislada, portable y reproducible.

Dentro de un contenedor viven:

  • La aplicación (por ejemplo, un servidor web)
  • Sus dependencias
  • Sus librerías
  • Su configuración mínima
  • Su propio sistema de archivos

Lo que NO incluye un contenedor: El kernel del sistema operativo. Los contenedores comparten el kernel del host, lo que los hace mucho más ligeros y rápidos que las máquinas virtuales tradicionales.

Todo esto se ejecuta de forma aislada del sistema operativo anfitrión, pero sin necesidad de crear una máquina virtual completa, lo que lo hace increíblemente eficiente.

Una buena forma de imaginarlo es esta: Si el sistema operativo es un edificio, cada contenedor es un departamento independiente. Todos comparten la estructura del edificio (el kernel), pero cada uno tiene su propio espacio, reglas y recursos controlados.

Nota importante: En Windows y macOS, Docker Desktop incluye una máquina virtual ligera que proporciona el kernel Linux necesario, ya que Docker fue diseñado originalmente para Linux. En Linux nativo, los contenedores corren directamente sobre el kernel del sistema.

2.2 ¿Qué es una Imagen (Image)?

Una imagen Docker es una plantilla inmutable que contiene todo lo necesario para crear uno o varios contenedores.

La imagen no se ejecuta por sí sola. Es como un molde o una receta. Cuando Docker «corre» una imagen, lo que hace es crear un contenedor basado en ella.

Una imagen define:

  • Qué sistema base se usa (por ejemplo, Linux Alpine, Ubuntu, Debian)
  • Qué software se instala
  • Qué archivos se copian
  • Qué variables de entorno se configuran
  • Qué comando se ejecuta al iniciar el contenedor

Gracias a esto, cualquier persona puede usar exactamente la misma imagen y obtener el mismo comportamiento, sin importar si está en Windows, Linux o macOS.

2.3 Diferencias entre Imagen y Contenedor

Aunque están muy relacionados, imagen y contenedor no son lo mismo, y confundirlos es uno de los errores más comunes al empezar con Docker.

La imagen es algo estático: no cambia, no se ejecuta y sirve como base.

El contenedor es algo dinámico: es la imagen en ejecución, con estado, procesos activos y cambios temporales.

Dicho de forma directa:

  • Una imagen es el plano
  • Un contenedor es la casa construida y habitada

Desde una sola imagen puedes crear muchos contenedores, todos iguales al inicio, pero con comportamientos independientes mientras están en ejecución.

Sobre el estado de los contenedores: Los cambios que hagas dentro de un contenedor en ejecución son efímeros por defecto. Esto significa que si eliminas el contenedor, esos cambios se pierden. Para mantener datos de forma permanente, necesitas usar volúmenes, que veremos más adelante.

2.4 Capas (Layers) de una Imagen Docker

Las imágenes Docker están construidas por capas, llamadas layers.

Cada capa representa un cambio concreto:

  • Instalar un paquete
  • Copiar un archivo
  • Definir una variable de entorno
  • Ejecutar un comando

Estas capas se apilan unas sobre otras y Docker las reutiliza inteligentemente. Si dos imágenes comparten capas, Docker no las duplica, lo que ahorra espacio y acelera descargas.

Característica importante de las capas: Las capas de una imagen son de solo lectura. Cuando creas un contenedor, Docker añade una capa superior de lectura-escritura donde se guardan los cambios temporales del contenedor en ejecución.

Esta arquitectura por capas tiene dos grandes ventajas:

  1. Eficiencia: menos uso de disco y red
  2. Velocidad: reconstrucciones más rápidas al modificar una imagen

Por eso Docker es tan rápido comparado con soluciones tradicionales de virtualización.

2.5 ¿Qué es un Dockerfile?

Un Dockerfile es un archivo de texto que contiene las instrucciones para construir una imagen Docker.

En él se describe paso a paso:

  • Qué imagen base usar
  • Qué dependencias instalar
  • Qué archivos copiar
  • Qué puertos exponer
  • Qué comando ejecutar al iniciar

Es, básicamente, la receta oficial de la imagen.

Gracias al Dockerfile, el proceso de creación de imágenes es:

  • Automático: se ejecuta con un solo comando
  • Repetible: produce el mismo resultado cada vez
  • Versionable: se puede guardar en Git junto al código

Esto convierte a Docker en una herramienta perfecta para entornos profesionales, CI/CD y trabajo en equipo.

2.6 ¿Qué es un Registro (Registry)?

Un registro Docker es un repositorio donde se almacenan imágenes Docker para poder compartirlas, descargarlas o publicarlas.

Funciona de forma similar a GitHub, pero en lugar de código, guarda imágenes.

Desde un registro puedes:

  • Subir imágenes propias
  • Descargar imágenes de otros desarrolladores
  • Versionar imágenes con etiquetas (tags)
  • Controlar accesos y permisos

Docker, por defecto, se conecta a Docker Hub (docker.io), el registro público oficial, pero también permite configurar y usar registros privados para mayor seguridad y control.

2.7 Docker Hub y Registros Privados

Docker Hub es el registro público oficial de Docker. Allí encontrarás miles de imágenes listas para usar: bases de datos, servidores web, lenguajes de programación y mucho más.

Docker Hub es ideal para:

  • Aprender y experimentar
  • Prototipar rápidamente
  • Proyectos pequeños o públicos
  • Usar imágenes oficiales verificadas

Sin embargo, en entornos empresariales o proyectos sensibles, lo común es usar registros privados, que permiten:

  • Control total sobre las imágenes
  • Mayor seguridad y cumplimiento normativo
  • Integración con sistemas internos de CI/CD
  • Gestión de accesos y permisos granulares

Algunos registros privados populares incluyen:

  • Amazon ECR (Elastic Container Registry)
  • Google Container Registry (GCR) / Artifact Registry
  • Azure Container Registry (ACR)
  • GitHub Container Registry
  • GitLab Container Registry
  • Harbor (solución open-source auto-hospedada)

Estos registros pueden estar en la nube o en servidores propios, manteniendo el mismo flujo de trabajo que Docker Hub.

3. Instalación de Docker

Instalar Docker es el primer paso práctico para empezar a trabajar con contenedores. Aunque Docker funciona en múltiples sistemas operativos, la forma de instalarlo y cómo se ejecuta internamente varía según la plataforma.

En esta sección veremos qué necesitas antes de instalar Docker, cómo hacerlo en Windows, macOS y Linux, cómo verificar que todo funcione correctamente y cuáles son los problemas más comunes que suelen aparecer durante la instalación.

3.1 Requisitos previos

Antes de instalar Docker, es importante entender que Docker depende fuertemente del sistema operativo y de la virtualización.

En términos generales, necesitas:

  • Un sistema operativo moderno y actualizado
  • Arquitectura de 64 bits
  • Soporte de virtualización habilitado en el equipo (para Windows y macOS)
  • Al menos 4 GB de RAM disponible para asignar a Docker (8 GB recomendados para desarrollo)

Sobre la virtualización: En Windows y macOS, Docker se ejecuta mediante Docker Desktop, que incluye una máquina virtual ligera con Linux. Por eso, es indispensable que la virtualización esté activada en el BIOS o UEFI del equipo (Intel VT-x o AMD-V).

En Linux, la situación es diferente: Docker corre directamente sobre el kernel del sistema, por lo que no necesita una máquina virtual adicional. Esto lo hace más eficiente y simple a nivel técnico.

Nota importante sobre licenciamiento: Docker Desktop es gratuito para uso personal, educación y pequeñas empresas. Las organizaciones con más de 250 empleados o ingresos anuales superiores a $10 millones USD requieren una licencia de pago. Docker Engine en Linux es completamente gratuito y open-source.

3.2 Instalación de Docker en Windows

En Windows, la forma oficial y recomendada de instalar Docker es mediante Docker Desktop.

Docker Desktop incluye:

  • Docker Engine (el motor de contenedores)
  • Docker CLI (interfaz de línea de comandos)
  • Docker Compose
  • Una máquina virtual Linux interna
  • Interfaz gráfica para gestionar contenedores e imágenes

Requisitos específicos para Windows:

Para que Docker funcione correctamente en Windows, debes cumplir estas condiciones:

  • Windows 10 de 64 bits: Pro, Enterprise o Education (build 19041 o superior)
  • Windows 11 de 64 bits: Cualquier versión
  • WSL 2 habilitado (recomendado y backend por defecto)
    • Alternativa: Hyper-V y Contenedores de Windows (backend legacy)

Actualmente, Docker Desktop utiliza WSL 2 como backend principal, ya que ofrece mejor rendimiento, compatibilidad y menor consumo de recursos que Hyper-V.

Proceso de instalación:

  1. Descarga el instalador desde el sitio oficial de Docker (https://www.docker.com/products/docker-desktop)
  2. Ejecuta el instalador Docker Desktop Installer.exe
  3. Sigue los pasos del asistente (se recomienda dejar las opciones por defecto)
  4. Durante la instalación, Docker configura automáticamente WSL 2 si no está presente
  5. Reinicia el sistema cuando se te solicite
  6. Inicia Docker Desktop desde el menú de Windows

Una vez instalado, Docker Desktop se ejecuta como una aplicación gráfica desde donde puedes controlar contenedores, imágenes, volúmenes, redes, recursos asignados y configuraciones.

Verificar WSL 2:

Para confirmar que WSL 2 está correctamente instalado y configurado, abre PowerShell y ejecuta:

wsl --list --verbose

Deberías ver una distribución de Linux con VERSION 2.

3.3 Instalación de Docker en macOS

En macOS, Docker también se instala mediante Docker Desktop, de forma muy similar a Windows.

Docker Desktop para macOS funciona creando una máquina virtual Linux ligera, ya que macOS no comparte el kernel Linux necesario para ejecutar contenedores directamente.

Compatibilidad:

Docker es compatible con:

  • Macs con procesadores Intel (x86_64)
  • Macs con Apple Silicon (M1, M2, M3, M4 – arquitectura ARM64)

El instalador detecta automáticamente la arquitectura y descarga la versión adecuada.

Requisitos:

  • macOS 11 (Big Sur) o superior
  • Al menos 4 GB de RAM (8 GB recomendados)

Proceso de instalación:

  1. Descarga el archivo .dmg correcto para tu arquitectura desde https://www.docker.com/products/docker-desktop
    • Docker Desktop for Mac with Intel chip
    • Docker Desktop for Mac with Apple silicon
  2. Abre el archivo .dmg descargado
  3. Arrastra el ícono de Docker a la carpeta Aplicaciones
  4. Abre Docker desde Aplicaciones
  5. Concede los permisos necesarios cuando se te solicite
  6. Espera a que Docker inicie completamente

Una vez iniciado, Docker Desktop se ejecuta en segundo plano y queda accesible desde la barra de menú (ícono de ballena).

Desde allí puedes gestionar recursos como CPU, memoria y disco asignados a Docker, algo especialmente útil en equipos portátiles para optimizar el rendimiento y la duración de la batería.

3.4 Instalación de Docker en Linux

En Linux, Docker se instala de forma nativa y directa sobre el sistema operativo, sin necesidad de Docker Desktop ni máquinas virtuales adicionales (aunque Docker Desktop también está disponible como opción con interfaz gráfica desde 2022).

Opciones de instalación en Linux:

Opción 1: Docker Engine (CLI, recomendado para servidores)

  • Instalación más ligera
  • Sin interfaz gráfica
  • Ideal para servidores y producción
  • Completamente gratuito y open-source

Opción 2: Docker Desktop para Linux

  • Incluye interfaz gráfica
  • Similar a la experiencia en Windows/macOS
  • Disponible para Ubuntu, Debian y Fedora
  • Sujeto a las mismas restricciones de licencia

Distribuciones compatibles con Docker Engine:

  • Ubuntu (20.04 LTS, 22.04 LTS, 24.04 LTS)
  • Debian (10, 11, 12)
  • Fedora (38, 39, 40)
  • CentOS / RHEL (7, 8, 9)
  • Arch Linux
  • openSUSE

Proceso de instalación (Docker Engine en Ubuntu/Debian):

La instalación se realiza mediante el gestor de paquetes de cada distribución. Para Ubuntu/Debian:

# 1. Actualizar el índice de paquetes
sudo apt-get update

# 2. Instalar dependencias necesarias
sudo apt-get install ca-certificates curl gnupg

# 3. Añadir la clave GPG oficial de Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# 4. Configurar el repositorio
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 5. Actualizar el índice de paquetes nuevamente
sudo apt-get update

# 6. Instalar Docker Engine
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Configuración post-instalación en Linux:

Una vez instalado, Docker se ejecuta como un servicio del sistema. Es importante realizar estos pasos adicionales:

1. Iniciar y habilitar el servicio Docker:

# Iniciar Docker
sudo systemctl start docker

# Habilitar Docker para que inicie automáticamente al arrancar el sistema
sudo systemctl enable docker

# Verificar el estado
sudo systemctl status docker

2. Configurar permisos de usuario (opcional pero recomendado):

Por defecto, Docker requiere privilegios de administrador. Para usar Docker sin sudo:

# Agregar tu usuario al grupo docker
sudo usermod -aG docker $USER

# Aplicar los cambios (o cerrar sesión y volver a iniciar)
newgrp docker

Nota de seguridad: Agregar un usuario al grupo docker otorga privilegios equivalentes a root. Solo hazlo en entornos de desarrollo o con usuarios de confianza.

Esta instalación es la más eficiente y la preferida en servidores y entornos de producción.

3.5 Verificar la instalación

Después de instalar Docker, es fundamental comprobar que todo funcione correctamente.

Verificar la versión instalada:

docker --version

Esto mostrará algo como: Docker version 24.0.7, build afdd53b

Para información más detallada:

docker version

Esto muestra información tanto del cliente como del servidor Docker.

Ejecutar el contenedor de prueba:

La forma más simple y universal de verificar que Docker funciona completamente es ejecutar el contenedor de prueba oficial:

docker run hello-world

Cuando ejecutas este comando, Docker:

  1. Busca la imagen hello-world localmente
  2. Si no la encuentra, la descarga automáticamente desde Docker Hub
  3. Crea un contenedor basado en esa imagen
  4. Ejecuta el contenedor
  5. Muestra un mensaje confirmando que Docker funciona correctamente
  6. El contenedor se detiene automáticamente

Salida esperada:

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.
...

Si ves este mensaje, significa que Docker está instalado, configurado y funcionando como se espera.

Verificar que Docker Compose está disponible:

docker compose version

Esto confirmará que Docker Compose (incluido en Docker Desktop y en las instalaciones modernas de Docker Engine) está funcionando.

3.6 Problemas comunes de instalación

Aunque Docker suele instalarse sin mayores inconvenientes, existen algunos problemas frecuentes, especialmente en equipos personales.

Problema 1: Virtualización deshabilitada (Windows/macOS)

Síntoma: Docker Desktop no inicia o muestra un error sobre virtualización.

Causa: La virtualización (Intel VT-x o AMD-V) está deshabilitada en el BIOS/UEFI.

Solución:

  1. Reinicia el equipo y entra al BIOS/UEFI (generalmente F2, F10, DEL o ESC)
  2. Busca opciones como «Virtualization Technology», «Intel VT-x», «AMD-V» o «SVM Mode»
  3. Habilita la opción
  4. Guarda los cambios y reinicia

Problema 2: WSL 2 no configurado correctamente (Windows)

Síntoma: Error al iniciar Docker Desktop sobre WSL 2.

Causa: WSL 2 no está instalado, no está actualizado o no está configurado como versión predeterminada.

Solución:

# Instalar WSL 2
wsl --install

# Actualizar WSL a la última versión
wsl --update

# Establecer WSL 2 como versión predeterminada
wsl --set-default-version 2

Reinicia el equipo después de estos comandos.

Problema 3: Permisos insuficientes (Linux)

Síntoma: Error «permission denied» al ejecutar comandos Docker.

Causa: El usuario no pertenece al grupo docker.

Solución:

# Agregar usuario al grupo docker
sudo usermod -aG docker $USER

# Aplicar cambios
newgrp docker

# O cerrar sesión y volver a iniciar

Problema 4: Conflicto de puertos

Síntoma: Contenedores no pueden iniciar o muestran errores de «port already in use».

Causa: Servicios existentes (Apache, MySQL, PostgreSQL, etc.) están usando los puertos que el contenedor intenta utilizar.

Solución:

  • Detén los servicios conflictivos
  • O modifica el mapeo de puertos del contenedor (veremos esto más adelante)

Ejemplo: si MySQL local usa el puerto 3306, mapea el contenedor a otro puerto:

docker run -p 3307:3306 mysql

Problema 5: Versiones antiguas de Docker

Síntoma: Comandos o funcionalidades nuevas no funcionan.

Causa: Docker desactualizado o instalación previa corrupta.

Solución:

  • Desinstala completamente la versión antigua
  • Elimina archivos de configuración residuales
  • Instala la versión más reciente desde el sitio oficial

Problema 6: Software de virtualización incompatible

Síntoma: Docker Desktop no inicia o entra en conflicto con otro software.

Causa: VirtualBox, VMware u otro software de virtualización puede entrar en conflicto con Hyper-V o WSL 2.

Solución:

  • Usa WSL 2 como backend (generalmente compatible)
  • O desactiva temporalmente el otro software de virtualización

Obtener ayuda adicional

La buena noticia es que Docker proporciona mensajes de error bastante claros, y la mayoría de estos problemas tienen solución rápida una vez identificada la causa.

Si encuentras un problema no listado aquí, consulta:

  • La documentación oficial de Docker (https://docs.docker.com)
  • Los foros de la comunidad Docker
  • Stack Overflow con la etiqueta [docker]
  • Los logs de Docker Desktop (disponibles en el menú de configuración)

4. Comandos Básicos de Docker

Una vez que Docker está instalado, el siguiente paso es aprender a interactuar con él. Docker se controla principalmente desde la línea de comandos (CLI), y aunque al inicio puede parecer intimidante, en realidad el flujo de trabajo se basa en unos pocos comandos fundamentales.

Dominar estos comandos te permitirá ejecutar contenedores, gestionar imágenes, depurar aplicaciones y controlar todo el ciclo de vida de tus servicios Docker.

Tabla de referencia rápida

Antes de profundizar en cada comando, aquí tienes una referencia rápida de los comandos más usados:

ComandoDescripción
docker --versionMuestra la versión de Docker
docker imagesLista todas las imágenes locales
docker pull <imagen>Descarga una imagen desde Docker Hub
docker run <imagen>Crea y ejecuta un contenedor
docker psLista contenedores en ejecución
docker ps -aLista todos los contenedores
docker stop <contenedor>Detiene un contenedor de forma controlada
docker start <contenedor>Inicia un contenedor detenido
docker restart <contenedor>Reinicia un contenedor
docker rm <contenedor>Elimina un contenedor
docker rmi <imagen>Elimina una imagen
docker logs <contenedor>Muestra los logs de un contenedor
docker exec -it <contenedor> <comando>Ejecuta un comando dentro de un contenedor
docker build -t <nombre> .Construye una imagen desde un Dockerfile
docker inspect <contenedor/imagen>Muestra información detallada

4.1 Verificar la versión de Docker

El primer comando que todo usuario de Docker debería conocer sirve para confirmar que Docker está correctamente instalado y accesible desde la terminal.

docker --version

Salida esperada:

Docker version 24.0.7, build afdd53b

Este comando devuelve la versión instalada del cliente Docker. Si obtienes una respuesta con el número de versión, significa que Docker está disponible y funcionando a nivel básico.

Para una verificación más completa que muestre información tanto del cliente como del servidor (Docker Engine):

docker version

Salida esperada:

Client: Docker Engine - Community
 Version:           24.0.7
 API version:       1.43
 Go version:        go1.20.10
 ...

Server: Docker Engine - Community
 Engine:
  Version:          24.0.7
  API version:      1.43 (minimum version 1.12)
  ...

Esto confirma que ambos componentes pueden comunicarse correctamente.

4.2 Listar imágenes disponibles

Antes de ejecutar contenedores, es útil saber qué imágenes tienes disponibles localmente.

docker images

O su forma más moderna y explícita:

docker image ls

Salida esperada:

REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
nginx         latest    605c77e624dd   2 weeks ago    141MB
node          20        e6f4f87f1032   3 weeks ago    910MB
hello-world   latest    feb5d9fea6a5   2 months ago   13.3kB

Este comando muestra:

  • REPOSITORY: Nombre de la imagen
  • TAG: Versión o etiqueta de la imagen (por defecto latest)
  • IMAGE ID: Identificador único de la imagen
  • CREATED: Cuándo fue creada la imagen
  • SIZE: Tamaño de la imagen

4.3 Descargar imágenes desde Docker Hub

Docker Hub es el registro público por defecto de Docker. Para descargar una imagen sin ejecutarla inmediatamente, se utiliza el comando docker pull.

docker pull nginx

Esto descarga la imagen oficial de Nginx (con la etiqueta latest por defecto) y la deja disponible localmente para futuros contenedores.

También puedes descargar versiones específicas usando etiquetas:

docker pull node:20
docker pull postgres:15-alpine
docker pull python:3.11-slim

Esto es fundamental para garantizar consistencia entre entornos y evitar cambios inesperados por actualizaciones automáticas.

Descargar una imagen específica de una plataforma:

Si trabajas con Apple Silicon (M1/M2/M3) o necesitas una arquitectura específica:

docker pull --platform linux/amd64 nginx

4.4 Ejecutar un contenedor

Ejecutar un contenedor es una de las acciones más comunes en Docker. El comando principal para hacerlo es docker run.

Ejemplo básico:

docker run hello-world

Este comando realiza varias acciones automáticamente:

  1. Busca la imagen hello-world localmente
  2. Si no existe, la descarga desde Docker Hub
  3. Crea un contenedor basado en la imagen
  4. Ejecuta el contenedor
  5. Muestra su salida
  6. El contenedor se detiene automáticamente

Ejecutar un servidor web en segundo plano:

En aplicaciones reales, normalmente ejecutarás contenedores que se mantienen activos:

docker run -d -p 8080:80 nginx

Explicación de los flags:

  • -d (detached): Ejecuta el contenedor en segundo plano y devuelve el control de la terminal
  • -p 8080:80 (port): Mapea el puerto 80 del contenedor al puerto 8080 del host
    • Formato: -p [puerto_host]:[puerto_contenedor]

Ahora puedes acceder a Nginx desde tu navegador en http://localhost:8080

Opciones adicionales útiles:

docker run -d \
  --name mi-nginx \
  -p 8080:80 \
  -v /ruta/local:/usr/share/nginx/html \
  nginx

Flags explicados:

  • --name mi-nginx: Asigna un nombre personalizado al contenedor (facilita su gestión)
  • -v (volume): Monta un directorio local dentro del contenedor
  • -e: Define variables de entorno (ejemplo: -e MYSQL_ROOT_PASSWORD=secret)
  • --rm: Elimina automáticamente el contenedor cuando se detiene
  • -it: Modo interactivo con terminal (útil para shells)

Ejemplo con variables de entorno:

docker run -d \
  --name mi-mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=mipassword \
  -e MYSQL_DATABASE=miapp \
  mysql:8

4.5 Listar contenedores

A medida que trabajas con Docker, crearás múltiples contenedores. Para ver cuáles están activos:

docker ps

Salida esperada:

CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                  NAMES
a1b2c3d4e5f6   nginx     "/docker-entrypoint.…"   5 minutes ago   Up 5 minutes   0.0.0.0:8080->80/tcp   mi-nginx

Este comando muestra únicamente los contenedores que están en ejecución.

Para ver todos los contenedores, incluidos los detenidos:

docker ps -a

Esto muestra también contenedores con estado Exited, útil cuando necesitas limpiar contenedores antiguos o revisar contenedores que fallaron al iniciar.

Mostrar solo los IDs de los contenedores:

docker ps -q
docker ps -aq  # Todos los IDs, incluidos detenidos

Esto es especialmente útil para scripts y comandos en batch.

4.6 Gestionar el ciclo de vida de contenedores

Detener un contenedor

Cuando un contenedor está en ejecución, puedes detenerlo de forma controlada usando docker stop:

docker stop mi-nginx
# O usando el ID del contenedor
docker stop a1b2c3d4e5f6

Docker envía una señal SIGTERM al proceso principal del contenedor, permitiendo que la aplicación se cierre correctamente (guarde estado, cierre conexiones, etc.). Si después de 10 segundos el contenedor no se ha detenido, Docker envía un SIGKILL forzado.

Para forzar la detención inmediata:

docker kill mi-nginx

Esto envía directamente SIGKILL, terminando el contenedor sin darle tiempo de limpieza. Úsalo solo cuando docker stop no funcione.

Iniciar un contenedor detenido

Si un contenedor fue detenido pero no eliminado, puedes reiniciarlo:

docker start mi-nginx

Esto mantiene todas las configuraciones, volúmenes y estado del contenedor.

Reiniciar un contenedor

Para detener y volver a iniciar un contenedor:

docker restart mi-nginx

Útil cuando necesitas aplicar cambios de configuración o resolver problemas temporales.

4.7 Eliminar contenedores

Una vez que un contenedor ya no es necesario, puedes eliminarlo para liberar recursos.

docker rm mi-nginx

Si el contenedor está en ejecución, primero debe detenerse, o usar el flag -f (force):

docker rm -f mi-nginx

Eliminar varios contenedores a la vez:

docker rm contenedor1 contenedor2 contenedor3

Eliminar todos los contenedores detenidos:

docker container prune

Docker te pedirá confirmación antes de eliminarlos. Para omitir la confirmación:

docker container prune -f

Eliminar automáticamente un contenedor al detenerse:

Cuando ejecutas un contenedor con el flag --rm, Docker lo elimina automáticamente cuando se detiene:

docker run --rm -it ubuntu bash

Esto es útil para contenedores de prueba o temporales.

4.8 Construir una imagen desde un Dockerfile

Cuando desarrollas tu propia aplicación, lo habitual es crear una imagen personalizada a partir de un Dockerfile.

El comando para construir una imagen es:

docker build -t mi-aplicacion:1.0 .

Explicación:

  • -t mi-aplicacion:1.0 (tag): Asigna un nombre y etiqueta a la imagen
    • Formato: nombre:version
    • Si omites la versión, se usa latest por defecto
  • . (punto): Indica el contexto de construcción, es decir, el directorio donde está el Dockerfile y los archivos necesarios

Docker lee el Dockerfile ubicado en el directorio actual, ejecuta cada instrucción línea por línea y genera una imagen lista para ser utilizada.

Especificar un Dockerfile con otro nombre o ubicación:

docker build -t mi-app -f Dockerfile.prod .

Construir sin usar caché (útil para forzar reconstrucción completa):

docker build --no-cache -t mi-app .

Una vez construida, puedes ejecutar un contenedor basado en esa imagen:

docker run -d -p 3000:3000 mi-aplicacion:1.0

Este flujo es la base de Docker en proyectos profesionales.

4.9 Gestionar imágenes

Ver información detallada de una imagen

docker inspect nginx

Esto muestra toda la metadata de la imagen en formato JSON: capas, variables de entorno, puertos expuestos, comandos, etc.

Eliminar imágenes

docker rmi nginx
# O usando el IMAGE ID
docker rmi 605c77e624dd

No puedes eliminar una imagen si hay contenedores (aunque estén detenidos) basados en ella. Primero debes eliminar los contenedores.

Forzar eliminación:

docker rmi -f nginx

Eliminar todas las imágenes sin usar:

docker image prune

Eliminar todas las imágenes (cuidado, esto elimina TODAS):

docker image prune -a

4.10 Ver logs de un contenedor

Cuando una aplicación dentro de un contenedor falla o se comporta de forma inesperada, los logs son la primera herramienta de diagnóstico.

docker logs mi-nginx

Este comando muestra la salida estándar (stdout) y errores (stderr) del contenedor, equivalente a lo que verías en la consola si la aplicación se ejecutara directamente.

Seguir los logs en tiempo real (como tail -f):

docker logs -f mi-nginx

Esto es especialmente útil para servidores, APIs y aplicaciones en ejecución continua. Presiona Ctrl+C para salir.

Mostrar solo las últimas N líneas:

docker logs --tail 50 mi-nginx

Mostrar logs con timestamps:

docker logs -t mi-nginx

Combinación útil (últimas 100 líneas en tiempo real con timestamps):

docker logs -f --tail 100 -t mi-nginx

4.11 Ejecutar comandos dentro de un contenedor

En ocasiones necesitas acceder al interior de un contenedor para inspeccionar archivos, ejecutar comandos o depurar un problema.

Abrir una terminal interactiva

docker exec -it mi-nginx bash

Explicación:

  • exec: Ejecuta un comando en un contenedor en ejecución
  • -i (interactive): Mantiene STDIN abierto
  • -t (tty): Asigna una pseudo-terminal
  • bash: El comando a ejecutar (en este caso, una shell)

En imágenes basadas en Alpine Linux u otras ligeras, usa sh en lugar de bash:

docker exec -it mi-contenedor sh

Nota importante: Algunos contenedores minimalistas (como scratch o contenedores con solo el binario de la aplicación) no tienen ningún shell instalado, por lo que no podrás acceder de esta manera.

Ejecutar un comando sin entrar al contenedor

docker exec mi-nginx ls -la /usr/share/nginx/html
docker exec mi-nginx cat /etc/nginx/nginx.conf

Ver procesos en ejecución dentro del contenedor

docker top mi-nginx

Obtener estadísticas en tiempo real

docker stats mi-nginx

Muestra uso de CPU, memoria, red y disco del contenedor en tiempo real.

4.12 Inspeccionar contenedores e imágenes

Para obtener información técnica detallada:

docker inspect mi-nginx

Esto devuelve un JSON con toda la configuración: red, volúmenes, variables de entorno, estado, etc.

Filtrar información específica con formato:

docker inspect --format='{{.NetworkSettings.IPAddress}}' mi-nginx

4.13 Copiar archivos entre host y contenedor

Copiar desde el host al contenedor:

docker cp archivo.txt mi-nginx:/usr/share/nginx/html/

Copiar desde el contenedor al host:

docker cp mi-nginx:/var/log/nginx/access.log ./logs/

4.14 Limpieza general del sistema Docker

Con el tiempo, Docker acumula imágenes, contenedores, volúmenes y redes sin usar que ocupan espacio.

Limpiar todo lo no utilizado (contenedores detenidos, redes sin usar, imágenes dangling, caché de build):

docker system prune

Limpieza agresiva (incluye todas las imágenes sin contenedores asociados):

docker system prune -a

Ver cuánto espacio está usando Docker:

docker system df

Resumen práctico

Con estos comandos ya puedes:

✅ Descargar y gestionar imágenes
✅ Ejecutar, detener y reiniciar contenedores
✅ Asignar puertos y variables de entorno
✅ Ver logs y depurar aplicaciones
✅ Entrar a contenedores en ejecución
✅ Construir imágenes personalizadas
✅ Limpiar recursos no utilizados
✅ Inspeccionar y monitorear el estado del sistema Docker

Dominar estos comandos te da el ochenta por ciento del poder práctico de Docker en el día a día. A partir de aquí, el siguiente paso es aprender a crear tus propias imágenes con Dockerfiles, trabajar con volúmenes persistentes y orquestar múltiples contenedores con Docker Compose.

5. Dockerfile: Estructura y Buenas Prácticas

El Dockerfile es el corazón de cualquier proyecto Docker serio. Si los contenedores son lo que se ejecuta y las imágenes lo que se distribuye, el Dockerfile es el documento que define exactamente cómo se construye todo.

Un buen Dockerfile hace que una aplicación sea reproducible, segura, eficiente y fácil de mantener. Un mal Dockerfile, en cambio, genera imágenes pesadas, lentas, inseguras y difíciles de depurar.

En esta sección aprenderás cómo funciona un Dockerfile, cuáles son sus instrucciones principales y qué buenas prácticas seguir para crear imágenes profesionales.

5.1 ¿Qué es un Dockerfile?

Un Dockerfile es un archivo de texto plano que contiene una serie de instrucciones que Docker ejecuta de forma secuencial para construir una imagen.

Cada línea del Dockerfile describe un paso concreto del proceso de construcción: qué sistema base usar, qué dependencias instalar, qué archivos copiar y cómo debe iniciarse la aplicación.

Gracias al Dockerfile:

  • La construcción de imágenes es automática
  • El resultado es siempre el mismo
  • El proceso puede versionarse junto al código
  • El entorno queda documentado de forma explícita

En otras palabras, el Dockerfile elimina la improvisación y convierte el entorno en parte del proyecto.

5.2 Estructura básica de un Dockerfile

Un Dockerfile sigue una estructura lógica y predecible. Aunque puede variar según el proyecto, la mayoría incluye estos bloques:

  1. Argumentos de construcción (opcional)
  2. Imagen base
  3. Metadata (opcional)
  4. Instalación de dependencias del sistema
  5. Configuración del directorio de trabajo
  6. Copia de archivos de dependencias
  7. Instalación de dependencias de la aplicación
  8. Copia del código fuente
  9. Configuración del entorno
  10. Exposición de puertos
  11. Usuario de ejecución (recomendado)
  12. Health check (opcional)
  13. Comando de arranque

Docker lee el Dockerfile de arriba hacia abajo, ejecutando cada instrucción y generando una nueva capa de imagen en cada paso.

El orden de las instrucciones es crucial tanto para el funcionamiento como para aprovechar eficientemente la caché de Docker y reducir tiempos de construcción.

5.3 Tabla de referencia de instrucciones

InstrucciónDescripciónEjemplo
FROMDefine la imagen baseFROM node:20-alpine
ARGDefine argumentos de construcciónARG NODE_VERSION=20
LABELAñade metadata a la imagenLABEL maintainer="dev@example.com"
WORKDIREstablece el directorio de trabajoWORKDIR /app
COPYCopia archivos al contenedorCOPY package.json .
ADDCopia archivos con funciones extrasADD archivo.tar.gz /app/
RUNEjecuta comandos durante la construcciónRUN npm install
ENVDefine variables de entornoENV NODE_ENV=production
EXPOSEDocumenta puertos expuestosEXPOSE 3000
USERCambia el usuario de ejecuciónUSER node
VOLUMEDefine puntos de montajeVOLUME /data
HEALTHCHECKDefine verificación de saludHEALTHCHECK CMD curl -f http://localhost/
CMDComando por defecto (sobrescribible)CMD ["node", "app.js"]
ENTRYPOINTComando principal (no sobrescribible)ENTRYPOINT ["node"]

5.4 Instrucciones principales del Dockerfile

5.4.1 FROM – Imagen base

Define la imagen base sobre la que se construirá la nueva imagen.

FROM node:20-alpine

Debe ser la primera instrucción del Dockerfile (excepto si hay ARG que se use en FROM).

Elegir una buena imagen base es crucial para el tamaño, seguridad y rendimiento final.

Buenas prácticas:

✅ Preferir imágenes oficiales verificadas
✅ Usar versiones específicas (no latest)
✅ Elegir variantes ligeras como alpine o slim cuando sea posible
✅ Considerar imágenes distroless para máxima seguridad

Ejemplos:

# Malo: versión imprecisa
FROM node

# Mejor: versión específica
FROM node:20

# Óptimo: versión específica y ligera
FROM node:20-alpine

# Para Python
FROM python:3.11-slim

# Para Go (multi-stage)
FROM golang:1.21-alpine AS builder

5.4.2 ARG – Argumentos de construcción

Define variables que solo existen durante la construcción de la imagen.

ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine

ARG APP_VERSION=1.0.0
LABEL version="${APP_VERSION}"

Diferencia con ENV:

  • ARG: solo disponible durante el build
  • ENV: disponible durante el build Y en el contenedor en ejecución

Pasar argumentos al construir:

docker build --build-arg NODE_VERSION=18 -t mi-app .

5.4.3 LABEL – Metadata

Añade información descriptiva a la imagen.

LABEL maintainer="desarrollador@ejemplo.com"
LABEL version="1.0.0"
LABEL description="API REST para gestión de usuarios"

Útil para documentación, automatización y organización de imágenes.

5.4.4 WORKDIR – Directorio de trabajo

Establece el directorio de trabajo para las instrucciones siguientes.

WORKDIR /app

Ventajas sobre RUN cd /app:

  • Crea el directorio si no existe
  • Más limpio y explícito
  • Afecta a todas las instrucciones siguientes

Buena práctica: Usar rutas absolutas y evitar /root como directorio de trabajo.

WORKDIR /app
COPY package.json .
RUN npm install
COPY . .

5.4.5 COPY vs ADD – Copiar archivos

COPY copia archivos o directorios desde el host al contenedor.

COPY package.json /app/
COPY src/ /app/src/
COPY . .

ADD tiene las mismas capacidades que COPY, pero con funcionalidades adicionales:

  • Descomprime automáticamente archivos .tar, .tar.gz, etc.
  • Puede descargar archivos desde URLs
ADD archivo.tar.gz /app/
ADD https://ejemplo.com/archivo.txt /app/

Buena práctica: Usar COPY siempre que sea posible y reservar ADD solo cuando necesites explícitamente sus funcionalidades extras. COPY es más predecible.

Copiado eficiente para aprovechar caché:

# ❌ Malo: copia todo de una vez
COPY . .
RUN npm install

# ✅ Bueno: copia dependencias primero
COPY package*.json ./
RUN npm install
COPY . .

5.4.6 RUN – Ejecutar comandos

Ejecuta comandos durante la construcción de la imagen.

RUN npm install
RUN apt-get update && apt-get install -y curl

Cada RUN crea una nueva capa en la imagen, por lo que conviene agrupar comandos relacionados para reducir el número de capas y el tamaño final.

❌ Malo: múltiples capas innecesarias

RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN rm -rf /var/lib/apt/lists/*

✅ Bueno: agrupado en una sola capa

RUN apt-get update && \
    apt-get install -y \
        curl \
        git \
    && rm -rf /var/lib/apt/lists/*

Explicación:

  • && encadena comandos (solo ejecuta el siguiente si el anterior tuvo éxito)
  • \ continúa la instrucción en la siguiente línea
  • Limpiar caché de paquetes en la misma capa reduce el tamaño

Buenas prácticas:

✅ Ordenar paquetes alfabéticamente para mejor legibilidad
✅ Limpiar cachés y archivos temporales en la misma capa
✅ Usar --no-cache o equivalente al instalar paquetes
✅ Verificar firma de paquetes cuando sea crítico

5.4.7 ENV – Variables de entorno

Define variables de entorno dentro del contenedor.

ENV NODE_ENV=production
ENV PORT=3000
ENV DATABASE_URL=postgres://localhost:5432/mydb

Las variables persisten en el contenedor en ejecución, a diferencia de ARG.

Pueden sobrescribirse al ejecutar el contenedor:

docker run -e NODE_ENV=development mi-app

Buena práctica: Usar variables de entorno para configuración que puede cambiar entre entornos (desarrollo, staging, producción).

5.4.8 EXPOSE – Documentar puertos

Documenta qué puerto utiliza la aplicación dentro del contenedor.

EXPOSE 3000
EXPOSE 8080

Importante: EXPOSE NO abre el puerto automáticamente. Solo sirve como documentación y para herramientas de orquestación.

Para realmente exponer el puerto, usa -p al ejecutar:

docker run -p 3000:3000 mi-app

5.4.9 USER – Usuario de ejecución

Cambia el usuario con el que se ejecutan las instrucciones siguientes y el contenedor.

# Crear usuario no privilegiado
RUN addgroup -g 1001 appgroup && \
    adduser -D -u 1001 -G appgroup appuser

# Cambiar propiedad de archivos
RUN chown -R appuser:appgroup /app

# Cambiar al usuario no root
USER appuser

⚠️ Importante para seguridad: Por defecto, los contenedores se ejecutan como root. Esto es un riesgo de seguridad significativo.

Buena práctica: Siempre cambiar a un usuario no privilegiado antes de ejecutar la aplicación.

Muchas imágenes oficiales ya incluyen usuarios no root:

# Node.js incluye el usuario 'node'
FROM node:20-alpine
USER node

5.4.10 VOLUME – Puntos de montaje

Define directorios que deben persistir o compartirse.

VOLUME /data
VOLUME /var/log/app

Los volúmenes permiten que los datos persistan incluso cuando el contenedor se elimina.

Generalmente, es mejor definir volúmenes al ejecutar el contenedor:

docker run -v /ruta/host:/data mi-app

5.4.11 HEALTHCHECK – Verificación de salud

Define un comando para verificar si el contenedor está saludable.

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

Parámetros:

  • --interval: Cada cuánto ejecutar el check
  • --timeout: Tiempo máximo de espera
  • --start-period: Tiempo de gracia al iniciar
  • --retries: Intentos antes de marcar como unhealthy

Docker usa esta información para monitorear el estado del contenedor y puede reiniciarlo automáticamente si falla.

5.4.12 CMD – Comando por defecto

Define el comando por defecto que se ejecuta cuando el contenedor se inicia.

CMD ["node", "app.js"]

Formatos:

# Formato exec (recomendado)
CMD ["executable", "param1", "param2"]

# Formato shell
CMD executable param1 param2

CMD puede ser sobrescrito al ejecutar el contenedor:

docker run mi-app node debug.js

5.4.13 ENTRYPOINT – Comando principal

Define el comando principal e inmutable del contenedor.

ENTRYPOINT ["node"]

A diferencia de CMD, ENTRYPOINT no se sobrescribe fácilmente. Se usa cuando el contenedor debe comportarse como un ejecutable específico.

Combinación de ENTRYPOINT y CMD:

Una práctica común es usar ENTRYPOINT para el comando base y CMD para los argumentos por defecto:

ENTRYPOINT ["node"]
CMD ["app.js"]

Esto permite ejecutar:

# Ejecuta: node app.js (por defecto)
docker run mi-app

# Ejecuta: node debug.js (sobrescribe CMD)
docker run mi-app debug.js

# Ejecuta: node --inspect app.js (añade flags)
docker run mi-app --inspect app.js

Cuándo usar cada uno:

SituaciónUsar
Aplicación flexible que puede cambiarCMD
Contenedor que actúa como herramienta/comando fijoENTRYPOINT
Comando base fijo + argumentos variablesENTRYPOINT + CMD

5.5 Archivo .dockerignore

El archivo .dockerignore funciona de manera similar a .gitignore. Indica qué archivos no deben copiarse al contexto de construcción.

Esto es fundamental para:

✅ Reducir el tamaño de la imagen
✅ Acelerar la construcción
✅ Evitar copiar archivos sensibles
✅ Prevenir invalidación innecesaria de caché

Ejemplo completo de .dockerignore:

# Dependencias
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Archivos de build
dist/
build/
*.pyc
__pycache__/

# Control de versiones
.git/
.gitignore
.gitattributes

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~

# Sistema operativo
.DS_Store
Thumbs.db

# Docker
Dockerfile
.dockerignore
docker-compose*.yml

# Archivos sensibles
.env
.env.local
*.key
*.pem
secrets/

# Documentación
README.md
docs/
*.md

# Tests
tests/
test/
*.test.js
*.spec.js
coverage/

# CI/CD
.github/
.gitlab-ci.yml
.travis.yml

# Logs
logs/
*.log

⚠️ Usar .dockerignore NO es opcional en proyectos reales: es una buena práctica esencial.

5.6 Multi-stage builds (construcciones en varias etapas)

Las multi-stage builds permiten usar varias imágenes base dentro de un mismo Dockerfile.

La idea es simple: usar una imagen completa para construir la aplicación y una imagen minimalista para ejecutarla.

Ventajas:

✅ Reduce drásticamente el tamaño final de la imagen
✅ Mejora la seguridad (menos paquetes = menos vulnerabilidades)
✅ Separa herramientas de build de runtime
✅ Imágenes de producción más limpias

Sintaxis:

# Etapa 1: Construcción (nombrada con AS)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Etapa 2: Producción (imagen final)
FROM node:20-alpine
WORKDIR /app
# Copiar solo lo necesario desde la etapa anterior
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm install --omit=dev
CMD ["node", "dist/app.js"]

Explicación:

  1. Primera etapa (AS builder): Usa todas las herramientas necesarias para construir (compiladores, build tools, dependencias de desarrollo)
  2. Segunda etapa: Parte de una imagen limpia y copia SOLO los artefactos construidos
  3. COPY --from=builder: Copia archivos desde una etapa anterior

Comparación de tamaños:

  • Sin multi-stage: 1.2 GB (incluye node_modules completo, código fuente, herramientas de build)
  • Con multi-stage: 150 MB (solo runtime y archivos compilados)

Esta técnica es estándar en aplicaciones Node.js, Go, Java, React y Angular.

5.7 El sistema de caché de Docker

Docker cachea cada capa de la imagen. Si una instrucción no ha cambiado, Docker reutiliza la capa cacheada en lugar de ejecutarla nuevamente.

Cómo funciona:

  1. Docker ejecuta cada instrucción
  2. Compara el resultado con capas cacheadas previas
  3. Si coincide, usa la caché (muy rápido)
  4. Si cambia, ejecuta la instrucción y invalida la caché de todas las capas siguientes

Por eso el orden importa:

❌ Malo: invalida caché con cada cambio de código

COPY . .
RUN npm install

Si cambias cualquier archivo, se invalida COPY . . y también RUN npm install, reinstalando todas las dependencias.

✅ Bueno: aprovecha caché al máximo

COPY package*.json ./
RUN npm install
COPY . .

Si cambias código pero no package.json, Docker reutiliza la capa de npm install.

Principio general: Copiar primero lo que cambia menos frecuentemente.

5.8 Buenas prácticas esenciales

1. Usar imágenes base oficiales y específicas

# ❌ Malo
FROM node

# ✅ Bueno
FROM node:20-alpine

2. Minimizar el número de capas

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

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

3. Ordenar instrucciones de menos a más cambiante

FROM node:20-alpine
WORKDIR /app

# 1. Dependencias (cambian poco)
COPY package*.json ./
RUN npm install

# 2. Código fuente (cambia frecuentemente)
COPY . .

CMD ["node", "app.js"]

4. No ejecutar como root

USER node

5. Usar .dockerignore

Siempre crear y mantener actualizado el .dockerignore.

6. Multi-stage builds para producción

Especialmente importante en Node.js, Go, Java, frontend builds.

7. Escanear vulnerabilidades

docker scan mi-imagen:latest

O usar herramientas como Trivy, Snyk, Clair.

8. No incluir secretos en la imagen

# ❌ NUNCA HAGAS ESTO
COPY .env .
ENV API_KEY=secreto123

Usa variables de entorno al ejecutar o servicios de secretos.

9. Limpiar en la misma capa

RUN apt-get update && \
    apt-get install -y package && \
    rm -rf /var/lib/apt/lists/*

10. Documentar con LABEL

LABEL maintainer="equipo@ejemplo.com"
LABEL version="2.1.0"
LABEL description="API de autenticación"

5.9 Ejemplos completos por lenguaje

Node.js (con multi-stage)

# Etapa de construcción
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Etapa de producción
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev && \
    npm cache clean --force
USER node
ENV NODE_ENV=production
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js
CMD ["node", "dist/app.js"]

Python

FROM python:3.11-slim
WORKDIR /app

# Instalar dependencias del sistema si es necesario
RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc && \
    rm -rf /var/lib/apt/lists/*

# Copiar solo requirements primero
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar código
COPY . .

# Crear usuario no root
RUN useradd -m -u 1000 appuser && \
    chown -R appuser:appuser /app
USER appuser

EXPOSE 8000
CMD ["python", "app.py"]

Go (multi-stage extremo)

# Etapa de construcción
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .

# Etapa de producción (imagen mínima)
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/app .
EXPOSE 8080
CMD ["./app"]

React/Frontend (build estático)

# Etapa de construcción
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Etapa de producción con Nginx
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Dominar Dockerfile es dar el salto de «usar Docker» a diseñar imágenes profesionales.

Un Dockerfile bien escrito mejora:

Rendimiento: construcciones más rápidas con caché optimizado
Seguridad: menos superficie de ataque, sin root, sin secretos
Mantenibilidad: código claro, versionable y documentado
Escalabilidad: imágenes pequeñas y eficientes
Costos: menos almacenamiento, menos transferencia de red

Los principios fundamentales son:

  1. Usar imágenes base oficiales y ligeras
  2. Aprovechar el sistema de caché
  3. Aplicar multi-stage builds
  4. No ejecutar como root
  5. Usar .dockerignore siempre
  6. Limpiar en la misma capa
  7. Documentar y versionar

Con estos conocimientos estás listo para crear Dockerfiles profesionales que funcionan bien en desarrollo y producción.

6. Volúmenes en Docker

Por diseño, los contenedores Docker son efímeros. Esto significa que cuando un contenedor se elimina, todos los datos que se hayan generado dentro de él desaparecen. Esta característica es ideal para la portabilidad, pero plantea un problema importante: ¿qué ocurre con los datos que deben persistir, como bases de datos, archivos subidos por usuarios o logs?

Para resolver esto, Docker introduce el concepto de volúmenes, un mecanismo que permite almacenar datos fuera del ciclo de vida del contenedor y reutilizarlos de forma segura y controlada.

6.1 ¿Qué son los volúmenes en Docker?

Un volumen en Docker es un mecanismo de almacenamiento persistente gestionado directamente por Docker. A diferencia del sistema de archivos interno del contenedor, los datos almacenados en un volumen no se eliminan cuando el contenedor se detiene o se borra.

Los volúmenes viven en el host (generalmente en /var/lib/docker/volumes/ en Linux), pero Docker se encarga completamente de su ubicación, permisos y gestión. Desde el punto de vista del contenedor, el volumen se comporta como un directorio normal, lo que permite a la aplicación leer y escribir datos sin saber dónde se almacenan realmente.

Ventajas de los volúmenes:

Persistencia: Los datos sobreviven a la eliminación del contenedor
Portabilidad: Independientes de la estructura del host
Rendimiento: Mejor performance que bind mounts en Windows/Mac
Gestión: Comandos dedicados para backup, migración y limpieza
Compartición: Múltiples contenedores pueden usar el mismo volumen
Seguridad: Aislamiento del sistema de archivos del host

Gracias a los volúmenes, Docker puede utilizarse de forma fiable para aplicaciones que manejan estado, algo imprescindible en entornos de producción.

6.2 Tipos de almacenamiento en Docker

Docker ofrece tres formas principales de manejar almacenamiento persistente, cada una pensada para distintos escenarios:

TipoDescripciónGestiónUso principal
VolumesGestionados por DockerDockerProducción – Datos persistentes
Bind MountsDirectorio del hostUsuarioDesarrollo – Código en tiempo real
tmpfsMemoria RAMTemporalDatos sensibles/temporales

6.2.1 Volumes (Volúmenes)

El enfoque recomendado por Docker. Son portables, seguros y desacoplados de la estructura del sistema anfitrión.

Host: /var/lib/docker/volumes/mi-volumen/_data
      ↓ (montado en)
Contenedor: /app/data

Cuándo usar:

  • Bases de datos
  • Datos de producción
  • Archivos subidos por usuarios
  • Cuando necesitas portabilidad entre entornos

6.2.2 Bind Mounts

Montan un directorio específico del host dentro del contenedor. Muy útiles en desarrollo, pero menos portables.

Host: /home/usuario/proyecto/src
      ↓ (montado en)
Contenedor: /app/src

Cuándo usar:

  • Desarrollo local
  • Hot reload de código
  • Compartir configuración del host
  • Acceso a archivos específicos del sistema

6.2.3 tmpfs Mounts

Almacenamiento en memoria RAM. Los datos se pierden al detener el contenedor.

Cuándo usar:

  • Datos sensibles que no deben persistir (tokens, contraseñas temporales)
  • Cache temporal de alto rendimiento
  • Datos que no necesitan persistir

Recomendación general:

  • Volúmenes para producción
  • Bind mounts para desarrollo
  • tmpfs para datos temporales sensibles

6.3 Trabajar con volúmenes

6.3.1 Crear un volumen

Para crear un volumen manualmente:

docker volume create mi-volumen

Salida esperada:

mi-volumen

Docker registra el volumen y lo mantiene disponible incluso si no hay contenedores usándolo.

Crear volumen con driver específico:

docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.1,rw \
  --opt device=:/path/to/dir \
  mi-volumen-nfs

6.3.2 Listar volúmenes

Para ver todos los volúmenes existentes:

docker volume ls

Salida esperada:

DRIVER    VOLUME NAME
local     mi-volumen
local     postgres-data
local     abc123def456  # Volumen anónimo

Filtrar volúmenes:

# Ver solo volúmenes huérfanos (sin contenedores asociados)
docker volume ls -f dangling=true

6.3.3 Inspeccionar un volumen

Para ver información detallada de un volumen:

docker volume inspect mi-volumen

Salida esperada:

[
    {
        "CreatedAt": "2024-01-27T10:30:00Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/mi-volumen/_data",
        "Name": "mi-volumen",
        "Options": {},
        "Scope": "local"
    }
]

El campo Mountpoint muestra dónde está físicamente en el host.

6.3.4 Eliminar volúmenes

Eliminar un volumen específico:

docker volume rm mi-volumen

⚠️ No puedes eliminar un volumen que esté en uso por un contenedor (aunque esté detenido).

Forzar eliminación (primero elimina el contenedor):

docker rm -f contenedor
docker volume rm mi-volumen

Eliminar todos los volúmenes no utilizados:

docker volume prune

Docker pedirá confirmación. Para omitirla:

docker volume prune -f

6.4 Montar volúmenes en contenedores

Existen dos sintaxis para montar volúmenes: -v (legacy, más simple) y --mount (moderna, más explícita).

6.4.1 Sintaxis con -v (volúmenes nombrados)

docker run -d \
  --name mi-app \
  -v mi-volumen:/app/data \
  nginx

Formato: -v [nombre-volumen]:[ruta-en-contenedor]:[opciones]

Si el volumen no existe, Docker lo crea automáticamente.

Opciones comunes:

  • ro (read-only): Solo lectura
  • rw (read-write): Lectura y escritura (por defecto)
docker run -v mi-volumen:/app/data:ro nginx

6.4.2 Sintaxis con –mount (recomendada)

docker run -d \
  --name mi-app \
  --mount source=mi-volumen,target=/app/data \
  nginx

Ventajas de –mount:

  • Más explícito y legible
  • Mejor manejo de errores
  • Soporta más opciones avanzadas

Opciones adicionales:

docker run -d \
  --mount source=mi-volumen,target=/app/data,readonly \
  nginx

6.4.3 Volúmenes anónimos

Si no especificas un nombre, Docker crea un volumen anónimo:

docker run -v /app/data nginx

Docker generará un ID aleatorio para el volumen (ej: abc123def456).

Problema: Difíciles de identificar y gestionar.

Solución: Siempre usar volúmenes nombrados en producción.

6.5 Bind Mounts (montajes del host)

Los bind mounts conectan un directorio específico del host con el contenedor.

6.5.1 Sintaxis con -v

docker run -d \
  -v /ruta/absoluta/en/host:/app/src \
  node:20-alpine

⚠️ La ruta del host debe ser absoluta.

6.5.2 Sintaxis con –mount

docker run -d \
  --mount type=bind,source=/home/usuario/proyecto,target=/app \
  node:20-alpine

Si la ruta no existe, –mount falla (más seguro).
Con -v, Docker crea el directorio automáticamente.

6.5.3 Ejemplo práctico: Desarrollo con hot reload

# Estructura del proyecto
/home/usuario/mi-app/
  ├── src/
  ├── package.json
  └── Dockerfile

# Ejecutar con bind mount para desarrollo
docker run -d \
  --name dev-app \
  -p 3000:3000 \
  -v /home/usuario/mi-app/src:/app/src \
  -v /home/usuario/mi-app/package.json:/app/package.json \
  mi-app:dev

Ahora puedes editar archivos en tu editor y ver los cambios reflejados inmediatamente en el contenedor (con hot reload configurado).

6.6 tmpfs Mounts (almacenamiento en memoria)

Los tmpfs mounts almacenan datos en la memoria RAM del host. Los datos se pierden al detener el contenedor.

docker run -d \
  --tmpfs /tmp:rw,size=100m,mode=1777 \
  nginx

Opciones:

  • size: Tamaño máximo
  • mode: Permisos (en octal)

Uso con –mount:

docker run -d \
  --mount type=tmpfs,target=/tmp,tmpfs-size=100m \
  nginx

Casos de uso:

  • Almacenar tokens temporales
  • Cache de sesiones
  • Archivos temporales sensibles

6.7 Volúmenes vs Bind Mounts: Comparación detallada

CaracterísticaVolumesBind Mounts
GestiónDockerUsuario
Ubicación/var/lib/docker/volumes/Cualquier ruta del host
Portabilidad✅ Alta❌ Baja
Rendimiento (Win/Mac)✅ Mejor⚠️ Más lento
Backup✅ Fácil⚠️ Manual
Seguridad✅ Aislado⚠️ Acceso directo al host
Uso en producción✅ Recomendado❌ No recomendado
Uso en desarrollo✅ Posible✅ Ideal
Auto-creación✅ Sí⚠️ Depende (-v sí, –mount no)

6.8 Casos de uso prácticos con ejemplos

6.8.1 Base de datos MySQL con volumen persistente

# Crear volumen para MySQL
docker volume create mysql-data

# Ejecutar MySQL con volumen
docker run -d \
  --name mysql-server \
  -e MYSQL_ROOT_PASSWORD=mipassword \
  -e MYSQL_DATABASE=miapp \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8

# Los datos persisten aunque elimines el contenedor
docker rm -f mysql-server

# Recrear el contenedor con los mismos datos
docker run -d \
  --name mysql-server \
  -e MYSQL_ROOT_PASSWORD=mipassword \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8

6.8.2 PostgreSQL con volumen

docker volume create postgres-data

docker run -d \
  --name postgres-server \
  -e POSTGRES_PASSWORD=mipassword \
  -e POSTGRES_DB=miapp \
  -v postgres-data:/var/lib/postgresql/data \
  -p 5432:5432 \
  postgres:15

6.8.3 MongoDB con volumen

docker volume create mongo-data

docker run -d \
  --name mongo-server \
  -v mongo-data:/data/db \
  -p 27017:27017 \
  mongo:7

6.8.4 Aplicación web con archivos subidos por usuarios

docker volume create uploads-data

docker run -d \
  --name web-app \
  -v uploads-data:/app/uploads \
  -p 8080:8080 \
  mi-aplicacion:latest

6.8.5 Logs persistentes

docker volume create app-logs

docker run -d \
  --name mi-servicio \
  -v app-logs:/var/log/app \
  mi-servicio:latest

# Acceder a los logs desde otro contenedor
docker run --rm \
  -v app-logs:/logs \
  alpine cat /logs/app.log

6.8.6 Compartir volumen entre múltiples contenedores

# Crear volumen compartido
docker volume create shared-data

# Contenedor 1: escribe datos
docker run -d \
  --name writer \
  -v shared-data:/data \
  alpine sh -c "while true; do echo \$(date) >> /data/log.txt; sleep 5; done"

# Contenedor 2: lee datos
docker run -d \
  --name reader \
  -v shared-data:/data:ro \
  alpine sh -c "while true; do tail -f /data/log.txt; sleep 10; done"

6.9 Volúmenes en Dockerfile

Puedes declarar volúmenes directamente en el Dockerfile usando la instrucción VOLUME:

FROM postgres:15

# Declarar volumen
VOLUME /var/lib/postgresql/data

CMD ["postgres"]

¿Qué hace esto?

  • Documenta qué directorios deben persistir
  • Docker creará un volumen anónimo automáticamente si no se especifica uno al ejecutar

Ejemplo de uso:

# Docker creará un volumen anónimo automáticamente
docker run -d postgres:15

# Mejor: especificar volumen nombrado
docker run -d -v postgres-data:/var/lib/postgresql/data postgres:15

Recomendación: Usar VOLUME en el Dockerfile para documentación, pero siempre especificar volúmenes nombrados al ejecutar contenedores en producción.

6.10 Backup y restore de volúmenes

6.10.1 Hacer backup de un volumen

# Crear un backup del volumen mysql-data
docker run --rm \
  -v mysql-data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/mysql-backup.tar.gz -C /data .

Explicación:

  1. Montamos el volumen a respaldar en /data
  2. Montamos el directorio actual en /backup
  3. Creamos un archivo tar.gz con el contenido del volumen
  4. El flag --rm elimina el contenedor temporal al terminar

6.10.2 Restaurar un volumen desde backup

# Crear nuevo volumen
docker volume create mysql-data-restored

# Restaurar desde backup
docker run --rm \
  -v mysql-data-restored:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/mysql-backup.tar.gz -C /data

6.10.3 Copiar volumen completo a otro volumen

# Crear volumen de destino
docker volume create mysql-data-copy

# Copiar datos
docker run --rm \
  -v mysql-data:/source:ro \
  -v mysql-data-copy:/dest \
  alpine sh -c "cd /source && cp -av . /dest"

6.11 Permisos y ownership en volúmenes

Uno de los problemas más comunes con volúmenes es la incompatibilidad de permisos, especialmente en Linux.

Problema común:

docker run -d \
  --name app \
  -v app-data:/app/data \
  mi-app:latest

El contenedor puede ejecutarse como usuario node (UID 1000), pero el volumen puede tener permisos de root.

Solución 1: Establecer ownership en el Dockerfile

FROM node:20-alpine

WORKDIR /app

# Crear directorio y establecer permisos
RUN mkdir -p /app/data && \
    chown -R node:node /app

# Cambiar a usuario no privilegiado
USER node

VOLUME /app/data

Solución 2: Establecer permisos al crear el contenedor

docker run -d \
  --name app \
  --user 1000:1000 \
  -v app-data:/app/data \
  mi-app:latest

Solución 3: Usar un contenedor temporal para ajustar permisos

docker run --rm \
  -v app-data:/data \
  alpine chown -R 1000:1000 /data

6.12 Drivers de volúmenes

Docker soporta diferentes drivers para volúmenes, permitiendo almacenamiento en la nube, NFS, etc.

Drivers comunes:

DriverDescripción
localAlmacenamiento local (por defecto)
nfsNetwork File System
vieux/sshfsSSH File System
rexray/ebsAmazon EBS
cloudstorDocker Cloud Storage

Ejemplo con NFS:

docker volume create \
  --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/path/to/share \
  nfs-volume

Ejemplo con sshfs (plugin necesario):

# Instalar plugin
docker plugin install vieux/sshfs

# Crear volumen
docker volume create \
  --driver vieux/sshfs \
  -o sshcmd=usuario@servidor:/ruta \
  -o password=mipassword \
  ssh-volume

6.13 Limpieza y mantenimiento

Ver uso de espacio de volúmenes

docker system df -v

Muestra cuánto espacio ocupan volúmenes, imágenes y contenedores.

Identificar volúmenes huérfanos

docker volume ls -f dangling=true

Eliminar volúmenes huérfanos

docker volume prune

Eliminar TODO (⚠️ peligroso)

docker system prune -a --volumes

Esto elimina:

  • Contenedores detenidos
  • Redes no utilizadas
  • Imágenes sin contenedores
  • TODOS los volúmenes no utilizados

6.14 Troubleshooting común

Problema 1: «No such volume»

Síntoma:

Error response from daemon: no such volume: mi-volumen

Solución:

# Verificar que el volumen existe
docker volume ls | grep mi-volumen

# Si no existe, créalo
docker volume create mi-volumen

Problema 2: «Permission denied» al escribir en volumen

Síntoma:

Error: EACCES: permission denied, open '/app/data/file.txt'

Solución:

# Verificar permisos del volumen
docker volume inspect mi-volumen

# Ajustar ownership
docker run --rm -v mi-volumen:/data alpine chown -R 1000:1000 /data

Problema 3: Datos no persisten

Síntoma: Los datos se pierden al recrear el contenedor.

Causas comunes:

  1. No estás usando el mismo volumen nombrado
  2. Estás usando un volumen anónimo diferente cada vez
  3. La ruta del bind mount cambió

Solución:

# Siempre usar volúmenes nombrados
docker run -v mi-volumen-nombrado:/data mi-app

# NO usar volúmenes anónimos en producción
docker run -v /data mi-app  # ❌ Evitar

Problema 4: Volumen lleno

Síntoma:

Error: ENOSPC: no space left on device

Solución:

# Ver tamaño del volumen
docker system df -v

# Limpiar datos antiguos dentro del volumen
docker run --rm -v mi-volumen:/data alpine rm -rf /data/old-files

# O crear nuevo volumen y migrar datos selectivamente

6.15 Mejores prácticas

Usar volúmenes nombrados en producción (nunca anónimos)
Documentar volúmenes en README del proyecto
Hacer backups regulares de volúmenes críticos
Usar bind mounts solo en desarrollo
Establecer permisos correctos antes de usar volúmenes
Limpiar volúmenes huérfanos periódicamente
Monitorear uso de espacio con docker system df
Usar tmpfs para datos sensibles temporales
NO incluir volúmenes en imágenes (usar .dockerignore)
NO depender de rutas absolutas del host en producción

En resumen, los volúmenes transforman Docker de una herramienta pensada solo para aplicaciones sin estado en una plataforma viable para sistemas reales y persistentes.

Puntos clave:

  1. Volumes → Producción (gestionados por Docker)
  2. Bind mounts → Desarrollo (directorios del host)
  3. tmpfs → Datos temporales sensibles (en memoria)
  4. Siempre usar volúmenes nombrados en producción
  5. Hacer backups de datos críticos
  6. Gestionar permisos correctamente
  7. Limpiar volúmenes no utilizados regularmente

Entender cómo y cuándo usar cada tipo de almacenamiento es una habilidad clave para cualquier persona que trabaje con Docker en producción.

7. Redes en Docker

Docker proporciona redes para conectar contenedores:

Tipos de redes:

  • Bridge: Predeterminado, conecta contenedores en el mismo host.
  • Host: Usa directamente la red del host.
  • Overlay: Para redes distribuidas.

Ejemplo:

Crear una red personalizada y conectar contenedores:

docker network create mi-red
docker run -d --name web --network mi-red nginx
docker run -d --name db --network mi-red mysql

8. Docker Compose

Docker Compose facilita la gestión de múltiples contenedores. Define tus servicios en un archivo docker-compose.yml.

Ejemplo de archivo docker-compose.yml:

version: "3.9"
services:
  web:
    image: nginx
    ports:
      - "8080:80"
  app:
    build:
      context: .
    volumes:
      - ".:/app"
    ports:
      - "3000:3000"
  db:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: password

Comandos comunes:

  • Levantar servicios: docker-compose up -d
  • Detener servicios: docker-compose down

9. Mejores Prácticas con Docker

  1. Mantén imágenes ligeras:
    • Usa imágenes base mínimas como alpine.
  2. Evita copiar archivos innecesarios:
    • Usa .dockerignore para excluir archivos.
  3. Etiqueta tus imágenes:
    • Usa versiones para gestionar actualizaciones (mi-app:1.0).
  4. Usa volúmenes para datos persistentes:
    • Nunca almacenes datos críticos dentro del contenedor.

10. Herramientas Complementarias

  • Portainer: Interfaz gráfica para gestionar contenedores.
  • Docker Swarm: Orquestación para entornos Docker.
  • Kubernetes: Gestión avanzada de contenedores en producción.

11. Casos de Uso Comunes para Desarrolladores Web

  1. Ambientes locales reproducibles:
    • Configura tu servidor web, base de datos y backend con Docker.
  2. Pruebas automáticas:
    • Usa contenedores para ejecutar pruebas en entornos aislados.
  3. Despliegue continuo:
    • Integra Docker en pipelines CI/CD.

12. Conclusión

Docker transforma la forma en que los desarrolladores web gestionan y despliegan aplicaciones. Con los fundamentos y herramientas descritas en esta guía, estarás preparado para aprovechar Docker al máximo y llevar tus proyectos al siguiente nivel.

¿Listo para comenzar? ¡Crea tu primer contenedor y experimenta con Docker hoy mismo!

5/5 - (1 voto)

Deja un comentario

Web Devs