tutoriales.com

Desarrollo Local Ágil: Usando Docker para Entornos de Desarrollo Coherentes y Aislados

Este tutorial te guiará paso a paso en la creación de entornos de desarrollo locales con Docker. Descubrirás cómo configurar proyectos para que sean reproducibles, minimizando los problemas de "funciona en mi máquina" y acelerando el onboarding de nuevos desarrolladores. Abarcaremos desde los fundamentos hasta ejemplos prácticos con diferentes stacks tecnológicos.

Intermedio18 min de lectura40 views
Reportar error

🚀 Introducción al Desarrollo Local con Docker

¿Alguna vez has oído la frase "funciona en mi máquina"? Esta es una de las mayores frustraciones en el desarrollo de software. Diferencias en versiones de librerías, dependencias del sistema operativo, o configuraciones locales pueden transformar el proceso de configuración de un proyecto en una pesadilla. Aquí es donde Docker entra en juego como un salvador.

Docker nos permite empaquetar aplicaciones y sus dependencias en contenedores ligeros y portables. Estos contenedores son unidades autocontenidas que pueden ejecutarse de manera consistente en cualquier entorno que tenga Docker instalado. Para el desarrollo local, esto significa que tú y tu equipo pueden trabajar en un entorno idéntico al de producción, eliminando las inconsistencias y acelerando el proceso de desarrollo.

¿Por qué usar Docker para Desarrollo Local? 🤔

Existen múltiples razones por las que Docker se ha convertido en una herramienta indispensable para equipos de desarrollo:

  • Consistencia: Garantiza que todos los desarrolladores trabajen con las mismas versiones de bases de datos, lenguajes, librerías y dependencias.
  • Aislamiento: Cada proyecto puede tener su propio conjunto de dependencias sin conflictos con otros proyectos o con el sistema operativo anfitrión.
  • Portabilidad: Un entorno Docker se puede mover fácilmente entre máquinas, sistemas operativos (Windows, macOS, Linux) y la nube, manteniendo su funcionalidad.
  • Onboarding Rápido: Los nuevos miembros del equipo pueden poner en marcha un proyecto en minutos, sin necesidad de instalar manualmente decenas de herramientas.
  • Simulación de Producción: Permite probar tu aplicación en un entorno muy similar al de producción desde las primeras etapas del desarrollo.
  • Limpieza: Evita la "contaminación" de tu máquina local con instalaciones y configuraciones específicas de cada proyecto.
💡 Consejo: Piensa en Docker como una máquina virtual ligera para tu aplicación y sus dependencias, pero mucho más eficiente en el uso de recursos.

🛠️ Herramientas Necesarias

Antes de sumergirnos en la práctica, asegúrate de tener las siguientes herramientas instaladas en tu sistema:

  1. Docker Desktop: Incluye Docker Engine, Docker CLI, Docker Compose, Kubernetes y una interfaz gráfica opcional. Es la forma más sencilla de empezar en Windows y macOS. Para Linux, puedes instalar Docker Engine y Docker Compose por separado.
  2. Un editor de texto o IDE: VS Code, Sublime Text, IntelliJ IDEA, etc.
  3. Terminal: La terminal de tu sistema operativo (Bash, PowerShell, zsh).

📝 Fundamentos de Docker para Desarrollo Local

Para aprovechar Docker en tu flujo de trabajo, es crucial entender algunos conceptos básicos:

Imágenes y Contenedores

  • Imágenes: Son plantillas de solo lectura que contienen las instrucciones para crear un contenedor Docker. Piensa en ellas como un blueprint o una clase. Contienen el sistema operativo base, las aplicaciones, las librerías, las dependencias y la configuración. Se construyen a partir de un Dockerfile y se almacenan en un registro (como Docker Hub).
  • Contenedores: Son instancias ejecutables de una imagen. Son como las instancias de una clase, procesos aislados que se ejecutan sobre el kernel del sistema operativo anfitrión, pero con sus propias dependencias y sistema de archivos. Pueden iniciarse, detenerse, reiniciarse y eliminarse.

Dockerfile: La Receta de tu Entorno 🧑‍🍳

Un Dockerfile es un archivo de texto que contiene una serie de instrucciones para construir una imagen Docker. Cada instrucción crea una capa en la imagen, lo que permite una reconstrucción eficiente cuando solo cambian algunas partes.

Aquí hay un Dockerfile básico para una aplicación Node.js:

# Usa una imagen base de Node.js
FROM node:18-alpine

# Establece el directorio de trabajo dentro del contenedor
WORKDIR /app

# Copia los archivos de configuración de dependencias
COPY package*.json ./

# Instala las dependencias
RUN npm install

# Copia el resto del código de la aplicación
COPY .

# Expone el puerto que la aplicación escuchará
EXPOSE 3000

