Saltar al contenido principal
50 min de lectura1 de agosto de 2026
DockerContenedoresDevOpsDockerfileCompose

Requisitos previos

  • git

Lo que aprenderás

  • Entender la diferencia entre imagen y contenedor
  • Escribir Dockerfiles eficientes
  • Construir, etiquetar y ejecutar contenedores
  • Persistir datos con volúmenes
  • Orquestar servicios con Docker Compose
Tutorial50 min

Docker: contenedores de cero a producción

Docker empaqueta tu aplicación junto con su entorno — dependencias, configuración, runtime — en una unidad reproducible llamada contenedor. Aprende desde los conceptos hasta el despliegue con Docker Compose y registros de imágenes.


¿Qué es Docker?

Docker es una plataforma para desarrollar, enviar y ejecutar aplicaciones en contenedores. Un contenedor es un proceso aislado que comparte el kernel del sistema operativo anfitrión pero tiene su propio sistema de archivos, red y espacio de procesos. Eso lo hace mucho más ligero que una máquina virtual.

Contenedores vs máquinas virtuales

Una VM virtualiza el hardware y ejecuta un sistema operativo completo por encima de un hypervisor: pesa gigabytes y tarda minutos en arrancar. Un contenedor virtualiza solo el espacio de usuario y comparte el kernel del host: pesa megabytes, arranca en milisegundos y arranca decenas de ellos sin agotar la máquina.

El aislamiento de contenedores se apoya en funcionalidades del kernel Linux: namespaces (procesos, red, montajes) y cgroups (límites de CPU y memoria). En macOS y Windows, Docker Desktop ejecuta una VM ligera de Linux en segundo plano para darles el mismo comportamiento.

Imágenes y contenedores

Una imagen es una plantilla inmutable y de solo lectura: código, runtime, librerías y configuración. Un contenedor es una instancia en ejecución de una imagen: puede arrancarse, detenerse, eliminarse y duplicarse. La relación es la misma que entre una clase y sus objetos en programación orientada a objetos.

El daemon y el CLI

Docker tiene dos piezas: el daemon (dockerd), un servicio en segundo plano que gestiona imágenes, contenedores, redes y volúmenes; y el cliente (docker), la CLI con la que le das órdenes. Al escribir docker run, el cliente envía la petición al daemon vía una API REST local.

En un flujo de CI/CD verás imágenes empaquetadas en el mismo formato que corre en producción: ese es el valor central. Si funciona en tu máquina, funcionará en el servidor, porque el contenedor lleva consigo el entorno completo.

Instalación

1. Descarga Docker Desktop desde docker.com y arrastra la app a Aplicaciones. Incluye el daemon, el CLI, Docker Compose y una interfaz gráfica.

2. Ábrela por primera vez y acepta el uso del Hypervisor (puede pedir permisos de administrador).

3. Verifica la instalación — el daemon debe estar en ejecución:

docker version
# Client: ... Engine: ... -> todo OK

docker info
# Muestra versión del kernel, contenedores e imágenes actuales

En Linux puedes instalar el engine directamente (apt install docker.io) y ejecutar contenedores sin GUI, pero Docker Desktop es la vía más sencilla en macOS y Windows.

Si docker version falla con "Cannot connect to the Docker daemon", el problema casi siempre es que Docker Desktop no está abierto. Ábrelo y espera a que la ballena deje de animarse.

Primeros comandos

Descarga una imagen sin ejecutarla con pull y ejecútala de forma interactiva con run -it:

# Descargar la imagen de Ubuntu
docker pull ubuntu:24.04

# Ejecutar un contenedor interactivo con una shell dentro
docker run -it ubuntu:24.04 bash
root@a1b2c3:/# ls /etc/os-release
root@a1b2c3:/# exit

# Ejecutar y eliminar al salir
docker run --rm -it ubuntu:24.04 echo "hola desde un contenedor"

Gestiona los contenedores en ejecución y detenidos:

docker ps          # contenedores en ejecución
docker ps -a      # también los detenidos
docker stop <id>  # detener uno
docker rm <id>    # eliminar uno (solo detenidos)
docker rm -f <id> # eliminar incluso en ejecución

Ejecuta un proceso adicional dentro de un contenedor vivo con exec — imprescindible para depurar:

docker run -d --name web nginx
docker exec -it web bash
# abre una shell dentro del contenedor nginx

docker logs web   # ver la salida estándar
docker inspect web# JSON con toda la configuración y estado
El flag -d (detached) ejecuta el contenedor en segundo plano y te devuelve el prompt. Con --name le pones un identificador legible para no depender de IDs largos.

El Dockerfile

Un Dockerfile es un script que describe cómo construir una imagen, instrucción a instrucción. Cada instrucción crea una capa inmutable que Docker cachea: si una capa no cambia, las siguientes se reutilizan en la siguiente construcción.

# Dockerfile de una API Node.js
FROM node:22-alpine

# Directorio de trabajo: todas las instrucciones se ejecutan ahí
WORKDIR /app

# Copiar dependencias primero para aprovechar el cache de capas
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# Después el código fuente (cambia más a menudo)
COPY . .

# Puerto que expondrá el contenedor (documental)
EXPOSE 3000

