Compartir comentarios
Las respuestas se generan en base a la documentación.

Utiliza contenedores para el desarrollo con Node.js

Requisitos previos

Completa la sección de Contenerizar una aplicación Node.js.

Descripción general

En esta sección, aprenderás a configurar un entorno de desarrollo para tu aplicación contenerizada. Esto incluye:

  • Agregar una base de datos local y persistir los datos.
  • Configurar tu contenedor para ejecutar un entorno de desarrollo.
  • Depurar tu aplicación contenerizada.

Agrega una base de datos local y persiste los datos

La aplicación utiliza PostgreSQL para la persistencia de datos. Agrega un servicio de base de datos a tu configuración de Docker Compose.

Agrega el servicio de base de datos a Docker Compose

Si aún no has creado un archivo compose.yml en la sección anterior, o si necesitas agregar el servicio de base de datos, update tu archivo compose.yml para incluir el servicio de la base de datos PostgreSQL:

services:
  # ... servicios de aplicación existentes ...

  # ========================================
  # Servicio de base de datos PostgreSQL
  # ========================================
  db:
    image: postgres:18-alpine
    container_name: todoapp-db
    environment:
      POSTGRES_DB: '${POSTGRES_DB:-todoapp}'
      POSTGRES_USER: '${POSTGRES_USER:-todoapp}'
      POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-todoapp_password}'
    volumes:
      - postgres_data:/var/lib/postgresql
    ports:
      - '${DB_PORT:-5432}:5432'
    restart: unless-stopped
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER:-todoapp} -d ${POSTGRES_DB:-todoapp}']
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 5s
    networks:
      - todoapp-network

# ========================================
# Configuración de volúmenes
# ========================================
volumes:
  postgres_data:
    name: todoapp-postgres-data
    driver: local

# ========================================
# Configuración de red
# ========================================
networks:
  todoapp-network:
    name: todoapp-network
    driver: bridge

Actualiza el servicio de tu aplicación

Asegúrate de que el servicio de tu aplicación en compose.yml esté configurado para conectarse a la base de datos:

compose.yml
services:
  app-dev:
    build:
      context: .
      dockerfile: Dockerfile
      target: development
    container_name: todoapp-dev
    ports:
      - '${APP_PORT:-3000}:3000' # Servidor API
      - '${VITE_PORT:-5173}:5173' # Servidor de desarrollo de Vite
      - '${DEBUG_PORT:-9229}:9229' # Depurador de Node.js
    environment:
      NODE_ENV: development
      DOCKER_ENV: 'true'
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      POSTGRES_DB: todoapp
      POSTGRES_USER: todoapp
      POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-todoapp_password}'
      ALLOWED_ORIGINS: '${ALLOWED_ORIGINS:-http://localhost:3000,http://localhost:5173}'
    volumes:
      - ./src:/app/src:ro
      - ./package.json:/app/package.json
      - ./vite.config.ts:/app/vite.config.ts:ro
      - ./tailwind.config.js:/app/tailwind.config.js:ro
      - ./postcss.config.js:/app/postcss.config.js:ro
    depends_on:
      db:
        condition: service_healthy
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
          ignore:
            - '**/*.test.*'
            - '**/__tests__/**'
        - action: rebuild
          path: ./package.json
        - action: sync
          path: ./vite.config.ts
          target: /app/vite.config.ts
        - action: sync
          path: ./tailwind.config.js
          target: /app/tailwind.config.js
        - action: sync
          path: ./postcss.config.js
          target: /app/postcss.config.js
    restart: unless-stopped
    networks:
      - todoapp-network

  db:
    image: postgres:18-alpine
    container_name: todoapp-db
    environment:
      POSTGRES_DB: '${POSTGRES_DB:-todoapp}'
      POSTGRES_USER: '${POSTGRES_USER:-todoapp}'
      POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-todoapp_password}'
    volumes:
      - postgres_data:/var/lib/postgresql
    ports:
      - '${DB_PORT:-5432}:5432'
    restart: unless-stopped
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER:-todoapp} -d ${POSTGRES_DB:-todoapp}']
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 5s
    networks:
      - todoapp-network

