OpenWebinars

Desarrollo Web

Introducción a Docker: Desarrollo y despliegue con contenedores

La llegada de Docker marcó un antes y un después en el desarrollo y despliegue de aplicaciones. Gracias a sus contenedores, es posible crear entornos replicables, escalar fácilmente y reducir los tiempos de configuración y puesta en marcha. Si aún no conoces sus ventajas, esta guía te ayudará a entender por qué Docker es clave en los flujos de trabajo modernos.

Diego Oliva

Diego Oliva

Experto en análisis de sistemas, optimización de procesos y transformación digital

Lectura 7 minutos

Publicado el 24 de marzo de 2025

Compartir

Introducción

¿Alguna vez te has topado a alguien que te lanza el frustrante mensaje “en mi máquina funciona”? Este es uno de los problemas más comunes en el desarrollo de software, y es precisamente el tipo de desafío que Docker vino a resolver.

Recuerdo claramente mi primera experiencia con este problema. Estaba liderando un equipo de desarrollo en una startup fintech, y teníamos una aplicación crítica que funcionaba perfectamente en el entorno de desarrollo, pero fallaba misteriosamente en producción. Después de tres días de debugging intensivo, descubrimos que la versión de una dependencia era diferente en cada entorno. Fue en ese momento cuando decidimos adoptar Docker, y nunca miramos atrás.

Los números hablan por sí solos: el 76% de equipos que adoptan Docker reducen en dos tercios el tiempo perdido en problemas de entorno (Stack Overflow 2024). Y según Datadog, estos equipos despliegan tres veces más rápido con mayor éxito. ¿Otro dato jugoso? El 85% de empresas recortan sus costos de infraestructura a la mitad usando contenedores (Forrester).

Si estás comenzando en el mundo del desarrollo o buscando mejorar tus procesos de deployment, estás en el lugar correcto. En este artículo, exploraremos cómo Docker ha revolucionado el desarrollo y despliegue de aplicaciones.

Qué es Docker

Docker es una plataforma de código abierto que automatiza el despliegue de aplicaciones dentro de contenedores de software. Pero, ¿qué significa esto realmente?

Los contenedores son como cajas estandarizadas que pueden transportar cualquier tipo de carga. Esta analogía no es casual - Docker se inspiró en los contenedores de transporte marítimo, que revolucionaron la industria logística al proporcionar un formato estándar para mover mercancías.

En el mundo del software, los contenedores resuelven varios problemas críticos:

Problema Solución con Docker
Inconsistencia entre entornos Empaqueta todo lo necesario en un contenedor
Conflictos de dependencias Aislamiento completo entre aplicaciones
Tiempo de configuración Entornos reproducibles con un solo comando
Recursos desperdiciados Uso eficiente y compartido de recursos

Conceptos básicos de Docker y contenedores

Qué son los contenedores

Un contenedor es una unidad estándar de software que empaqueta el código y todas sus dependencias. Imagina que cada aplicación viene en su propia “caja” con todo lo que necesita: sistema operativo, librerías, configuración, etc. Permíteme ilustrarlo con un ejemplo real:

En mi último proyecto, teníamos una aplicación Python que requería una versión específica de TensorFlow. El equipo de ciencia de datos necesitaba Python 3.8, mientras que el equipo de backend usaba Python 3.9. Sin contenedores, esto habría sido una pesadilla de configuración. Con Docker, cada equipo trabajaba en su propio contenedor, sin conflictos y con total independencia.

¿Qué problemas solucionamos con esto? Lo vemos en la siguiente tabla:

Dolor tradicional Solución Docker
“En mi PC funciona” Todo empaquetado en un contenedor
“Se me rompió otra dependencia” Aislamiento total entre apps
Configurar entornos eternos docker-compose up y listo
Máquinas virtuales obesas Contenedores ligeros y ágiles

Cómo funciona Docker

Docker utiliza una arquitectura cliente-servidor donde:

  • Docker Daemon (dockerd) es el servidor que:

    • Gestiona contenedores
    • Maneja imágenes
    • Administra redes y almacenamiento
  • Docker Client (docker) es la interfaz de usuario que:

    • Acepta comandos
    • Se comunica con el daemon
    • Puede conectarse a múltiples daemons

Otra forma de ver estos primeros dos puntos es en la forma en que funciona un restaurante de lujo:

  • El chef (daemon): Prepara los platos (contenedores), gestiona la cocina (recursos) y mantiene la despensa (imágenes)
  • El camarero (cliente): Toma tu pedido (comandos) y te sirve los resultados

