Te proporciono toda la configuración necesaria para desplegar tu aplicación React en producción, siguiendo la misma estructura y convenciones de tu API Laravel.
react-app/
├── docker/
│ ├── nginx/
│ │ └── default.conf
│ └── Dockerfile
├── src/
│ └── (código fuente React)
├── public/
├── .dockerignore
├── .env.example
├── .env.development
├── .env.production
├── docker-compose.yml
├── docker-compose.dev.yml
├── docker-compose.prod.yml
├── package.json
└── README.md| Opción | Ventajas | Desventajas | Recomendación |
|---|---|---|---|
| Nginx (estático) | Máximo rendimiento, bajo consumo de recursos, caché eficiente | Configuración adicional para SPA | ✅ Recomendado para producción |
| Node.js (serve) | Fácil configuración | Mayor consumo de memoria, menos eficiente | Para desarrollo/testing |
| Node.js (SSR) | SEO mejorado, renderizado servidor | Complejidad, más recursos | Si necesitas SSR |
Recomendación: Nginx sirve archivos estáticos de forma más eficiente y es el estándar de la industria para SPAs en producción.
docker/Dockerfile(Multi-stage optimizado)# ============================================
# STAGE 1: Build
# ============================================
FROM node:20-alpine AS builder
# Argumentos de build para variables de entorno
ARG REACT_APP_API_URL
ARG REACT_APP_ENV=production
# Variables de entorno para el build
ENV REACT_APP_API_URL=$REACT_APP_API_URL
ENV REACT_APP_ENV=$REACT_APP_ENV
WORKDIR /app
# Copiar archivos de dependencias primero (mejor caché)
COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
# Instalar dependencias
RUN if [ -f package-lock.json ]; then npm ci --legacy-peer-deps; \
elif [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm install --frozen-lockfile; \
else npm install; fi
# Copiar código fuente
COPY . .
# Build de producción
RUN npm run build
# ============================================
# STAGE 2: Production (Nginx)
# ============================================
FROM nginx:1.26-alpine AS production
# Etiquetas de metadata
LABEL maintainer="tu-email@ejemplo.com"
LABEL description="React App - Producción"
LABEL version="1.0.0"
# Instalar curl para healthcheck
RUN apk add --no-cache curl
# Eliminar configuración por defecto de Nginx
RUN rm -rf /usr/share/nginx/html/*
RUN rm /etc/nginx/conf.d/default.conf
# Copiar build de React
COPY --from=builder /app/build /usr/share/nginx/html
# Copiar configuración personalizada de Nginx
COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf
# Crear usuario no-root para seguridad
RUN chown -R nginx:nginx /usr/share/nginx/html && \
chown -R nginx:nginx /var/cache/nginx && \
chown -R nginx:nginx /var/log/nginx && \
touch /var/run/nginx.pid && \
chown -R nginx:nginx /var/run/nginx.pid
# Exponer puerto
EXPOSE 80
# Healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost/health || exit 1
# Comando de inicio
CMD ["nginx", "-g", "daemon off;"]docker/nginx/default.conf(Configuración Nginx optimizada)# ============================================
# Configuración Nginx para React SPA
# ============================================
server {
listen 80;
listen [::]:80;
server_name localhost;
# Directorio raíz donde está el build de React
root /usr/share/nginx/html;
index index.html;
# ==========================================
# COMPRESIÓN GZIP
# Reduce el tamaño de transferencia ~70%
# ==========================================
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/x-javascript
application/xml
application/xml+rss
application/vnd.ms-fontobject
application/x-font-ttf
font/opentype
image/svg+xml
image/x-icon;
# ==========================================
# HEADERS DE SEGURIDAD
# ==========================================
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Content Security Policy (ajustar según necesidades)
# add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
# ==========================================
# ENDPOINT DE HEALTHCHECK
# ==========================================
location /health {
access_log off;
return 200 "OK";
add_header Content-Type text/plain;
}
# ==========================================
# ARCHIVOS ESTÁTICOS CON CACHÉ LARGO
# Assets con hash en el nombre (JS, CSS, imágenes)
# ==========================================
location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# ==========================================
# ARCHIVOS JSON Y MANIFEST
# Caché corto porque pueden cambiar
# ==========================================
location ~* \.(?:json|manifest)$ {
expires 1d;
add_header Cache-Control "public, must-revalidate";
}
# ==========================================
# CONFIGURACIÓN SPA (React Router)
# Redirige todas las rutas a index.html
# ==========================================
location / {
try_files $uri $uri/ /index.html;
# No cachear index.html
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# ==========================================
# DESHABILITAR ACCESO A ARCHIVOS OCULTOS
# ==========================================
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# ==========================================
# LOGS
# ==========================================
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
}docker-compose.yml(Base)# ============================================
# Docker Compose - Configuración Base
# ============================================
services:
react-app:
build:
context: .
dockerfile: docker/Dockerfile
args:
- REACT_APP_API_URL=${REACT_APP_API_URL}
- REACT_APP_ENV=${REACT_APP_ENV}
image: biometria-react:${APP_VERSION:-latest}
container_name: biometria-react-nginx
restart: unless-stopped
ports:
- "${REACT_PORT:-3000}:80"
environment:
- TZ=${TZ:-America/Argentina/Buenos_Aires}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
- laravel-network
labels:
- "app.name=biometria-react"
- "app.env=${REACT_APP_ENV:-production}"
networks:
laravel-network:
external: true
name: biometria-api_laravel-networkdocker-compose.prod.yml(Override para Producción)# ============================================
# Docker Compose - Override Producción
# Uso: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# ============================================
services:
react-app:
restart: always
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.1'
memory: 64M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"docker-compose.dev.yml(Override para Desarrollo)# ============================================
# Docker Compose - Override Desarrollo
# Uso: docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# ============================================
services:
react-app:
build:
args:
- REACT_APP_ENV=development
volumes:
- ./build:/usr/share/nginx/html:ro
ports:
- "3000:80".dockerignore# Dependencias
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build local
build
dist
# Control de versiones
.git
.gitignore
.gitattributes
# Docker
Dockerfile*
docker-compose*
.docker
# IDE
.idea
.vscode
*.swp
*.swo
# Tests
coverage
.nyc_output
__tests__
*.test.js
*.spec.js
# Documentación
README.md
docs
*.md
# Variables de entorno (solo .env.example va al build)
.env
.env.local
.env.development
.env.production
.env*.local
# Misceláneos
.DS_Store
Thumbs.db
*.log.env.example# ============================================
# Variables de Entorno - React App
# Copiar a .env y configurar valores
# ============================================
# Versión de la aplicación
APP_VERSION=1.0.0
# Puerto donde se expondrá la aplicación
REACT_PORT=3000
# Ambiente: development | testing | production
REACT_APP_ENV=production
# URL de la API Backend (Laravel)
# En producción: URL del servidor
# En desarrollo local: http://localhost:8080 o http://host.docker.internal:8080
REACT_APP_API_URL=http://localhost:8080/api
# Zona horaria
TZ=America/Argentina/Buenos_Aires
# ============================================
# Variables adicionales de React (REACT_APP_*)
# ============================================
# REACT_APP_NOMBRE_VARIABLE=valor.env.developmentREACT_APP_ENV=development
REACT_APP_API_URL=http://localhost:8080/api
REACT_PORT=3000
APP_VERSION=dev.env.testingREACT_APP_ENV=testing
REACT_APP_API_URL=http://api-testing.tu-dominio.com/api
REACT_PORT=3000
APP_VERSION=test.env.productionREACT_APP_ENV=production
REACT_APP_API_URL=https://api.tu-dominio.com/api
REACT_PORT=3000
APP_VERSION=1.0.0# 1. Clonar o navegar al proyecto
cd C:\ruta\a\tu\react-app
# 2. Crear archivo .env desde el ejemplo
copy .env.example .env
# 3. Editar .env con tus valores
notepad .env
# 4. Construir la imagen
docker compose build
# 5. Iniciar el contenedor
docker compose up -d
# 6. Verificar estado
docker compose ps
docker compose logs -f react-app# 1. Conectarse al servidor
ssh usuario@servidor
# 2. Clonar repositorio
git clone https://tu-repositorio.git /opt/apps/react-app
cd /opt/apps/react-app
# 3. Configurar variables de entorno
cp .env.example .env
nano .env # Configurar valores de producción
# 4. Crear la red si no existe (debe coincidir con la API)
docker network create biometria-api_laravel-network 2>/dev/null || true
# 5. Construir imagen de producción
docker compose -f docker-compose.yml -f docker-compose.prod.yml build --no-cache
# 6. Desplegar
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 7. Verificar
docker compose ps
curl http://localhost:3000/health# 1. Obtener últimos cambios
cd /opt/apps/react-app
git pull origin main
# 2. Reconstruir imagen
docker compose -f docker-compose.yml -f docker-compose.prod.yml build --no-cache
# 3. Reiniciar con cero downtime
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --force-recreate
# 4. Limpiar imágenes antiguas
docker image prune -f| Aspecto | Recomendación |
|---|---|
| HTTPS | Usar un reverse proxy (Traefik/Nginx externo) con certificados SSL |
| Headers | Ya configurados en nginx.conf (X-Frame-Options, etc.) |
| Variables | NUNCA commitear archivos .env con secretos |
| Usuario | El contenedor corre con usuario no-root |
| Recursos | Limitar CPU/memoria en producción |
| Logs | Configurar rotación de logs |
| Red | Usar red interna de Docker para comunicación con API |
Dado que tu API Laravel ya está en la red laravel-network, la aplicación React puede comunicarse internamente usando el nombre del contenedor:
# Comunicación interna Docker (más rápido, más seguro)
REACT_APP_API_URL=http://biometria-api-nginx:8080/api
# O comunicación externa (si están en redes separadas)
REACT_APP_API_URL=http://tu-servidor:8080/apiEsta configuración sigue exactamente las mismas convenciones que tu API Laravel, con healthchecks, redes compartidas, y estructura de archivos consistente. El build multi-stage genera una imagen final de ~25MB muy eficiente para producción.
No he generado ningún proyecto todavía. Lo que leí fue un documento de configuración/guía que compartiste sobre cómo desplegar una aplicación React con Docker.
Si deseas, puedo crear un proyecto con toda esa estructura de Docker que describes en el documento:
docker/Dockerfile (multi-stage optimizado)docker/nginx/default.confdocker-compose.ymldocker-compose.prod.ymldocker-compose.dev.yml.dockerignore.env.example¿Quieres que genere estos archivos de configuración Docker para que puedas descargarlos como ZIP? También necesitaría saber si tienes una aplicación React existente o si quieres que cree una aplicación React básica de ejemplo junto con la configuración Docker.