volumes:
  postgres_data:
    name: todoapp-postgres-data
    driver: local

networks:
  todoapp-network:
    name: todoapp-network
    driver: bridge
  1. La configuración de la base de datos PostgreSQL se gestiona automáticamente por la aplicación. La base de datos se crea e inicializa cuando la aplicación se inicia, y los datos persisten mediante el volumen postgres_data.

  2. Configura tu entorno copiando el archivo de ejemplo:

    $ cp .env.example .env
    

    Actualiza el archivo .env con tus preferencias:

    # Configuración de la aplicación
    NODE_ENV=development
    APP_PORT=3000
    VITE_PORT=5173
    DEBUG_PORT=9230
    
    # Configuración de la base de datos
    POSTGRES_HOST=db
    POSTGRES_PORT=5432
    POSTGRES_DB=todoapp
    POSTGRES_USER=todoapp
    POSTGRES_PASSWORD=todoapp_password
    
    # Configuración de seguridad
    ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
  3. Ejecuta el siguiente comando para iniciar tu aplicación en modo de desarrollo:

    $ docker compose up app-dev --build
    
  4. Abre un navegador y verifica que la aplicación se esté ejecutando en http://localhost:5173 para el frontend o en http://localhost:3000 para la API. El frontend de React es servido por el servidor de desarrollo de Vite en el puerto 5173, y las llamadas a la API se reenvían al servidor Express en el puerto 3000.

  5. Agrega algunos elementos a la lista de tareas para probar la persistencia de datos.

  6. Después de agregar algunos elementos a la lista de tareas, presiona CTRL + C en la terminal para detener la aplicación.

  7. Ejecuta la aplicación de nuevo:

    $ docker compose up app-dev
    
  8. Actualiza http://localhost:5173 en tu navegador y verifica que los elementos de la lista de tareas persisten, incluso después de que los contenedores hayan sido eliminados y ejecutados de nuevo.

Configura y ejecuta un contenedor de desarrollo

Puedes utilizar un montaje de tipo bind (bind mount) para montar tu código fuente en el contenedor. De este modo, el contenedor podrá ver los cambios que realices en el código de forma inmediata, en cuanto guardes un archivo. Esto significa que puedes ejecutar procesos, como nodemon, dentro del contenedor para vigilar los cambios en el sistema de archivos y responder a ellos. Para conocer más sobre los montajes de tipo bind, consulta la descripción general de almacenamiento.

Además de agregar un montaje bind, puedes configurar tu Dockerfile y tu archivo compose.yaml para instalar dependencias de desarrollo y ejecutar herramientas de desarrollo.

Actualiza tu Dockerfile para el desarrollo

Tu Dockerfile debe estar configurado como una construcción multi-etapa con etapas independientes para desarrollo, producción y pruebas. Si seguiste la sección anterior, tu Dockerfile ya incluye una etapa de desarrollo que cuenta con todas las dependencias de desarrollo y ejecuta la aplicación con la recarga en caliente habilitada.

Esta es la etapa de desarrollo de tu Dockerfile multi-etapa:

Dockerfile
# ========================================
# Etapa de desarrollo
# ========================================
FROM build-deps AS development

# Establecer el entorno
ENV NODE_ENV=development \
    NPM_CONFIG_LOGLEVEL=warn

# Copiar archivos fuente
COPY . .

# Asegurar que todos los directorios tengan los permisos adecuados
RUN mkdir -p /app/node_modules/.vite && \
    chown -R nodejs:nodejs /app && \
    chmod -R 755 /app

# Cambiar a usuario no root
USER nodejs

# Exponer puertos
EXPOSE 3000 5173 9229

# Iniciar el servidor de desarrollo
CMD ["npm", "run", "dev:docker"]