Por otro lado, Docker también funciona con:

  • Docker Hub: El registro público de imágenes Docker. ¿Necesitas una base de datos MongoDB?
docker pull mongo
  • Docker Compose: Un archivo de configuración que define y ejecuta múltiples contenedores. ¿Tienes un backend, un frontend y una base de datos?
docker compose up

Veamos un ejemplo práctico de interacción:

  • El cliente (docker) envía un comando al daemon (dockerd)
  • El daemon ejecuta el comando y devuelve el resultado al cliente
$ docker version  
Client: Docker Engine - Community  
 Version:           24.0.7  
...  
Server: Docker Engine - Community  
 Engine:  
  Version:          24.0.7

Aquí el cliente le pregunta al daemon: “Oye, ¿qué versión tienes?” Y el daemon responde. ¡Como un asistente virtual, pero para contenedores!

Elementos clave en Docker

Docker utiliza varios elementos clave para su funcionamiento:

  • Imágenes: Son plantillas de solo lectura que contienen:

    • Sistema operativo base
    • Runtime environment
    • Dependencias de aplicación
    • Configuración
  • Contenedores: Son instancias ejecutables de una imagen. Por ejemplo:

$ docker run -d nginx
6d7f219ed6f43c99b30f0df58d66b7d5c5513b61ac3b038f9d2e96c0a772ba22

$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
6d7f219ed6f4   nginx     "/docker-entrypoint.…"   5 seconds ago   Up 4 seconds   80/tcp    friendly_euler

En este bloque de código, estamos creando un contenedor en segundo plano con la imagen de Nginx.

El comando docker run nos permite crear un contenedor a partir de una imagen y ejecutar un comando en él. En este caso, estamos ejecutando el comando por defecto de Nginx, que es “/docker-entrypoint.sh” nginx -g ‘daemon off;’. Luego, estamos mostrando la lista de contenedores en ejecución con el comando docker ps.

  • Dockerfile: Define cómo construir una imagen. Ejemplo básico:
FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "start"]

Ahora estamos definiendo un Dockerfile que construye una imagen personalizada para una aplicación Node.js. El Dockerfile comienza con una instrucción FROM que indica que queremos partir de la imagen node:14. Luego, creamos un directorio /app en el contenedor y copiamos los archivos package.json y package-lock.json en él. Después, ejecutamos el comando npm install para instalar las dependencias de la aplicación. Finalmente, copiamos el resto de los archivos de la aplicación en el contenedor y definimos el comando por defecto que se ejecutará cuando se inicie el contenedor, que es npm start.

  • Docker Hub: El registro público de imágenes Docker. ¿Necesitas una base de datos MongoDB? Tan simple como:
$ docker pull mongodb
Using default tag: latest
latest: Pulling from library/mongodb
a603fa5e3b41: Pull complete 
c428f1a41b1c: Pull complete
... [más capas]
Status: Downloaded newer image for mongodb:latest

Por último, estamos descargando la imagen de MongoDB de Docker Hub. El comando docker pull nos permite descargar una imagen de Docker Hub. En este caso, estamos descargando la imagen de MongoDB.

Para profundizar aún más en estos conceptos, te recomiendo nuestro Curso de introducción a Docker, donde aprenderás desde los fundamentos hasta las mejores prácticas.

Aprende a desarrollar webs optimizadas
Comienza 15 días gratis en OpenWebinars y accede cursos, talleres y laboratorios prácticos de JavaScript, React, Angular, HTML, CSS y más.
Registrarme ahora

Ventajas del uso de Docker

Portabilidad

La portabilidad es uno de los beneficios más significativos de Docker. En mi experiencia como consultor DevOps, he visto cómo esta característica ha salvado innumerables horas de trabajo.

Por ejemplo, una empresa de e-commerce con la que trabajé tenía problemas para migrar su aplicación entre diferentes proveedores cloud. Con Docker, logramos hacer la migración de AWS a Google Cloud en cuestión de días, no semanas como inicialmente se había estimado.

Eficiencia

La eficiencia en Docker se manifiesta de varias maneras:

Aspecto Beneficio Ejemplo Real
Uso de recursos Menor overhead que VMs 50% menos uso de memoria
Tiempo de inicio Arranque casi instantáneo De minutos a segundos
Almacenamiento Sistema de capas eficiente Reducción del 60% en espacio
Desarrollo Entornos consistentes Reducción de bugs en producción

Despliegue rápido

En una startup de fintech, redujimos el tiempo de despliegue de 45 minutos a apenas 3 minutos utilizando Docker. Aquí está cómo lo logramos:

# Construir y etiquetar la imagen
docker build -t miapp .  # Construye la imagen
docker service update --image miapp miservicio  # Actualiza en vivo

