# Contenerizar una aplicación de React.js


## Prerrequisitos

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.

---

## Resumen

Esta guía te acompaña a lo largo de todo el proceso de contenerización de una aplicación de React.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 del despliegue.

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

- Contenerizar una aplicación de React.js usando Docker.
- Crear y optimizar un Dockerfile para compilaciones de producción. 
- Utilizar compilaciones en múltiples etapas (multi-stage builds) para minimizar el tamaño de la imagen.
- Servir la aplicación de manera eficiente con una configuración personalizada de NGINX.
- Seguir las mejores prácticas para construir imágenes de Docker seguras 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, ve al directorio en el que quieras trabajar y ejecuta el siguiente comando para clonar el repositorio de git:

```console
$ git clone https://github.com/kristiyan-velkov/docker-reactjs-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 la generación de un `Dockerfile`, `.dockerignore`, `compose.yaml` y `README.Docker.md`.

Para comenzar, navega al directorio raíz de tu proyecto:

```console
$ cd docker-reactjs-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 ejemplo de abajo cuando se te solicite:
| Pregunta                                                   | Respuesta       |
|------------------------------------------------------------|-----------------|
| What application platform does your project use?           | Node            |
| What version of Node do you want to use?                   | 24.12.0-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?                      | 8080            |

Al finalizar, el directorio de tu proyecto contendrá los siguientes archivos nuevos:

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

---

## Construye la imagen de Docker

El Dockerfile predeterminado generado por `docker init` sirve como un buen punto de partida para aplicaciones generales de Node.js. Sin embargo, React.js es una biblioteca de frontend que se compila en recursos estáticos, por lo que necesitamos adaptar el Dockerfile para optimizar la forma en que se compilan y sirven las aplicaciones de React en un entorno de producción.

### Paso 1: Revisa los archivos generados

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

- Utiliza compilaciones en múltiples etapas (multi-stage builds) para mantener la imagen final limpia y pequeña.
- Sirve la aplicación usando NGINX, un servidor web rápido y seguro.
- Mejora el rendimiento y la seguridad incluyendo únicamente lo necesario.

Estas actualizaciones ayudan a garantizar que tu aplicación sea fácil de desplegar, rápida de cargar y esté lista para producción.

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


### Paso 2: Configura el archivo Dockerfile