La etapa de desarrollo:

  • Instala todas las dependencias, incluidas las de desarrollo (dev dependencies).
  • Expone puertos para el servidor API (3000), el servidor de desarrollo de Vite (5173) y el depurador de Node.js (9229).
  • Ejecuta npm run dev, que inicia tanto el servidor Express como el servidor de desarrollo de Vite de forma concurrente.
  • Incluye comprobaciones de estado para monitorear el estado del contenedor.

A continuación, tendrás que actualizar tu archivo de Compose para utilizar esta nueva etapa.

Actualiza tu archivo de Compose para el desarrollo

Actualiza tu archivo compose.yml para ejecutar la etapa de desarrollo con montajes bind para la recarga en caliente:

compose.yml
services:
  app-dev:
    build:
      context: .
      dockerfile: Dockerfile
      target: development
    container_name: todoapp-dev
    ports:
      - '${APP_PORT:-3000}:3000' # Servidor API
      - '${VITE_PORT:-5173}:5173' # Servidor de desarrollo de Vite
      - '${DEBUG_PORT:-9229}:9229' # Depurador de Node.js
    environment:
      NODE_ENV: development
      DOCKER_ENV: 'true'
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      POSTGRES_DB: todoapp
      POSTGRES_USER: todoapp
      POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-todoapp_password}'
      ALLOWED_ORIGINS: '${ALLOWED_ORIGINS:-http://localhost:3000,http://localhost:5173}'
    volumes:
      - ./src:/app/src:ro
      - ./package.json:/app/package.json
      - ./vite.config.ts:/app/vite.config.ts:ro
      - ./tailwind.config.js:/app/tailwind.config.js:ro
      - ./postcss.config.js:/app/postcss.config.js:ro
    depends_on:
      db:
        condition: service_healthy
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
          ignore:
            - '**/*.test.*'
            - '**/__tests__/**'
        - action: rebuild
          path: ./package.json
        - action: sync
          path: ./vite.config.ts
          target: /app/vite.config.ts
        - action: sync
          path: ./tailwind.config.js
          target: /app/tailwind.config.js
        - action: sync
          path: ./postcss.config.js
          target: /app/postcss.config.js
    restart: unless-stopped
    networks:
      - todoapp-network

Características clave de la configuración de desarrollo:

  • Exposición multipuerto: Servidor API (3000), servidor de desarrollo de Vite (5173) y depurador (9229).
  • Montajes bind completos: Código fuente, archivos de configuración y archivos de paquete para recarga en caliente.
  • Variables de entorno: Configurables a través del archivo .env o valores por defecto.
  • Base de datos PostgreSQL: Base de datos lista para producción con almacenamiento persistente.
  • Docker Compose watch: Sincronización automática de archivos y reconstrucción de contenedores.
  • Comprobaciones de estado: Monitoreo del estado de salud de la base de datos con gestión automática de dependencias.

Ejecuta tu contenedor de desarrollo y depura tu aplicación

Ejecuta el siguiente comando para iniciar tu aplicación con la configuración de desarrollo:

$ docker compose up app-dev --build

O bien, con observación de archivos para actualizaciones automáticas:

$ docker compose up app-dev --watch

Para el desarrollo local sin Docker:

$ npm run dev:with-db

O inicia los servicios por separado:

$ npm run db:start    # Iniciar el contenedor de PostgreSQL
$ npm run dev         # Iniciar tanto el servidor como el cliente

Uso de Task Runner (alternativa)

El proyecto incluye un archivo Taskfile.yml para flujos de trabajo avanzados:

# Desarrollo
$ task dev              # Iniciar el entorno de desarrollo
$ task dev:build        # Construir la imagen de desarrollo
$ task dev:run          # Ejecutar el contenedor de desarrollo

# Producción
$ task build            # Construir la imagen de producción
$ task run              # Ejecutar el contenedor de producción
$ task build-run        # Construir y ejecutar en un solo paso

# Pruebas
$ task test             # Ejecutar todas las pruebas
$ task test:unit        # Ejecutar pruebas unitarias con cobertura
$ task test:lint        # Ejecutar análisis estático (linting)

# Kubernetes
$ task k8s:deploy       # Desplegar en Kubernetes
$ task k8s:status       # Comprobar el estado del despliegue
$ task k8s:logs         # Ver los logs de los pods