Escalabilidad

La escalabilidad con Docker es excepcional. En un proyecto reciente para una plataforma de e-learning, pudimos escalar de 1,000 a 100,000 usuarios sin cambios en la arquitectura. La clave fue la capacidad de Docker para:

  • Escalar horizontalmente
  • Balancear carga automáticamente
  • Manejar picos de tráfico

Un ejemplo de escalabilidad con Docker:

# Escalar el servicio
$ docker service scale miservicio=100

En este bloque de código, estamos escalando un servicio con el comando docker service scale. En este caso, estamos escalando el servicio miservicio a 100 instancias. Esto nos permite manejar picos de tráfico y escalar horizontalmente.

Pero ojo: escalar sin control es como añadir motores a un coche sin mejores frenos. Necesitas monitorización y balanceo inteligente.

Casos prácticos de uso de Docker

Desarrollo local

El desarrollo local con Docker ha transformado cómo los equipos trabajan.

Ejemplo real de un proyecto web:

# Estructura típica
myapp/
├── src/
├── tests/
├── Dockerfile
├── docker-compose.yml
└── .dockerignore

# Iniciar el entorno
$ docker-compose up -d
Creating network "myapp_default" with the default driver
Creating myapp_db_1    ... done
Creating myapp_redis_1 ... done
Creating myapp_web_1   ... done

# Verificar servicios
$ docker-compose ps
    Name                   Command               State           Ports
----------------------------------------------------------------------------
myapp_db_1      docker-entrypoint.sh mongod     Up      27017/tcp
myapp_redis_1   docker-entrypoint.sh redis ...   Up      6379/tcp
myapp_web_1     npm start                       Up      0.0.0.0:3000->3000/tcp

El Dockerfile y el docker-compose.yml definen la configuración del entorno de desarrollo. Se vería de la siguiente manera:

# Dockerfile
FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "start"]
# docker-compose.yml
version: '3'
services:
  web:
    build: .
    ports:
      - "3000:3000"
  db:
    image: mongo
    ports:
      - "27017:27017"
  redis:
    image: redis
    ports:
      - "6379:6379"

Esto nos permite iniciar un entorno de desarrollo con docker-compose up -d y detenerlo con docker-compose down. Para poder acceder a los endpoints de la aplicación, podemos usar localhost:3000.

Integración continua

La integración continua con Docker ha transformado cómo validamos y probamos nuestro código. En un proyecto reciente para una institución financiera, implementamos un pipeline de CI (con GitHub Actions) que redujo los falsos positivos en testing en un 80%. El secreto fue la consistencia del entorno de pruebas.

Ejemplo de un pipeline básico de CI:

  • Esto está hecho en GitHub Actions pero puedes adaptarlo a cualquier otro sistema de CI.
name: CI Pipeline
on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Build test image
        run: docker build -t myapp:test -f Dockerfile.test .

      - name: Run unit tests
        run: |
          docker run --rm myapp:test npm run test

      - name: Run integration tests
        run: |
          docker-compose -f docker-compose.test.yml up \
            --abort-on-container-exit \
            --exit-code-from test

Básicamente lo que hace es:

  • Construir una imagen de pruebas
  • Ejecutar pruebas unitarias
  • Ejecutar pruebas de integración

Si sale todo bien, el pipeline se detiene. Si sale mal, el pipeline se detiene y envía un correo de alerta.

Resultado típico de la ejecución:

$ docker run --rm myapp:test npm run test
> myapp@1.0.0 test
> jest --coverage

 PASS  tests/api.test.js
 PASS  tests/auth.test.js
 PASS  tests/user.test.js

Test Suites: 3 passed, 3 total
Tests:       24 passed, 24 total
Snapshots:   0 total
Time:        3.45 s

Esto significa que todo está bien, el pipeline se detiene.

Despliegue en la nube

El despliegue en la nube con Docker es uno de los casos de uso más potentes. En un proyecto reciente, necesitábamos desplegar la misma aplicación en múltiples regiones geográficas. Docker hizo esto posible con un esfuerzo mínimo.

Ejemplo de despliegue en diferentes proveedores cloud:

Proveedor Servicio Comando de despliegue
AWS ECS aws ecs update-service --force-new-deployment
Google Cloud Cloud Run gcloud run deploy --image gcr.io/myapp
Azure ACI az container create --image myapp:latest

Hay muchos más que se pueden usar, como Heroku, DigitalOcean, etc.

Script de despliegue automatizado:

#!/bin/bash
# deploy.sh