# Define el comando para iniciar la aplicación
CMD ["npm", "start"]

Docker Compose: Orquestación de Servicios Multi-Contenedor 🎻

Para la mayoría de los proyectos reales, no solo necesitas un contenedor, sino varios (por ejemplo, una aplicación web, una base de datos, un caché). Docker Compose es una herramienta que te permite definir y ejecutar aplicaciones multi-contenedor usando un archivo YAML. Con un solo comando, puedes levantar todo tu stack.

Un ejemplo de docker-compose.yml:

version: '3.8'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules # Evita que los node_modules del host sobrescriban los del contenedor
    environment:
      NODE_ENV: development
      DATABASE_URL: postgres://user:password@db:5432/mydb
    depends_on:
      - db

  db:
    image: postgres:13
    restart: always
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:
🔥 Importante: La coherencia es clave. Si tu equipo usa diferentes sistemas operativos, el `Dockerfile` y `docker-compose.yml` aseguran que todos compartan el mismo entorno, eliminando la mayoría de los problemas de compatibilidad.

🚶‍♀️ Paso a Paso: Configurando un Entorno de Desarrollo con Docker

Vamos a construir un entorno de desarrollo para una aplicación web sencilla, utilizando Node.js como backend y PostgreSQL como base de datos.

🎯 Estructura del Proyecto

Crearemos la siguiente estructura de directorios:

my-docker-app/
├── .dockerignore
├── Dockerfile
├── docker-compose.yml
├── package.json
├── server.js
└── ... (otros archivos de tu aplicación)

Paso 1: Inicializar el Proyecto y Crear package.json

En tu terminal, crea una nueva carpeta para tu proyecto y entra en ella:

mkdir my-docker-app
cd my-docker-app

Luego, crea un archivo package.json para tu aplicación Node.js. Por ejemplo:

{
  "name": "my-docker-app",
  "version": "1.0.0",
  "description": "Simple Node.js app with Docker",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js" 
  },
  "dependencies": {
    "express": "^4.17.1",
    "pg": "^8.7.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.15"
  }
}

Paso 2: Crear el Archivo server.js

Este será un servidor Node.js Express muy básico que se conectará a PostgreSQL:

const express = require('express');
const { Pool } = require('pg');

const app = express();
const port = process.env.PORT || 3000;

// Configuración de la base de datos desde variables de entorno
const pool = new Pool({
  user: process.env.POSTGRES_USER || 'user',
  host: process.env.POSTGRES_HOST || 'localhost',
  database: process.env.POSTGRES_DB || 'mydb',
  password: process.env.POSTGRES_PASSWORD || 'password',
  port: process.env.POSTGRES_PORT || 5432,
});

app.get('/', async (req, res) => {
  try {
    const client = await pool.connect();
    const result = await client.query('SELECT NOW() as current_time');
    client.release();
    res.send(`Hola desde Docker! La hora actual de la DB es: ${result.rows[0].current_time}`);
  } catch (err) {
    console.error('Error al conectar o consultar la base de datos', err);
    res.status(500).send('Error al conectar a la base de datos.');
  }
});

app.listen(port, () => {
  console.log(`Aplicación escuchando en http://localhost:${port}`);
});

Paso 3: Escribir el Dockerfile para la Aplicación Node.js

Crea un archivo llamado Dockerfile (sin extensión) en la raíz de tu proyecto con el siguiente contenido:

# Usa una imagen base de Node.js
FROM node:18-alpine

# Establece el directorio de trabajo dentro del contenedor
WORKDIR /app

# Copia los archivos de configuración de dependencias
COPY package*.json ./

# Instala las dependencias. Usa --no-cache para limpiar el cache de npm.
RUN npm install

# Copia el resto del código de la aplicación
COPY . .

# Expone el puerto que la aplicación escuchará
EXPOSE 3000

# Define el comando para iniciar la aplicación en modo desarrollo con nodemon
CMD ["npm", "run", "dev"]

Paso 4: Crear el Archivo .dockerignore

Similar a .gitignore, este archivo le dice a Docker qué archivos y directorios debe ignorar al construir la imagen. Esto acelera la construcción y reduce el tamaño de la imagen. Crea .dockerignore:

node_modules
npm-debug.log
.git
.gitignore
.env

Paso 5: Definir el docker-compose.yml

Crea un archivo llamado docker-compose.yml en la raíz de tu proyecto:

version: '3.8'
services:
  web:
    build: .
    ports:
      - "3000:3000" # Mapea el puerto 3000 del host al 3000 del contenedor
    volumes:
      - .:/app # Monta el directorio actual del host en /app del contenedor
      - /app/node_modules # Excluye el directorio node_modules del host del montaje
    environment:
      NODE_ENV: development
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
      POSTGRES_HOST: db # El nombre del servicio de base de datos en Docker Compose
      POSTGRES_PORT: 5432
    depends_on:
      - db # Asegura que la DB inicie antes que la app web

  db:
    image: postgres:13
    restart: always
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - db_data:/var/lib/postgresql/data # Persistencia de datos para la DB

