Appearance
Deployment - Portal de Clientes
Modelo de despliegue basado en Docker por tenant para el frontend PWA. El backend es compartido.
Estado: Planificado
Modelo de Deployment
Cada tenant recibe su propia instancia Docker del frontend. El backend (bautista-backend) es compartido y no se dockeriza por tenant.
mermaid
graph TD
subgraph "Docker Host"
subgraph "Tenant A"
ContA["Docker Container<br/>nginx:alpine<br/>Puerto 3001"]
EnvA[".env<br/>VITE_TENANT_ID=tenant_a<br/>VITE_BACKEND_URL=https://api.bautista.com"]
end
subgraph "Tenant B"
ContB["Docker Container<br/>nginx:alpine<br/>Puerto 3002"]
EnvB[".env<br/>VITE_TENANT_ID=tenant_b<br/>VITE_BACKEND_URL=https://api.bautista.com"]
end
subgraph "Tenant C"
ContC["Docker Container<br/>nginx:alpine<br/>Puerto 3003"]
EnvC[".env<br/>VITE_TENANT_ID=tenant_c<br/>VITE_BACKEND_URL=https://api.bautista.com"]
end
end
RP["Reverse Proxy<br/>Traefik o nginx"]
RP --> ContA
RP --> ContB
RP --> ContC
subgraph "Backend Compartido"
API["bautista-backend<br/>PHP 8.2 + Slim 4"]
DB["PostgreSQL<br/>Multi-tenant (schemas)"]
end
ContA --> API
ContB --> API
ContC --> API
API --> DBPrincipios
- Un contenedor Docker por tenant: Cada tenant tiene su propia instancia del frontend PWA
- Backend compartido: Todos los contenedores apuntan al mismo backend API
- Configuracion via .env: Branding, tenant_id, sucursal_id, backend_url, todo en variables de entorno
- Sin multi-tenancy por dominio: La URL no determina el tenant. Cada instancia Docker ya esta pre-configurada
- Build multi-stage: Dockerfile con etapa Node (build) + etapa nginx:alpine (serve)
Documentacion
Infraestructura
Arquitectura Docker, Dockerfile, docker-compose template, reverse proxy, onboarding de nuevos tenants.
Incluye:
- Dockerfile multi-stage
- docker-compose template por tenant
- Configuracion reverse proxy (Traefik o nginx)
- Template .env
- Script de onboarding para nuevos tenants
- SSL/TLS
- Monitoreo
Dockerfile
Build multi-stage para el frontend PWA:
dockerfile
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG VITE_BACKEND_URL
ARG VITE_TENANT_ID
ARG VITE_SUCURSAL_ID
ARG VITE_APP_NAME
ARG VITE_LOGO_URL
ARG VITE_PRIMARY_COLOR
ARG VITE_THEME_COLOR
RUN npm run build
# Stage 2: Serve
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Las variables VITE_* se inyectan como build args porque Vite las resuelve en build time (no en runtime).
nginx.conf
Configuracion para SPA routing:
nginx
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}docker-compose Template por Tenant
yaml
# docker-compose.tenant-a.yml
version: "3.8"
services:
portal-tenant-a:
build:
context: ./portal-usuarios
args:
VITE_BACKEND_URL: "https://api.bautista.com"
VITE_TENANT_ID: "tenant_a"
VITE_SUCURSAL_ID: "suc0001"
VITE_APP_NAME: "Portal Empresa A"
VITE_LOGO_URL: "https://empresa-a.com/logo.png"
VITE_PRIMARY_COLOR: "#1e40af"
VITE_THEME_COLOR: "#1e3a8a"
ports:
- "3001:80"
restart: unless-stopped