# Variables de entorno
DOCKER_IMAGE="myapp:${VERSION:-latest}"
DEPLOY_ENV="${ENV:-staging}"

# Construir y pushear imagen
echo "🏗️ Construyendo imagen: $DOCKER_IMAGE"
docker build -t $DOCKER_IMAGE .
docker push $DOCKER_IMAGE

# Desplegar en el ambiente correspondiente
echo "🚀 Desplegando en $DEPLOY_ENV"
case $DEPLOY_ENV in
  "staging")
    docker stack deploy -c docker-compose.staging.yml myapp-staging
    ;;
  "production")
    docker stack deploy -c docker-compose.prod.yml myapp-prod
    ;;
esac

# Verificar despliegue
echo "✅ Verificando despliegue..."
docker service ls --filter name=myapp

Esto lo que hace es desplegar la aplicación en el ambiente correspondiente, donde staging es el ambiente de pruebas y production es el ambiente de producción.

Microservicios

La arquitectura de microservicios es donde Docker realmente brilla. Permíteme compartir un caso real: transformamos un monolito de comercio electrónico en microservicios, reduciendo el tiempo de despliegue de nuevas características de semanas a horas.

Estructura típica de un proyecto de microservicios:

ecommerce/
├── services/
│   ├── users/
│   │   ├── Dockerfile
│   │   └── src/
│   ├── products/
│   │   ├── Dockerfile
│   │   └── src/
│   └── orders/
│       ├── Dockerfile
│       └── src/
├── docker-compose.yml
└── nginx/
    └── nginx.conf

Docker Compose para orquestar los servicios:

version: '3.8'

services:
  users:
    build: ./services/users
    environment:
      - DB_HOST=users_db
    depends_on:
      - users_db

  products:
    build: ./services/products
    environment:
      - CACHE_HOST=redis
    depends_on:
      - redis

  orders:
    build: ./services/orders
    environment:
      - KAFKA_BROKERS=kafka:9092
    depends_on:
      - kafka

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro

Esto lo que hace a detalle es desplegar los microservicios en contenedores, donde users es el servicio de usuarios, products es el servicio de productos y orders es el servicio de pedidos, con nginx como proxy inverso.

Además, podemos ver que en el docker-compose.yml se definen las variables de entorno, las dependencias, los puertos y los volúmenes.

Mejores prácticas al usar Docker

A continuación, detallo las mejores prácticas al usar Docker. Donde ahondamos en el uso eficiente de imágenes, la gestión de volúmenes y la optimización del despliegue.

Uso eficiente de imágenes

La optimización de imágenes es crucial para el rendimiento. En un proyecto de IoT, redujimos el tamaño de nuestras imágenes de 1.2GB a 85MB siguiendo estas prácticas:

  • Multi-stage builds:

Este es un patrón donde se divide el proceso de construcción en dos etapas: una para construir y otra para producir. La ventaja es que la imagen final es más ligera y contiene solo los archivos necesarios.

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

# Etapa de producción
FROM node:14-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm install --production
CMD ["npm", "start"]
  • Optimización de capas:

Es importante optimizar las capas de las imágenes para reducir el tiempo de construcción y el tamaño final.

Práctica Impacto
Combinar comandos RUN Reduce el número de capas
.dockerignore Excluye archivos innecesarios
Ordenar operaciones Mejora el uso de caché

La optimización siempre es importante para el rendimiento de las imágenes.

Gestión de volúmenes

La gestión efectiva de volúmenes es esencial para la persistencia de datos. En producción, implementamos esta estrategia:

version: '3.8'
services:
  postgres:
    image: postgres:13
    volumes:
      - pgdata:/var/lib/postgresql/data  # Volumen nombrado
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro  # Volumen bind
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/pg_password
    secrets:
      - pg_password

volumes:
  pgdata:  # Docker gestiona este volumen

Esto hace que los datos persistan entre las ejecuciones de los contenedores, lo que es esencial para la persistencia de datos. Además, los volúmenes nombrados son manejados por Docker, lo que facilita la gestión y la limpieza.

Comandos útiles para la gestión de volúmenes:

# Listar volúmenes
$ docker volume ls
DRIVER    VOLUME NAME
local     myapp_pgdata
local     myapp_redis_data

# Inspeccionar un volumen
$ docker volume inspect myapp_pgdata
[
    {
        "CreatedAt": "2025-10-15T14:30:00Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "myapp",
            "com.docker.compose.volume": "pgdata"
        },
        "Mountpoint": "/var/lib/docker/volumes/myapp_pgdata/_data",
        "Name": "myapp_pgdata",
        "Scope": "local"
    }
]