volumes:
  db_data: # Define un volumen para la persistencia de datos de PostgreSQL
¿Por qué `/app/node_modules` en `volumes`? Esta línea ` - /app/node_modules` dentro del servicio `web` es crucial. Si no la incluyes, el `node_modules` que se instaló dentro del contenedor (por el `Dockerfile`) sería sobrescrito por un `node_modules` posiblemente vacío o inexistente de tu máquina local debido al montaje `.:/app`. Al especificarlo, le dices a Docker que cree un volumen anónimo para ese directorio dentro del contenedor, manteniendo las dependencias instaladas dentro de él.

Paso 6: Levantar el Entorno de Desarrollo ⬆️

Ahora, desde la raíz de tu proyecto (my-docker-app/), ejecuta el siguiente comando:

docker-compose up --build
  • up: Inicia los servicios definidos en docker-compose.yml.
  • --build: Fuerza a Docker Compose a reconstruir las imágenes antes de iniciar los contenedores (útil cuando haces cambios en el Dockerfile).

Verás cómo Docker descarga las imágenes necesarias (Node.js, PostgreSQL), construye la imagen de tu aplicación y levanta ambos contenedores. Una vez que los logs se estabilicen y veas el mensaje Aplicación escuchando en http://localhost:3000, tu entorno estará listo.

Paso 7: Acceder a tu Aplicación

Abre tu navegador y ve a http://localhost:3000. Deberías ver un mensaje similar a: "Hola desde Docker! La hora actual de la DB es: [fecha y hora]".

Paso 8: Detener y Limpiar el Entorno ⬇️

Para detener los contenedores (sin eliminarlos), presiona Ctrl + C en tu terminal. Para detener y eliminar los contenedores, redes y volúmenes anónimos (pero no los volúmenes nombrados como db_data):

docker-compose down

Para detener y eliminar todo, incluyendo los volúmenes nombrados (cuidado, esto borrará los datos de tu base de datos):

docker-compose down -v
⚠️ Advertencia: El comando `docker-compose down -v` eliminará los datos de tu base de datos. ¡Úsalo con precaución!

✨ Mejoras y Buenas Prácticas para Entornos Dockerizados

💡 Hot-Reloading para Desarrollo Frontend/Backend

Para el desarrollo web, es esencial que los cambios en el código se reflejen automáticamente sin reiniciar el servidor manualmente. Ya configuramos nodemon en nuestro package.json y Dockerfile para Node.js.

El mapeo de volúmenes (.:/app) en docker-compose.yml es lo que permite esto. Docker comparte el directorio de tu proyecto local con el contenedor, por lo que nodemon (o herramientas similares como Webpack Dev Server, Vite, etc.) dentro del contenedor detectarán los cambios en los archivos locales y recargarán la aplicación.

Variables de Entorno y .env 🔑

Es una buena práctica gestionar las variables de entorno de manera externa. Docker Compose soporta un archivo .env en la misma carpeta que docker-compose.yml.

Crea un archivo .env en my-docker-app/:

POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydb_dev

Ahora, en tu docker-compose.yml, puedes referenciar estas variables. Las variables definidas bajo environment: en el docker-compose.yml tienen prioridad, pero puedes eliminar las que quieras que se tomen del .env global.

# ... (parte superior de docker-compose.yml)
  web:
    # ...
    environment:
      NODE_ENV: development
      # POSTGRES_USER: user  <-- Estas ya no serían necesarias si vienen del .env
      # POSTGRES_PASSWORD: password
      # POSTGRES_DB: mydb
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
    # ...

  db:
    image: postgres:13
    restart: always
    environment:
      # POSTGRES_USER: user  <-- Estas también se pueden omitir si vienen del .env
      # POSTGRES_PASSWORD: password
      # POSTGRES_DB: mydb
      PGDATA: /var/lib/postgresql/data/pgdata # Ruta custom para los datos de postgres
    volumes:
      - db_data:/var/lib/postgresql/data/pgdata # Asegúrate de que coincida con PGDATA

volumes:
  db_data:
⚠️ Advertencia: Nunca subas tus archivos `.env` con credenciales sensibles a sistemas de control de versiones como Git. Asegúrate de añadirlos a tu `.gitignore` (`.env`).

Integración con tu IDE (VS Code Dev Containers) 💻