Antes de crear un Dockerfile, debes elegir una imagen base. Puedes usar la [Imagen oficial de Node.js](https://hub.docker.com/_/node) o una Imagen de Docker Reforzada (DHI) del [catálogo de Imágenes Reforzadas](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 obtener más información, consulta [Imágenes de Docker Reforzadas](https://docs-docker.esdocu.com/dhi/).

> [!IMPORTANT]
> Esta guía utiliza una etiqueta de imagen estable Node.js LTS que se considera segura en el momento de escribir la guía. Debido a que se publican nuevas versiones y parches de seguridad con regularidad, es posible que la etiqueta que se muestra aquí ya no sea 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

**Uso de Imágenes de Docker Reforzadas**


Las Imágenes de Docker Reforzadas (DHIs) para Node.js están disponibles en el [catálogo de Imágenes de Docker Reforzadas](https://hub.docker.com/hardened-images/catalog/dhi/node). Las Imágenes de Docker Reforzadas están disponibles de forma gratuita para todo el mundo sin necesidad de suscripción. Puedes descargarlas y usarlas como cualquier otra imagen de Docker después de iniciar sesión en el registro de DHI. Para obtener 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:
   ```console
   $ docker login dhi.io
   ```

2. Descarga la DHI de Node.js (consulta el catálogo para ver las versiones disponibles):
   ```console
   $ docker pull dhi.io/node:24-alpine3.22-dev
   ```

3. Descarga la DHI de Nginx (consulta el catálogo para ver las versiones disponibles):
   ```console
   $ docker pull dhi.io/nginx:1.28.0-alpine3.21-dev
   ```

En el siguiente Dockerfile, las instrucciones `FROM` utilizan `dhi.io/node:24-alpine3.22-dev` y `dhi.io/nginx:1.28.0-alpine3.21-dev` como imágenes base.

```dockerfile
# =========================================
# Etapa 1: Construir la aplicación de React.js
# =========================================

# Utilizar una imagen ligera de Node.js para la compilación (personalizable mediante ARG)
FROM dhi.io/node:24-alpine3.22-dev AS builder

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

# Copiar primero los archivos relacionados con paquetes para aprovechar el mecanismo de almacenamiento en caché de Docker
COPY package.json package-lock.json* ./

# Instalar las dependencias del proyecto usando npm ci (garantiza una instalación limpia y reproducible)
RUN --mount=type=cache,target=/root/.npm npm ci

# Copiar el resto del código fuente de la aplicación dentro del contenedor
COPY . .

# Construir la aplicación de React.js (la salida va a /app/dist)
RUN npm run build

# =========================================
# Etapa 2: Preparar Nginx para servir archivos estáticos
# =========================================

FROM dhi.io/nginx:1.28.0-alpine3.21-dev AS runner

# Copiar la configuración personalizada de Nginx
COPY nginx.conf /etc/nginx/nginx.conf

# Copiar la salida estática de la etapa de compilación al directorio predeterminado de servicio HTML de Nginx
COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html

# Usar un usuario no root por mejores prácticas de seguridad
USER nginx

# Exponer el puerto 8080 para permitir el tráfico HTTP
# Nota: El contenedor NGINX predeterminado ahora escucha en el puerto 8080 en lugar del 80 
EXPOSE 8080

# Iniciar Nginx directamente con la configuración personalizada
ENTRYPOINT ["nginx", "-c", "/etc/nginx/nginx.conf"]
CMD ["-g", "daemon off;"]
```

**Uso de la Imagen Oficial de Docker**



Ahora debes crear un Dockerfile de múltiples etapas listo para producción. Reemplaza el Dockerfile generado con la siguiente configuración optimizada:

```dockerfile
# =========================================
# Etapa 1: Construir la aplicación de React.js
# =========================================
ARG NODE_VERSION=24.12.0-alpine
ARG NGINX_VERSION=alpine3.22

# Utilizar una imagen ligera de Node.js para la compilación (personalizable mediante ARG)
FROM node:${NODE_VERSION} AS builder

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

# Copiar primero los archivos relacionados con paquetes para aprovechar el mecanismo de almacenamiento en caché de Docker
COPY package.json package-lock.json* ./

# Instalar las dependencias del proyecto usando npm ci (garantiza una instalación limpia y reproducible)
RUN --mount=type=cache,target=/root/.npm npm ci

# Copiar el resto del código fuente de la aplicación dentro del contenedor
COPY . .

# Construir la aplicación de React.js (la salida va a /app/dist)
RUN npm run build

# =========================================
# Etapa 2: Preparar Nginx para servir archivos estáticos
# =========================================

FROM nginxinc/nginx-unprivileged:${NGINX_VERSION} AS runner

# Copiar la configuración personalizada de Nginx
COPY nginx.conf /etc/nginx/nginx.conf

# Copiar la salida estática de la etapa de compilación al directorio predeterminado de servicio HTML de Nginx
COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html

# Usar un usuario no root integrado por mejores prácticas de seguridad
USER nginx

# Exponer el puerto 8080 para permitir el tráfico HTTP
# Nota: El contenedor NGINX predeterminado ahora escucha en el puerto 8080 en lugar del 80 
EXPOSE 8080

# Iniciar Nginx directamente con la configuración personalizada
ENTRYPOINT ["nginx", "-c", "/etc/nginx/nginx.conf"]
CMD ["-g", "daemon off;"]
```

> [!NOTE]
> Estamos usando nginx-unprivileged en lugar de la imagen estándar de NGINX para seguir las mejores prácticas de seguridad.
> Ejecutar como un usuario no root en la imagen final:
>- Reduces the attack surface
>- Se alinea con las recomendaciones de Docker para el endurecimiento de contenedores
>- Ayuda a cumplir con políticas de seguridad más estrictas en entornos de producción



### Paso 3: Configura el archivo .dockerignore

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


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

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

```dockerignore
# Ignorar dependencias y salida de compilación
node_modules/
dist/
out/
.tmp/
.cache/

# Ignorar artefactos de compilación específicos de Vite, Webpack y React
.vite/
.vitepress/
.eslintcache
.npm/
coverage/
jest/
cypress/
cypress/screenshots/
cypress/videos/
reports/

# Ignorar archivos de entorno y configuración (datos confidenciales)
*.env*
*.log

# Ignorar artefactos de compilación de TypeScript (si se usa TypeScript)
*.tsbuildinfo

# Ignorar archivos de bloqueo (opcional si se usa Docker para la instalación de paquetes)
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Ignorar archivos de desarrollo local
.git/
.gitignore
.vscode/
.idea/
*.swp
.DS_Store
Thumbs.db

# Ignorar archivos relacionados con Docker (para evitar copiar configuraciones innecesarias)
Dockerfile
.dockerignore
docker-compose.yml
docker-compose.override.yml

# Ignorar archivos de caché específicos de la compilación
*.lock

```

### Paso 4: Crea el archivo `nginx.conf`

Para servir tu aplicación de React.js de manera eficiente dentro del contenedor, configurarás NGINX con un diseño personalizado. Esta configuración está optimizada para el rendimiento, el almacenamiento en caché del navegador, la compresión gzip y el soporte para el enrutamiento del lado del cliente.

Crea un archivo llamado `nginx.conf` en la raíz del directorio de tu proyecto y agrega el siguiente contenido:

> [!NOTE]
> Para obtener más información sobre la configuración de NGINX, consulta la [documentación oficial de NGINX](https://nginx.org/en/docs/).


```nginx
worker_processes auto;

# Guardar el PID en /tmp (siempre tiene permisos de escritura)
pid /tmp/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    # Desactivar el registro para evitar problemas de permisos
    access_log off;
    error_log  /dev/stderr warn;

    # Optimizar el servicio de archivos estáticos
    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;
    keepalive_timeout  65;
    keepalive_requests 1000;

    # Compresión gzip para una entrega optimizada
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
    gzip_min_length 256;
    gzip_vary on;

    server {
        listen       8080;
        server_name  localhost;

        # Directorio raíz donde se colocan los archivos compilados de React.js
        root /usr/share/nginx/html;
        index index.html;

        # Servir archivos estáticos de React.js con un almacenamiento en caché adecuado
        location / {
            try_files $uri /index.html;
        }

        # Servir recursos estáticos con una expiración de caché prolongada
        location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|map)$ {
            expires 1y;
            access_log off;
            add_header Cache-Control "public, immutable";
        }

        # Manejar el enrutamiento del lado del cliente de React.js
        location /static/ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
}
```

### Paso 5: Construye la imagen de la aplicación de React.js

Con tu configuración personalizada lista, ya puedes construir la imagen de Docker para tu aplicación de React.js.

La configuración actualizada incluye:

- Almacenamiento en caché del navegador y compresión gzip optimizados
- Registro seguro que no requiere privilegios de root para evitar problemas de permisos
- Soporte para enrutamiento del lado del cliente en React al redirigir las rutas no coincidentes a `index.html`

Después de completar los pasos anteriores, el directorio de tu proyecto debería contener los siguientes archivos:

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

Ahora que tu Dockerfile está configurado, puedes construir la imagen de Docker para tu aplicación de React.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 (llamado el [contexto de construcción](/build/concepts/context/#what-is-is-a-build-context)).

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

```console
$ docker build --tag docker-reactjs-sample .
```

Lo que hace este comando:
- Utiliza el Dockerfile del directorio actual (`.`)
- Empaqueta la aplicación y sus dependencias en una imagen de Docker
- Etiqueta la imagen como `docker-reactjs-sample` para que puedas hacer referencia a ella más tarde


#### Paso 6: 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/). Como ya estás trabajando en la terminal, usemos la CLI de Docker.

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

```console
$ docker images
```

Ejemplo de salida:

```shell
REPOSITORY                TAG               IMAGE ID       CREATED         SIZE
docker-reactjs-sample     latest            f39b47a97156   14 seconds ago   75.8MB
```

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 las diferentes compilaciones (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 compilación fue exitosa, deberías ver listada la imagen `docker-reactjs-sample`.

---

## Ejecuta la aplicación contenerizada

En el paso anterior, creaste un Dockerfile para tu aplicación de React.js y construiste una imagen de Docker usando 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-reactjs-sample`, ejecuta el siguiente comando en una terminal:

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

Abre un navegador y visualiza la aplicación en [http://localhost:8080](http://localhost:8080). Deberías ver una aplicación web sencilla de React.js.

Presiona `ctrl+c` en la terminal para detener tu 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-reactjs-sample`, ejecuta el siguiente comando en una terminal:

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

Abre un navegador y visualiza la aplicación en [http://localhost:8080](http://localhost:8080). Deberías ver una vista previa sencilla de la aplicación web.

Para confirmar que el contenedor se está ejecutando, usa 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 el puerto 8080.

Ejemplo de salida:

```shell
CONTAINER ID   IMAGE                          COMMAND                  CREATED             STATUS             PORTS                    NAMES
88bced6ade95   docker-reactjs-sample-server   "nginx -c /etc/nginx…"   About a minute ago  Up About a minute  0.0.0.0:8080->8080/tcp   docker-reactjs-sample-server-1
```

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 de React.js usando 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.
- Reemplazaste el `Dockerfile` predeterminado con una compilación de múltiples etapas que compila la aplicación de React.js y sirve los archivos estáticos usando Nginx.
- 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).
- Aprendiste a detener la aplicación contenerizada usando `docker compose down`.

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

---

## Recursos relacionados

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

- [Compilaciones de múltiples etapas](/build/building/multi-stage/) – Aprende a separar las etapas de compilació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/) – Aprende cómo afecta el contexto a las compilaciones de imágenes.
- [Referencia de la CLI de `docker init`](/reference/cli/docker/init/) – Genera automáticamente recursos de Docker.
- [Referencia de la CLI de `docker build`](/reference/cli/docker/image/build/) – Construye imágenes de Docker a partir de un Dockerfile.
- [Referencia de la CLI de `docker images`](/reference/cli/docker/image/ls/) – Administra e inspecciona imágenes de Docker locales.
- [Referencia de la CLI de `docker compose up`](/reference/cli/docker/compose/up/) – Inicia y ejecuta aplicaciones multicontenedor.
- [Referencia de la CLI de `docker compose down`](/reference/cli/docker/compose/down/) – Detén y elimina contenedores, redes y volúmenes.

---

## Próximos pasos

Con tu aplicación de React.js ahora contenerizada, estás listo para pasar al siguiente paso.

En la siguiente sección, aprenderás a desarrollar tu aplicación utilizando contenedores de Docker, lo que permite un entorno de desarrollo coherente, aislado y reproducible en cualquier máquina.

