Dockerizar tu aplicación React (Vite) 🐳⚛️

 

Dockerizar tu aplicación React (Vite) 🐳⚛️

Vamos a crear un contenedor Docker para tu app React creada con Vite. Veremos dos escenarios:

  • Modo desarrollo → con hot-reload (para seguir programando dentro de Docker).

  • Modo producción → build optimizado con Nginx (para desplegar).


📋 Requisitos previos

HerramientaVerificar con
Docker Desktopdocker --version
Docker Composedocker compose version
Tu app React funcionando localmentenpm run dev

Estructura de partida (asumo que seguiste el tutorial anterior):

text
pasteles-app/
├── src/
├── public/
├── index.html
├── package.json
├── vite.config.js
└── ...

1️⃣ Crear el archivo .dockerignore

En la raíz de pasteles-app/, crea .dockerignore:

text
node_modules
dist
build
.git
.gitignore
.env
.env.local
.vscode
.idea
*.log
npm-debug.log*
.DS_Store
Dockerfile
docker-compose.yml
README.md

¿Por qué? Sin esto, Docker copiaría tu node_modules local (que puede pesar cientos de MB y tener binarios de tu SO) al contenedor, causando conflictos y builds lentos.


2️⃣ Configurar Vite para exponerse fuera del contenedor

Edita vite.config.js:

javascript
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    host: '0.0.0.0',   // 👈 Permite conexiones desde fuera del contenedor
    port: 5173,
    strictPort: true,
    watch: {
      usePolling: true, // 👈 Necesario en Windows/Mac con Docker Desktop
    },
  },
});

Sin host: '0.0.0.0', Vite solo escucha en localhost dentro del contenedor y no podrás acceder desde tu navegador.


🅰️ MODO DESARROLLO (con hot-reload)

Este es el modo recomendado mientras programas.

3️⃣A Crear Dockerfile.dev

En la raíz de pasteles-app/:

dockerfile
# Dockerfile.dev
FROM node:20-alpine

WORKDIR /app

# Copiamos solo los manifiestos primero (mejor caché de capas)
COPY package*.json ./

# Instalamos dependencias
RUN npm install

# Copiamos el resto del código
COPY . .

# Exponemos el puerto de Vite
EXPOSE 5173

# Comando para desarrollo
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

4️⃣A Crear docker-compose.dev.yml

yaml
# docker-compose.dev.yml
services:
  react-dev:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: pasteles-react-dev
    ports:
      - "5173:5173"
    volumes:
      # Montamos el código para hot-reload
      - .:/app
      # Volumen anónimo para node_modules (evita sobrescribirlo)
      - /app/node_modules
    environment:
      - CHOKIDAR_USEPOLLING=true
      - VITE_API_URL=http://localhost:8000/api
    stdin_open: true
    tty: true
    networks:
      - pasteles-net

networks:
  pasteles-net:
    driver: bridge

💡 El volumen - /app/node_modules es truco clave: le dice a Docker que mantenga los node_modules del contenedor (Linux) y no los de tu máquina host. Sin esto, verás errores como Cannot find module 'vite'.

5️⃣A Levantar el contenedor en modo desarrollo

bash
# Construir y levantar
docker compose -f docker-compose.dev.yml up --build

# O en segundo plano
docker compose -f docker-compose.dev.yml up -d --build

Verás algo como:

text
pasteles-react-dev  |   VITE v5.x  ready in 300 ms
pasteles-react-dev  |   ➜  Local:   http://localhost:5173/

Abre http://localhost:5173 en tu navegador. 🎉

6️⃣A Comandos útiles (modo dev)

bash
# Ver logs en vivo
docker compose -f docker-compose.dev.yml logs -f

# Detener
docker compose -f docker-compose.dev.yml down

# Entrar a la terminal del contenedor
docker exec -it pasteles-react-dev sh

# Instalar una nueva dependencia (dentro del contenedor)
docker exec -it pasteles-react-dev npm install axios

# Reconstruir desde cero
docker compose -f docker-compose.dev.yml down -v
docker compose -f docker-compose.dev.yml up --build