Para una experiencia de desarrollo Dockerizada aún más fluida, herramientas como VS Code ofrecen extensiones de "Dev Containers". Esto te permite abrir tu proyecto dentro de un contenedor Docker. Tu IDE se ejecuta en tu máquina local, pero todas las herramientas, extensiones y terminales operan dentro del contenedor, garantizando un entorno 100% consistente.

Host OS Docker Container VS Code IDE (Local) Código Fuente Runtime & Herramientas Dev Protocol Volume Mount

Para configurar VS Code Dev Containers, necesitas un archivo .devcontainer/devcontainer.json.

{
  "name": "Node.js & PostgreSQL (Docker Compose)",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "web",
  "workspaceFolder": "/app",
  "settings": {
    "terminal.integrated.defaultProfile.linux": "bash"
  },
  "extensions": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "ms-azuretools.vscode-docker",
    "ms-vscode.vscode-typescript-javascript-grammar",
    "eg2.vscode-npm-script"
  ],
  "remoteUser": "node"
}

Con esto, al abrir el proyecto en VS Code, te preguntará si quieres "Reabrir en Contenedor". Al aceptar, VS Code iniciará los servicios de Docker Compose, conectará el IDE a tu contenedor web, y podrás usar la terminal del IDE directamente en el contexto del contenedor, con todas las dependencias instaladas.

Gestión de Volúmenes para Persistencia de Datos 💾

Ya usamos un volumen nombrado (db_data) para PostgreSQL. Esto es crucial para asegurar que los datos de tu base de datos persistan incluso si eliminas los contenedores. Sin un volumen, cada vez que levantes el contenedor de la DB, empezarías con una base de datos vacía.

Tipo de VolumenDescripciónCaso de UsoVentajasDesventajas
---------------
Volúmenes NombradosGestionados por Docker, ideal para datos persistentes.Bases de datos, cachés, registros.Fácil de gestionar, alta eficiencia.No directamente accesible desde el host.
Montajes BindEnlaza un directorio del host con uno del contenedor.Desarrollo local de código fuente.Refleja cambios en tiempo real, accesible en el host.Depende de la estructura de archivos del host.
---------------
Volúmenes AnónimosCreados automáticamente por Docker, sin nombre específico.Datos temporales, node_modules.Simple, automático.Difícil de reutilizar o inspeccionar, se borran al eliminar el contenedor.
90% Persistencia asegurada

Docker Redes para Comunicación entre Contenedores 🕸️

Docker Compose crea automáticamente una red de puente (bridge network) para todos los servicios definidos en el docker-compose.yml. Esto permite que los contenedores se comuniquen entre sí usando los nombres de sus servicios como nombres de host. Por eso, en server.js y docker-compose.yml, usamos db como POSTGRES_HOST en lugar de localhost o una IP.

Paso 1: Docker Compose crea una red (`my-docker-app_default`).
Paso 2: El servicio `web` se une a la red.
Paso 3: El servicio `db` se une a la red.
Paso 4: `web` puede resolver `db` a la IP del contenedor de PostgreSQL.

🔄 Flujo de Trabajo Típico con Docker para Desarrollo

  1. Clonar el repositorio: Obtén el código de tu proyecto.
git clone https://github.com/tu_usuario/tu_proyecto.git
cd tu_proyecto
  1. Configurar .env: Copia el archivo de ejemplo (.env.example) a .env y rellena las credenciales.
cp .env.example .env
# Edita .env con tus valores
  1. Levantar el entorno: Inicia todos los servicios necesarios.
docker-compose up --build
  1. Desarrollar: Edita tu código. Gracias a los volúmenes, los cambios se reflejarán en el contenedor y, si tienes hot-reloading configurado, tu aplicación se actualizará automáticamente. Puedes ejecutar comandos en el contenedor:
docker-compose exec web npm test # Ejecuta tests dentro del contenedor 'web'
docker-compose exec db psql -U user mydb # Conecta a la DB dentro del contenedor 'db'
  1. Detener el entorno: Cuando termines de trabajar, detén los servicios.
docker-compose down
💡 Consejo: Usa `docker-compose up -d` para iniciar los contenedores en segundo plano (detached mode). Luego, puedes ver los logs con `docker-compose logs -f` y detenerlos con `docker-compose down`.

🔚 Conclusión

Docker es una herramienta transformadora para el desarrollo local, ofreciendo coherencia, aislamiento y portabilidad que resuelven muchos de los dolores de cabeza comunes en los equipos de desarrollo. Al invertir tiempo en dockerizar tus entornos, estarás construyendo una base sólida para un desarrollo más rápido, colaborativo y menos propenso a errores.

Desde un simple Dockerfile hasta la orquestación con Docker Compose y la integración con IDEs, has visto cómo configurar un entorno completo y reproducible. ¡Es hora de dejar atrás los problemas de configuración y abrazar un flujo de trabajo más eficiente y moderno!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!