# Utilidades
$ task clean            # Limpiar contenedores e imágenes
$ task health           # Comprobar el estado de salud de la aplicación
$ task logs             # Ver los logs del contenedor

La aplicación se iniciará con el servidor API Express y el servidor de desarrollo de Vite:

Cualquier cambio en los archivos fuente de la aplicación en tu máquina local se reflejará inmediatamente en el contenedor en ejecución gracias a los montajes bind.

Intenta realizar un cambio para probar la recarga en caliente:

  1. Abre src/client/components/TodoApp.tsx en un IDE o editor de texto.

  2. Actualiza el texto del encabezado principal:

    - <h1 className="text-3xl font-bold text-gray-900 mb-8">
    -   Modern Todo App
    - </h1>
    + <h1 className="text-3xl font-bold text-gray-900 mb-8">
    +   My Todo App
    + </h1>
    
  3. Guarda el archivo y el servidor de desarrollo de Vite recargará la página automáticamente con tus cambios.

Soporte de depuración:

Puedes conectar un depurador a tu aplicación en el puerto 9229. El inspector de Node.js está habilitado con --inspect=0.0.0.0:9230 en el script de desarrollo (dev:server).

Configuración del depurador en VS Code

  1. Crea una configuración de lanzamiento en .vscode/launch.json:

    {
      "version": "0.2.0",
      "configurations": [
        {
          "name": "Adjuntar al contenedor Docker",
          "type": "node",
          "request": "attach",
          "port": 9229,
          "address": "localhost",
          "localRoot": "${workspaceFolder}",
          "remoteRoot": "/app",
          "protocol": "inspector",
          "restart": true,
          "sourceMaps": true,
          "skipFiles": ["<node_internals>/**"]
        }
      ]
    }
  2. Inicia tu contenedor de desarrollo:

    docker compose up app-dev --build
    
  3. Adjunta el depurador:

    • Abre VS Code.
    • En el panel de depuración (Ctrl/Cmd + Shift + D), selecciona Adjuntar al contenedor Docker en el menú desplegable.
    • Selecciona el botón verde de reproducción o presiona F5.

Chrome DevTools (alternativa)

También puedes usar Chrome DevTools para la depuración:

  1. Inicia tu contenedor (si aún no se está ejecutando):

    docker compose up app-dev --build
    
  2. Abre Chrome y ve a chrome://inspect.

  3. En la opción Configure (Configurar), agrega:

    localhost:9229
  4. Cuando aparezca tu objetivo (target) de Node.js, selecciona inspect.

Detalles de la configuración de depuración

La configuración de depuración:

  • Puerto del contenedor: 9230 (puerto interno del depurador)
  • Puerto del host: 9229 (puerto externo mapeado)
  • Script: tsx watch --inspect=0.0.0.0:9230 src/server/index.ts

El depurador escucha en todas las interfaces (0.0.0.0) dentro del contenedor en el puerto 9230 y es accesible en el puerto 9229 desde tu máquina host.

Solución de problemas de conexión del depurador

Si el depurador no se conecta:

  1. Comprueba si el contenedor se está ejecutando:

    docker ps
    
  2. Comprueba si el puerto está expuesto:

    docker port todoapp-dev
    
  3. Comprueba los logs del contenedor:

    docker compose logs app-dev
    

    Deberías ver un mensaje como:

    Debugger listening on ws://0.0.0.0:9230/...

Ahora puedes establecer puntos de interrupción (breakpoints) en tus archivos fuente de TypeScript y depurar tu aplicación Node.js contenerizada.

Para obtener más detalles sobre la depuración en Node.js, consulta la documentación oficial de Node.js.

Resumen

Has configurado tu archivo de Compose con una base de datos PostgreSQL y persistencia de datos. También creaste un Dockerfile multi-etapa y configuraste montajes bind para el desarrollo.

Información relacionada:

Próximos pasos

En la siguiente sección, aprenderás cómo ejecutar pruebas unitarias utilizando Docker.