🅱️ MODO PRODUCCIÓN (con Nginx)

Este es el modo que usarás para desplegar. Genera un build estático y lo sirve con Nginx.

3️⃣B Crear Dockerfile (multi-stage)

En la raíz de pasteles-app/:

dockerfile
# ---------- ETAPA 1: BUILD ----------
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

# Variable de entorno para el build (apunta al backend de Laravel)
ARG VITE_API_URL=http://localhost:8000/api
ENV VITE_API_URL=$VITE_API_URL

RUN npm run build


# ---------- ETAPA 2: SERVIDOR ----------
FROM nginx:1.27-alpine

# Copiamos la config personalizada de Nginx
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Copiamos el build generado desde la etapa 1
COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

4️⃣B Crear nginx.conf

En la raíz de pasteles-app/:

nginx
server {
    listen 80;
    server_name localhost;

    root /usr/share/nginx/html;
    index index.html;

    # Gzip para mejor performance
    gzip on;
    gzip_types text/css application/javascript application/json image/svg+xml;
    gzip_min_length 1000;

    # Cache agresivo para assets (tienen hash en el nombre)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # SPA fallback: cualquier ruta → index.html (para React Router)
    location / {
        try_files $uri $uri/ /index.html;
    }
}

💡 El try_files ... /index.html es clave para SPA. Sin esto, si el usuario recarga en /pasteles/5, Nginx daría 404 porque esa ruta no existe como archivo. React Router maneja eso en el cliente.

5️⃣B Crear docker-compose.yml (producción)

yaml
# docker-compose.yml
services:
  react-prod:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        VITE_API_URL: http://localhost:8000/api
    container_name: pasteles-react-prod
    ports:
      - "8080:80"
    restart: unless-stopped
    networks:
      - pasteles-net

networks:
  pasteles-net:
    driver: bridge

6️⃣B Levantar en modo producción

bash
# Construir la imagen
docker build -t pasteles-react:prod \
  --build-arg VITE_API_URL=http://localhost:8000/api .

# O con compose
docker compose up --build -d

Abre http://localhost:8080 y verás tu app servida por Nginx. 🚀


7️⃣ ⚠️ Conectar React con Laravel en Docker (CORS)

Como ahora React corre en http://localhost:8080 (prod) o http://localhost:5173 (dev), debes agregar esos orígenes a CORS en Laravel.

Edita config/cors.php:

php
'allowed_origins' => [
    'http://localhost:5173',  // dev
    'http://localhost:8080',  // prod
],

Y limpia la caché:

bash
php artisan config:clear

8️⃣ (Opcional) Dockerizar también Laravel + MySQL

Si quieres levantar todo el stack con un solo comando, aquí va un docker-compose.yml completo en la raíz del proyecto padre:

text
proyecto/
├── laravel-app/        ← tu backend
├── pasteles-app/       ← tu frontend React
└── docker-compose.yml  ← orquestador

docker-compose.yml:

yaml
services:
  db:
    image: mysql:8.0
    container_name: pasteles-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: laravel_db
      MYSQL_ROOT_PASSWORD: root
      MYSQL_PASSWORD: secret
      MYSQL_USER: laravel
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - pasteles-net

  laravel:
    build:
      context: ./laravel-app
      dockerfile: Dockerfile
    container_name: pasteles-laravel
    ports:
      - "8000:8000"
    depends_on:
      - db
    environment:
      DB_CONNECTION: mysql
      DB_HOST: db
      DB_PORT: 3306
      DB_DATABASE: laravel_db
      DB_USERNAME: laravel
      DB_PASSWORD: secret
    networks:
      - pasteles-net

  react-dev:
    build:
      context: ./pasteles-app
      dockerfile: Dockerfile.dev
    container_name: pasteles-react
    ports:
      - "5173:5173"
    volumes:
      - ./pasteles-app:/app
      - /app/node_modules
    environment:
      - VITE_API_URL=http://localhost:8000/api
    depends_on:
      - laravel
    networks:
      - pasteles-net

networks:
  pasteles-net:
    driver: bridge

volumes:
  db_data:

No, no es obligatorio detener y eliminar todos los contenedores existentes para agregar el de React. Docker Compose está diseñado para detectar cambios y actuar solo sobre lo necesario

Qué pasa si solo agregas el servicio frontend a docker-compose.yml

Si no tocas la definición de app, nginx ni db, puedes simplemente:

bash
docker compose up -d

Compose:

  • Creará y levantará el nuevo contenedor react-frontend-1.

  • Dejará los contenedores existentes (laravel-app-1, laravel-nginx-1, laravel-db-1) tal como están, sin detenerlos ni eliminarlos.

Si modificaste algo de los servicios existentes

Por ejemplo, si cambiaste depends_on, redes, volúmenes o variables de entorno de app, nginx o db, Compose recreará automáticamente solo esos contenedores que hayan cambiado. No necesitas hacerlo a mano.

Si quieres forzar una reconstrucción completa

Puedes hacer:

bash
docker compose down
docker compose up -d --build

Pero cuidado: docker compose down elimina los contenedores y la red, pero conserva los volúmenes nombrados (como db_data). Si usas down -v, borrarás también los datos de MySQL. Para desarrollo está bien, pero si quieres conservar la base de datos, no uses -v.


Con esto:

bash
docker compose up -d --build

Y tendrás los 3 servicios corriendo:

ServicioURL
Reacthttp://localhost:5173
Laravel APIhttp://localhost:8000/api/pasteles
MySQLlocalhost:3306

📁 Estructura final (solo React)

text
pasteles-app/
├── src/
├── public/
├── Dockerfile             ← Producción (Nginx)
├── Dockerfile.dev         ← Desarrollo (Vite)
├── docker-compose.yml     ← Producción
├── docker-compose.dev.yml ← Desarrollo
├── nginx.conf             ← Config Nginx
├── .dockerignore          ← Excluir archivos
├── package.json
├── vite.config.js
└── index.html

🐛 Solución de problemas comunes

ErrorCausaSolución
Cannot find module 'vite'Volumen sobrescribe node_modulesAgregar - /app/node_modules al volumen
ERR_CONNECTION_REFUSED desde el navegadorVite no escucha en 0.0.0.0Agregar host: '0.0.0.0' en vite.config.js
Hot-reload no funciona (Windows/Mac)File watching no detecta cambiosusePolling: true en vite.config.js
CORS policy blockedOrigen no permitido en LaravelAgregar http://localhost:5173 en config/cors.php
Build falla con ENOSPCDocker sin espaciodocker system prune -a
Nginx da 404 al recargar en /rutaSPA fallback faltanteYa lo agregamos en nginx.conf
port is already allocatedPuerto ocupadoCambia el puerto o mata el proceso

🧹 Comandos útiles de Docker

bash
# Ver contenedores activos
docker ps

# Ver todos (incluidos detenidos)
docker ps -a

# Ver imágenes
docker images

# Ver uso de recursos
docker stats

# Limpiar todo (⚠️ borra imágenes y volúmenes no usados)
docker system prune -a --volumes

# Ver logs de un contenedor específico
docker logs -f pasteles-react-dev

✅ Resumen de comandos

Desarrollo

bash
docker compose -f docker-compose.dev.yml up --build
# → http://localhost:5173

Producción

bash
docker compose up --build -d
# → http://localhost:8080

Detener

bash
docker compose -f docker-compose.dev.yml down   # dev
docker compose down                             # prod

🎯 ¿Cuándo usar cada modo?

EscenarioModoArchivo
Estoy programando la appDevDockerfile.dev + docker-compose.dev.yml
Quiero mostrarla al clienteProdDockerfile + docker-compose.yml
Voy a desplegar en un servidorProdIgual, pero cambia VITE_API_URL al dominio real

Con esto ya tienes tu app React totalmente dockerizada

Comentarios

Entradas más populares de este blog

12. Hola Mundo en Docker.

2.1-Instalación de Docker (nuevo método oficial), configuración de un proyecto Laravel con Nginx y MySQL en contenedores

11¿Qué es Docker? y ¿Por qué debo saberlo?