CMD ["node", "src/index.js"]

ENTRYPOINT vs CMD

CMD define el comando por defecto y se puede sobrescribir al ejecutar: docker run mi-api node otra.js. ENTRYPOINT define el ejecutable fijo de la imagen y no se reemplaza, aunque admite argumentos adicionales.

# ENTRYPOINT fijo: la imagen SIEMPRE ejecuta python app.py
ENTRYPOINT ["python", "app.py"]

# Forma exec (recomendada) vs forma shell
CMD ["python", "app.py"]      # exec: el PID 1 es el proceso real
CMD python app.py             # shell: envuelto en /bin/sh -c
Prefiere siempre la forma exec (array JSON). En la forma shell, el proceso real queda envuelto por /bin/sh -c, así las señales (CTRL+C, SIGTERM) no llegan al proceso y Docker debe esperar un timeout antes de forzar el cierre.

La selección de la imagen base importa: las variantes -alpine o -slim reducen decenas de megabytes y la superficie de ataque, a cambio de alguna librería que quizá debas instalar tú mismo.

.dockerignore

Funciona como .gitignore: excluye archivos del contexto de construcción que se envía al daemon. Evita mandar node_modules, builds y archivos de entorno con secretos:

node_modules
dist
.env
.git
*.log
.DS_Store
Jamás copies .env dentro de la imagen: las capas se quedan guardadas y cualquiera con acceso al registro puede extraer los secretos. Los valores de entorno se inyectan en tiempo de ejecución con --env-file o con secretos de tu proveedor.

Construir y etiquetar

Construye la imagen a partir del Dockerfile con build y nómbrala con -t. El tag va seguido del registro, el repositorio y la versión:

# Construir con tag nombre:version
docker build -t mi-api:v1 .

# Ver las imágenes locales
docker images

# Etiquetar una imagen existente con otro nombre (para subirla a un registro)
docker tag mi-api:v1 ghcr.io/miusuario/mi-api:latest

# Ejecutar mapeando puertos: HOST:CONTENEDOR
docker run -d -p 8080:3000 --name api mi-api:v1
curl http://localhost:8080

En -p 8080:3000, 8080 es el puerto de tu máquina y 3000 el puerto interno del contenedor. Sin este mapeo, la app queda inaccesible desde el host aunque esté escuchando.

Volúmenes

El sistema de archivos de un contenedor es efímero: todo lo que escribas desaparece al eliminarlo. Los volúmenes montan datos del host (o gestionados por Docker) dentro del contenedor para persistir y compartir información:

# Bind mount: carpeta del host dentro del contenedor
docker run -d -p 80:80 -v "$PWD/sitio:/usr/share/nginx/html" nginx

# Named volume: Docker gestiona el almacenamiento por ti
docker volume create datos-postgres
docker run -d -v datos-postgres:/var/lib/postgresql/data postgres:16

docker volume ls
docker volume inspect datos-postgres
El bind mount (-v ruta:ruta) es ideal en desarrollo: editas el código en el host y el contenedor lo ve al instante. Los named volumes son la opción correcta para datos de bases de datos: Docker controla la ubicación y el backup.

Docker Compose

Compose define en un único archivo YAML los servicios, redes y volúmenes de tu aplicación y los arranca con un comando. Es la forma estándar de orquestar varios contenedores en desarrollo: una API con su base de datos, por ejemplo.

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - .:/app
      - /app/node_modules

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - datos-postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  datos-postgres:
# Arrancar todo en segundo plano
docker compose up -d

# Ver logs de todos los servicios
docker compose logs -f

# Detener y eliminar contenedores (los volúmenes persisten)
docker compose down

# Eliminar también los volúmenes
docker compose down -v

Dentro de la red de Compose, los servicios se resuelven por su nombre: api conecta a db:5432 sin necesidad de IPs ni configuración de host. depends_on con condition: service_healthy espera a que la base de datos pase el healthcheck antes de arrancar la API.

Registros y push

Un registro es un repositorio remoto de imágenes. Docker Hub es el público por defecto; GitHub Container Registry (ghcr.io), GitLab y ECR de AWS son alternativas habituales ligadas a tu plataforma de CI/CD.

# Login en Docker Hub
docker login

# Etiquetar con tu usuario de Hub y subir
docker tag mi-api:v1 miusuario/mi-api:v1
docker push miusuario/mi-api:v1

# Cualquiera (o tu CI/CD) puede bajarla
docker pull miusuario/mi-api:v1
docker run -d -p 3000:3000 miusuario/mi-api:v1

Usa tags inmutables por versión (v1.2.3) en producción y reserva latest para desarrollo. Si un despliegue falla, así puedes volver a la versión exacta anterior.


Ejercicios


Con Docker ya tienes el ciclo completo: contenedores aislados, imágenes reproducibles, volúmenes persistentes y multi-servicio con Compose. El siguiente paso natural es la orquestación: cuando necesitas decenas de contenedores con balanceo, escalado y auto-reparación, herramientas como Kubernetes toman el relevo.

Siguiente lección

CI/CD con GitHub Actions

Implementa integración y despliegue continuos con GitHub Actions: workflows, jobs y steps, triggers, build + lint + test, secrets, cache y despliegue automático a producción.