# Conteneriza una aplicación Node.js


## Requisitos previos

Antes de comenzar, asegúrate de que las siguientes herramientas estén instaladas y disponibles en tu sistema:

- Has instalado la última versión de [Docker Desktop](/get-started/get-docker/).
- Tienes un [cliente de git](https://git-scm.com/downloads). Los ejemplos de esta sección utilizan un cliente de git basado en la línea de comandos, pero puedes usar cualquier cliente.

> **¿Eres nuevo en Docker?**  
> Comienza con la guía de [conceptos básicos de Docker](/get-started/docker-concepts/the-basics/what-is-a-container/) para familiarizarte con conceptos clave como imágenes, contenedores y Dockerfiles.

---

## Descripción general

Esta guía te acompaña a lo largo del proceso completo de contenerización de una aplicación Node.js con Docker. Aprenderás a crear una imagen de Docker lista para producción utilizando las mejores prácticas que mejoran el rendimiento, la seguridad, la escalabilidad y la eficiencia operativa.

Al final de esta guía, lograrás:

- Contenerizar una aplicación Node.js usando Docker.
- Crear y optimizar un Dockerfile adaptado a entornos Node.js.
- Utilizar construcciones multi-etapa (multi-stage builds) para separar las dependencias y reducir el tamaño de la imagen.
- Configurar el contenedor para un tiempo de ejecución seguro y eficiente utilizando un usuario no root.
- Seguir las mejores prácticas para construir imágenes de Docker seguras, ligeras y fáciles de mantener.

## Obtén la aplicación de ejemplo

Clona la aplicación de ejemplo para usarla con esta guía. Abre una terminal, cambia al directorio en el que deseas trabajar y ejecuta el siguiente comando para clonar el repositorio de git:

```console
$ git clone https://github.com/kristiyan-velkov/docker-nodejs-sample
```

## Genera un Dockerfile

Docker proporciona una herramienta de CLI interactiva llamada `docker init` que ayuda a estructurar los archivos de configuración necesarios para contenerizar tu aplicación. Esto incluye generar un `Dockerfile`, `.dockerignore`, `compose.yaml` y `README.Docker.md`.

Para comenzar, navega a la raíz del directorio de tu proyecto:

```console
$ cd docker-nodejs-sample
```

Luego ejecuta el siguiente comando:

```console
$ docker init
```

Verás una salida similar a:

```text
Welcome to the Docker Init CLI

This utility will walk you through creating the following files with sensible defaults for your project:
  - .dockerignore
  - Dockerfile
  - compose.yaml
  - README.Docker.md

Let's get started!
```

La CLI te hará algunas preguntas sobre la configuración de tu aplicación.
Para mantener la coherencia, utiliza las mismas respuestas que se muestran en el siguiente ejemplo cuando se te solicite:

| Pregunta | Respuesta |
|------------------------------------------------------------|-----------------|
| What application platform does your project use? | Node |
| What version of Node do you want to use? | 24.11.1-alpine |
| Which package manager do you want to use? | npm |
| Do you want to run "npm run build" before starting server? | yes |
| What directory is your build output to? | dist |
| What command do you want to use to start the app? | npm run dev |
| What port does your server listen on? | 3000 |

Después de completarlo, el directorio de tu proyecto contendrá los siguientes archivos nuevos:

```text
├── docker-nodejs-sample/
│ ├── Dockerfile
│ ├── .dockerignore
│ ├── compose.yaml
│ └── README.Docker.md
```

## Crea un archivo de Docker Compose

Aunque `docker init` genera un archivo `compose.yaml` básico, necesitarás crear una configuración más completa para esta aplicación full-stack. Reemplaza el archivo `compose.yaml` generado por una configuración lista para producción.

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

```yaml
# ========================================
# Configuración de Docker Compose
# Aplicación de tareas (Todo) moderna en Node.js
# ========================================

services:
  # ========================================
  # Servicio de desarrollo
  # ========================================
  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

  # ========================================
  # Servicio de producción
  # ========================================
  app-prod:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    container_name: todoapp-prod
    ports:
      - '${PROD_PORT:-8080}:3000'
    environment:
      NODE_ENV: production
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      POSTGRES_DB: todoapp
      POSTGRES_USER: todoapp
      POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-todoapp_password}'
      ALLOWED_ORIGINS: '${ALLOWED_ORIGINS:-https://tudominio.com}'
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: '${PROD_MEMORY_LIMIT:-2G}'
          cpus: '${PROD_CPU_LIMIT:-1.0}'
        reservations:
          memory: '${PROD_MEMORY_RESERVATION:-512M}'
          cpus: '${PROD_CPU_RESERVATION:-0.25}'
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp
    networks:
      - todoapp-network
    profiles:
      - prod

  # ========================================
  # 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
```

Esta configuración de Docker Compose incluye:

- **Servicio de desarrollo** (`app-dev`): Entorno de desarrollo completo con recarga en caliente (hot reload), soporte de depuración y montajes de tipo bind (bind mounts).
- **Servicio de producción** (`app-prod`): Despliegue de producción optimizado con límites de recursos y endurecimiento de seguridad (security hardening).
- **Servicio de base de datos** (`db`): PostgreSQL 18 con almacenamiento persistente y comprobaciones de estado.
- **Redes**: Red aislada para una comunicación segura entre servicios.
- **Volúmenes**: Almacenamiento persistente para los datos de la base de datos.

## Crea la configuración del entorno

Crea un archivo `.env` para configurar los ajustes de tu aplicación:

```console
$ cp .env.example .env
```

Actualiza el archivo `.env` con tus preferencias:

```env
# Configuración de la aplicación
NODE_ENV=development
APP_PORT=3000
VITE_PORT=5173
DEBUG_PORT=9229

# Configuración de producción
PROD_PORT=8080
PROD_MEMORY_LIMIT=2G
PROD_CPU_LIMIT=1.0
PROD_MEMORY_RESERVATION=512M
PROD_CPU_RESERVATION=0.25

# Configuración de la base de datos
POSTGRES_HOST=db
POSTGRES_PORT=5432
POSTGRES_DB=todoapp
POSTGRES_USER=todoapp
POSTGRES_PASSWORD=todoapp_password
DB_PORT=5432

# Configuración de seguridad
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
```

---

## Construye la imagen de Docker

El Dockerfile predeterminado generado por `docker init` proporciona una base confiable para aplicaciones Node.js estándar. Sin embargo, dado que este proyecto es una aplicación TypeScript full-stack que incluye tanto una API backend como componentes React de frontend, el Dockerfile debe ser personalizado para soportar y optimizar mejor esta arquitectura específica.

### Revisa los archivos generados

En el siguiente paso, mejorarás el Dockerfile y los archivos de configuración siguiendo las mejores prácticas:

- Utiliza construcciones multi-etapa para mantener la imagen final limpia y pequeña.
- Mejora el rendimiento y la seguridad incluyendo únicamente lo necesario.

Estas actualizaciones hacen que tu aplicación sea más fácil de desplegar y más rápida de cargar.

> [!NOTE]
> Un `Dockerfile` es un archivo de texto plano que contiene instrucciones paso a paso para construir una imagen de Docker. Automatiza el empaquetado de tu aplicación junto con sus dependencias y entorno de ejecución.  
> Para obtener detalles completos, consulta la [referencia de Dockerfile](/reference/dockerfile/).

### Paso 1: Configura el Dockerfile

Antes de crear un Dockerfile, debes elegir una imagen base. Puedes utilizar la [imagen oficial de Node.js](https://hub.docker.com/_/node) o una Imagen Endurecida de Docker (DHI) del [catálogo de imágenes endurecidas](https://hub.docker.com/hardened-images/catalog).

Elegir una DHI ofrece la ventaja de contar con una imagen lista para producción que es ligera y segura. Para más información, consulta [Docker Hardened Images](https://docs-docker.esdocu.com/dhi/).

> [!IMPORTANT]
> Esta guía utiliza una etiqueta de imagen estable LTS de Node.js que se considera segura en el momento de escribir la guía. Dado que se publican nuevas versiones y parches de seguridad con regularidad, la etiqueta mostrada aquí podría no ser la opción más segura cuando sigas la guía. Revisa siempre las últimas etiquetas de imagen disponibles y selecciona una versión segura y actualizada antes de construir o desplegar tu aplicación.
>
> Imágenes oficiales de Node.js en Docker: https://hub.docker.com/_/node

**Usando Docker Hardened Images**


Las imágenes endurecidas de Docker (DHIs) para Node.js están disponibles en el [catálogo de Docker Hardened Images](https://hub.docker.com/hardened-images/catalog/dhi/node). Las Docker Hardened Images están disponibles de forma gratuita para todos sin necesidad de suscripción. Puedes descargarlas y utilizarlas como cualquier otra imagen de Docker tras iniciar sesión en el registro de DHI. Para más información, consulta la guía de [inicio rápido de DHI](/dhi/get-started/).

1. Inicia sesión en el registro de DHI:

   $ docker login dhi.io

2. Descarga la DHI de Node.js (consulta el catálogo para ver las versiones disponibles):

   $ docker pull dhi.io/node:24-alpine3.22-dev

En el siguiente Dockerfile, la instrucción `FROM` utiliza `dhi.io/node:24-alpine3.22-dev` como imagen base.

```dockerfile
# ========================================
# Dockerfile multi-etapa optimizado
# Aplicación Node.js TypeScript (usando DHI)
# ========================================

FROM dhi.io/node:24-alpine3.22-dev AS base

# Establecer el directorio de trabajo
WORKDIR /app

# Crear un usuario no root por seguridad
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 -G nodejs && \
    chown -R nodejs:nodejs /app

# ========================================
# Etapa de dependencias
# ========================================
FROM base AS deps

# Copiar archivos de paquete
COPY package*.json ./

# Instalar dependencias de producción
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
    npm ci --omit=dev && \
    npm cache clean --force

# Establecer la propiedad adecuada
RUN chown -R nodejs:nodejs /app

# ========================================
# Etapa de dependencias de construcción
# ========================================
FROM base AS build-deps

# Copiar archivos de paquete
COPY package*.json ./

# Instalar todas las dependencias con optimizaciones de construcción
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
    npm ci --no-audit --no-fund && \
    npm cache clean --force

# Crear los directorios necesarios y establecer permisos
RUN mkdir -p /app/node_modules/.vite && \
    chown -R nodejs:nodejs /app

# ========================================
# Etapa de construcción
# ========================================
FROM build-deps AS build

# Copiar solo los archivos necesarios para la construcción (respeta .dockerignore)
COPY --chown=nodejs:nodejs . .

# Construir la aplicación
RUN npm run build

# Establecer la propiedad adecuada
RUN chown -R nodejs:nodejs /app

# ========================================
# 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"]

# ========================================
# Etapa de producción
# ========================================
FROM dhi.io/node:24-alpine3.22-dev AS production

# Establecer el directorio de trabajo
WORKDIR /app

# Crear un usuario no root por seguridad
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 -G nodejs && \
    chown -R nodejs:nodejs /app

# Establecer variables de entorno optimizadas
ENV NODE_ENV=production \
    NODE_OPTIONS="--max-old-space-size=256 --no-warnings" \
    NPM_CONFIG_LOGLEVEL=silent

# Copiar las dependencias de producción desde la etapa deps
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=deps --chown=nodejs:nodejs /app/package*.json ./
# Copiar la aplicación construida desde la etapa build
COPY --from=build --chown=nodejs:nodejs /app/dist ./dist

# Cambiar a usuario no root por seguridad
USER nodejs

# Exponer puerto
EXPOSE 3000

# Iniciar el servidor de producción
CMD ["node", "dist/server.js"]

# ========================================
# Etapa de pruebas
# ========================================
FROM build-deps AS test

# Establecer el entorno
ENV NODE_ENV=test \
    CI=true

# Copiar archivos fuente
COPY --chown=nodejs:nodejs . .

# Cambiar a usuario no root
USER nodejs

# Ejecutar pruebas con cobertura
CMD ["npm", "run", "test:coverage"]
```

**Usando la imagen oficial de Docker**



Ahora necesitas crear un Dockerfile multi-etapa listo para producción. Reemplaza el Dockerfile generado por la siguiente configuración optimizada:

```dockerfile
# ========================================
# Dockerfile multi-etapa optimizado
# Aplicación Node.js TypeScript
# ========================================

ARG NODE_VERSION=24.11.1-alpine
FROM node:${NODE_VERSION} AS base

# Establecer el directorio de trabajo
WORKDIR /app

# Crear un usuario no root por seguridad
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 -G nodejs && \
    chown -R nodejs:nodejs /app

# ========================================
# Etapa de dependencias
# ========================================
FROM base AS deps

# Copiar archivos de paquete
COPY package*.json ./

# Instalar dependencias de producción
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
    npm ci --omit=dev && \
    npm cache clean --force

# Establecer la propiedad adecuada
RUN chown -R nodejs:nodejs /app

# ========================================
# Etapa de dependencias de construcción
# ========================================
FROM base AS build-deps

# Copiar archivos de paquete
COPY package*.json ./

# Instalar todas las dependencias con optimizaciones de construcción
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
    npm ci --no-audit --no-fund && \
    npm cache clean --force

# Crear los directorios necesarios y establecer permisos
RUN mkdir -p /app/node_modules/.vite && \
    chown -R nodejs:nodejs /app

# ========================================
# Etapa de construcción
# ========================================
FROM build-deps AS build

# Copiar solo los archivos necesarios para la construcción (respeta .dockerignore)
COPY --chown=nodejs:nodejs . .

# Construir la aplicación
RUN npm run build

# Establecer la propiedad adecuada
RUN chown -R nodejs:nodejs /app

# ========================================
# 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"]

# ========================================
# Etapa de producción
# ========================================
ARG NODE_VERSION=24.11.1-alpine
FROM node:${NODE_VERSION} AS production

# Establecer el directorio de trabajo
WORKDIR /app

# Crear un usuario no root por seguridad
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 -G nodejs && \
    chown -R nodejs:nodejs /app

# Establecer variables de entorno optimizadas
ENV NODE_ENV=production \
    NODE_OPTIONS="--max-old-space-size=256 --no-warnings" \
    NPM_CONFIG_LOGLEVEL=silent

# Copiar las dependencias de producción desde la etapa deps
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=deps --chown=nodejs:nodejs /app/package*.json ./
# Copiar la aplicación construida desde la etapa build
COPY --from=build --chown=nodejs:nodejs /app/dist ./dist

# Cambiar a usuario no root por seguridad
USER nodejs

# Exponer puerto
EXPOSE 3000

# Iniciar el servidor de producción
CMD ["node", "dist/server.js"]

# ========================================
# Etapa de pruebas
# ========================================
FROM build-deps AS test

# Establecer el entorno
ENV NODE_ENV=test \
    CI=true

# Copiar archivos fuente
COPY --chown=nodejs:nodejs . .

# Cambiar a usuario no root
USER nodejs

# Ejecutar pruebas con cobertura
CMD ["npm", "run", "test:coverage"]
```


Características clave de este Dockerfile:
- Estructura multi-etapa — Etapas separadas para dependencias, construcción, desarrollo, producción y pruebas para mantener cada fase limpia y eficiente.
- Imagen de producción ligera — La superposición de capas optimizada reduce el tamaño y conserva únicamente lo requerido para ejecutar la aplicación.
- Configuración centrada en la seguridad — Utiliza un usuario dedicado no root y excluye paquetes innecesarios.
- Diseño enfocado al rendimiento — Uso eficaz de la caché y capas bien estructuradas para construcciones más rápidas.
- Entorno de ejecución limpio — Elimina archivos innecesarios en producción, como documentación, pruebas y cachés de construcción.
- Uso directo de puertos — La aplicación se ejecuta internamente en el puerto 3000, expuesto externamente como puerto 8080.
- Tiempo de ejecución optimizado en memoria — Node.js está configurado para ejecutarse con un límite de memoria más pequeño que el predeterminado.

### Paso 2: Configura el archivo .dockerignore

El archivo `.dockerignore` le indica a Docker qué archivos y carpetas excluir al construir la imagen.

> [!NOTE]
> Esto ayuda a:
>
> - Reducir el tamaño de la imagen.  
> - Acelerar el proceso de construcción.  
> - Evitar que archivos confidenciales o innecesarios (como `.env`, `.git` o `node_modules`) se agreguen a la imagen final.
>
> Para conocer más, visita la [referencia de .dockerignore](/reference/dockerfile/#dockerignore-file).

Copia y reemplaza el contenido de tu `.dockerignore` existente con la configuración optimizada:

```dockerignore
# .dockerignore optimizado para la aplicación de tareas (Todo) en Node.js + React
# Basado en la estructura real del proyecto

# Control de versiones
.git/
.github/
.gitignore

# Dependencias (instaladas en el contenedor)
node_modules/

# Salidas de construcción (construidas en el contenedor)
dist/

# Archivos de entorno
.env*

# Archivos de desarrollo
.vscode/
*.log
coverage/
.eslintcache

# Archivos del sistema operativo
.DS_Store
Thumbs.db

# Documentación
*.md
docs/

# Configuraciones de despliegue
compose.yml
Taskfile.yml
nodejs-sample-kubernetes.yaml

# Configuraciones no esenciales (mantener configuraciones de construcción)
*.config.js
!vite.config.ts
!esbuild.config.js
!tailwind.config.js
!postcss.config.js
!tsconfig.json
```

### Paso 3: Construye la imagen de la aplicación Node.js

Después de crear todos los archivos de configuración, el directorio de tu proyecto debería contener todos los archivos de configuración de Docker necesarios:

```text
├── docker-nodejs-sample/
│ ├── Dockerfile
│ ├── .dockerignore
│ ├── compose.yml
│ └── README.Docker.md
```

Ahora puedes construir la imagen de Docker para tu aplicación Node.js.

> [!NOTE]
> El comando `docker build` empaqueta tu aplicación en una imagen utilizando las instrucciones del Dockerfile. Incluye todos los archivos necesarios del directorio actual (denominado [contexto de construcción](/build/concepts/context/#what-is-a-build-context)).

Ejecuta el siguiente comando desde la raíz de tu proyecto:

```console
$ docker build --target production --tag docker-nodejs-sample .
```

Qué hace este comando:

- Utiliza el Dockerfile en el directorio actual (`.`).
- Apunta a la etapa de producción (`production`) de la construcción multi-etapa.
- Empaqueta la aplicación y sus dependencias en una imagen de Docker.
- Etiqueta la imagen como `docker-nodejs-sample` para que puedas hacer referencia a ella más adelante.

#### Paso 4: Visualiza las imágenes locales

Después de construir tu imagen de Docker, puedes verificar qué imágenes están disponibles en tu máquina local usando la CLI de Docker o [Docker Desktop](/desktop/use-desktop/images/). Dado que ya estás trabajando en la terminal, utiliza la CLI de Docker.

Para listar todas las imágenes de Docker disponibles localmente, ejecuta el siguiente comando:

```console
$ docker images
```

Salida de ejemplo:

```shell
REPOSITORY               TAG              IMAGE ID       CREATED         SIZE
docker-nodejs-sample     latest           423525528038   14 seconds ago  237.46MB
```

Esta salida proporciona detalles clave sobre tus imágenes:

- **Repository** – El nombre asignado a la imagen.
- **Tag** – Una etiqueta de versión que ayuda a identificar diferentes construcciones (por ejemplo, `latest`).
- **Image ID** – Un identificador único para la imagen.
- **Created** – La marca de tiempo que indica cuándo se construyó la imagen.
- **Size** – El espacio total en disco utilizado por la imagen.

Si la construcción fue exitosa, deberías ver la imagen `docker-nodejs-sample` en la lista.

---

## Ejecuta la aplicación contenerizada

En el paso anterior, creaste un Dockerfile para tu aplicación Node.js y construiste una imagen de Docker utilizando el comando `docker build`. Ahora es el momento de ejecutar esa imagen en un contenedor y verificar que tu aplicación funcione como se espera.

Dentro del directorio `docker-nodejs-sample`, ejecuta el siguiente comando en una terminal:

```console
$ docker compose up app-dev --build
```

La aplicación de desarrollo se iniciará con ambos servidores:

- **Servidor API**: [http://localhost:3000](http://localhost:3000) - Backend de Express.js con API REST
- **Frontend**: [http://localhost:5173](http://localhost:5173) - Servidor de desarrollo de Vite con frontend de React
- **Comprobación de estado (Health Check)**: [http://localhost:3000/health](http://localhost:3000/health) - Estado de salud de la aplicación

Para un despliegue de producción, puedes utilizar:

```console
$ docker compose up app-prod --build
```

Lo cual sirve la aplicación full-stack en [http://localhost:8080](http://localhost:8080) con el servidor Express ejecutándose internamente en el puerto 3000, mapeado externamente al puerto 8080.

Deberías ver una aplicación moderna de lista de tareas (Todo List) con React 19 y una API REST completamente funcional.

Presiona `CTRL + C` en la terminal para detener la aplicación.

### Ejecuta la aplicación en segundo plano

Puedes ejecutar la aplicación desconectada de la terminal (detached mode) agregando la opción `-d`. Dentro del directorio `docker-nodejs-sample`, ejecuta el siguiente comando en una terminal:

```console
$ docker compose up app-dev --build -d
```

Abre un navegador y visualiza la aplicación en [http://localhost:3000](http://localhost:3000) (API) o [http://localhost:5173](http://localhost:5173) (frontend). Deberías ver la aplicación de tareas ejecutándose.

Para confirmar que el contenedor se está ejecutando, utiliza el comando `docker ps`:

```console
$ docker ps
```

Esto listará todos los contenedores activos junto con sus puertos, nombres y estado. Busca un contenedor que exponga los puertos 3000, 5173 y 9229 para la aplicación de desarrollo.

Salida de ejemplo:

```shell
CONTAINER ID   IMAGE                          COMMAND                  CREATED          STATUS                 PORTS                                                                                                                                   NAMES
93f3faee32c3   docker-nodejs-sample-app-dev   "docker-entrypoint.s…"   33 seconds ago   Up 31 seconds          0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp, 0.0.0.0:5173->5173/tcp, [::]:5173->5173/tcp, 0.0.0.0:9230->9229/tcp, [::]:9230->9229/tcp   todoapp-dev
```

### Ejecuta diferentes perfiles

Puedes ejecutar diferentes configuraciones utilizando los perfiles de Docker Compose:

```console
# Ejecutar producción
$ docker compose up app-prod -d

# Ejecutar pruebas
$ docker compose up app-test -d
```

Para detener la aplicación, ejecuta:

```console
$ docker compose down
```

> [!NOTE]
> Para obtener más información sobre los comandos de Compose, consulta la [referencia de la CLI de Compose](/reference/cli/docker/compose/).

---

## Resumen

En esta guía, aprendiste a contenerizar, construir y ejecutar una aplicación Node.js utilizando Docker. Siguiendo las mejores prácticas, creaste una configuración segura, optimizada y lista para producción.

Lo que lograste:

- Inicializaste tu proyecto usando `docker init` para estructurar los archivos de configuración esenciales de Docker.
- Creaste un archivo `compose.yml` con servicios de desarrollo, producción y base de datos.
- Estableciste la configuración del entorno con un archivo `.env` para ajustes de despliegue flexibles.
- Reemplazaste el `Dockerfile` predeterminado por una construcción multi-etapa optimizada para TypeScript y React.
- Reemplazaste el archivo `.dockerignore` predeterminado para excluir archivos innecesarios y mantener la imagen limpia y eficiente.
- Construiste tu imagen de Docker usando `docker build`.
- Ejecutaste el contenedor usando `docker compose up`, tanto en primer plano como en segundo plano (detached mode).
- Verificaste que la aplicación se estuviera ejecutando visitando [http://localhost:8080](http://localhost:8080) (producción) o [http://localhost:3000](http://localhost:3000) (desarrollo).
- Aprendiste a detener la aplicación contenerizada usando `docker compose down`.

Ahora tienes una aplicación Node.js completamente contenerizada, ejecutándose en un contenedor Docker y lista para ser desplegada en cualquier entorno con confianza y consistencia.

---

## Recursos relacionados

Explora referencias oficiales y mejores prácticas para perfeccionar tu flujo de trabajo con Docker:

- [Construcciones multi-etapa](/build/building/multi-stage/) – Aprende a separar las etapas de construcción y de ejecución.
- [Mejores prácticas para escribir Dockerfiles](/develop/develop-images/dockerfile_best-practices/) – Escribe Dockerfiles eficientes, fáciles de mantener y seguros.
- [Contexto de construcción en Docker](/build/concepts/context/) – Conoce cómo afecta el contexto a las construcciones de imágenes.
- [Referencia de CLI de `docker init`](/reference/cli/docker/init/) – Genera automáticamente assets de Docker.
- [Referencia de CLI de `docker build`](/reference/cli/docker/image/build/) – Construye imágenes de Docker a partir de un Dockerfile.
- [Referencia de CLI de `docker images`](/reference/cli/docker/image/ls/) – Gestiona e inspecciona imágenes de Docker locales.
- [Referencia de CLI de `docker compose up`](/reference/cli/docker/compose/up/) – Inicia y ejecuta aplicaciones multi-contenedor.
- [Referencia de CLI de `docker compose down`](/reference/cli/docker/compose/down/) – Detiene y elimina contenedores, redes y volúmenes.

---

## Próximos pasos

Con tu aplicación Node.js ahora contenerizada, estás listo para avanzar al siguiente paso.

En la siguiente sección, aprenderás cómo desarrollar tu aplicación utilizando contenedores Docker, lo que te permitirá contar con un entorno de desarrollo consistente, aislado y reproducible en cualquier máquina.

