Skip to content

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 --> DB

Principios

  • 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

Ver tambien