Según estos comandos, podemos ver que los volúmenes nombrados y bind son manejados por Docker, lo que facilita la gestión y la limpieza.

Seguridad

La seguridad en Docker es fundamental. En un proyecto para una institución financiera, implementamos estas medidas críticas:

  • Escaneo continuo de vulnerabilidades:

Esto es esencial para identificar y mitigar vulnerabilidades en las imágenes.

# Escanear imagen
$ docker scan myapp:latest
✗ Low severity vulnerability found in curl/libcurl4
  Description: Improper Certificate Validation
  Fixed in: 7.68.0-1ubuntu2.8
  CVSS Score: 3.7

✗ Medium severity vulnerability found in openssl/libssl1.1
  Description: Buffer Overflow
  Fixed in: 1.1.1f-1ubuntu2.8
  CVSS Score: 5.9
  • Configuración segura:

El uso de un usuario no root y permisos restrictivos es esencial para la seguridad. Implica que los contenedores no se ejecutan como root, lo que reduce el impacto de un ataque.

# Usar usuario no root
RUN groupadd -r myapp && \
    useradd -r -g myapp myapp
USER myapp

# Establecer permisos restrictivos
RUN chmod -R 550 /app && \
    chmod -R 770 /app/data

# Minimizar superficie de ataque
EXPOSE 8080

Monitorización de contenedores

La monitorización efectiva salvó nuestro servicio durante un incidente de producción. Nuestro stack salvavidas:

  • Prometheus para métricas:
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'docker'
    static_configs:
      - targets: ['localhost:9323']
  • Grafana para visualización:
$ docker run -d \
  -p 3000:3000 \
  --name=grafana \
  -v grafana-storage:/var/lib/grafana \
  grafana/grafana

Dashboard típico de monitorización:

Métrica Descripción Umbral de alerta
Container CPU Uso de CPU por contenedor >80% por 5min
Memory Usage Uso de memoria >90%
Disk I/O Operaciones de disco >5000 IOPS
Network Traffic Tráfico de red >100MB/s

Esto nos ayuda a identificar y mitigar problemas en el despliegue de contenedores. Nos provee de un dashboard para visualizar los datos de los contenedores donde también podemos configurar alertas y notificaciones.

Construye interfaces de usuarios personalizadas y atractivas
Lleva la formación de tu equipo al siguiente nivel con cursos, talleres y laboratorios prácticos de JavaScript, React, Angular, HTML, CSS y más.
Solicitar más información

Conclusiones

Docker ha revolucionado el desarrollo y despliegue de software. A través de mi experiencia implementando Docker en diferentes organizaciones, he visto cómo esta eficiencia permite a los equipos enfocarse en el desarrollo y las pruebas sin perder tiempo en configuraciones complejas.

Además, Docker elimina por completo el temido “works on my machine”. Al garantizar entornos homogéneos en cada fase del proyecto, facilita que las aplicaciones funcionen de forma consistente sin importar dónde se ejecuten.

Otro de sus grandes beneficios es la capacidad de escalar y mantener los sistemas de manera sencilla. Docker hace que desplegar nuevas versiones o ajustar recursos sea un proceso ágil y controlado, algo fundamental en entornos de producción.

La seguridad y el aislamiento también se ven reforzados. Cada contenedor funciona como una unidad independiente, reduciendo riesgos y mejorando el control sobre los servicios y aplicaciones que se ejecutan.

Por último, facilita la monitorización y el despliegue continuo, integrándose de forma natural en los flujos de trabajo modernos y contribuyendo a la eficiencia operativa de los equipos. Todo esto convierte a Docker en una herramienta imprescindible en el desarrollo de software actual.

Docker no es solo una herramienta: es un cambio de mentalidad. Elimina el “en mi máquina funciona”, acelera tus despliegues y escala sin miedo. 

Bombilla

Lo que deberías recordar de Docker

  • La containerización es el presente y el futuro del desarrollo.
  • La consistencia entre entornos es crítica.
  • La seguridad debe ser una prioridad desde el inicio.
  • La monitorización es esencial en producción.
  • Las mejores prácticas ahorran tiempo y recursos.
Compartir este post

También te puede interesar

Curso

Docker para desarrolladores

Principiante
4 h. y 4 min.

El objetivo de esta formación es ofrecer una comprensión integral sobre Docker, empezando por sus fundamentos básicos. Aprenderemos...

Ronald Cuzco
4.4
Curso

Gestión de contenedores con Podman

Intermedio
10 h. y 5 min.

En esta formación de gestión de contenedores con Podman, abarcaremos la creación, administración y despliegue de aplicaciones en...

José Domingo Muñoz
4.5