Stack de desarrollo multiinquilino con Docker: configuración completa para despliegues escalables por cliente
Cómo crear un entorno de desarrollo multiinquilino basado en plantillas, con 16 servicios en contenedores, que funciona sin conexión y sigue siendo accesible en línea mediante enrutamiento por subdominios
Gestionar entornos de desarrollo para múltiples clientes a menudo significa elegir entre configuraciones manuales complejas o soluciones en la nube costosas. Los despliegues manuales consumen tiempo y son propensos a errores. Las plataformas en la nube son convenientes pero crean dependencia de proveedores y costos continuos que escalan con el uso.
Hoy veremos cómo construir un stack de desarrollo multiinquilino escalable, con entornos lógicamente aislados por cliente, que ofrece ambas ventajas: aislamiento sólido entre los entornos de los clientes y despliegue automatizado, manteniendo el control total de su infraestructura. Este enfoque se basa en nuestra filosofía de soluciones autoalojadas, como hemos mostrado al explicar cómo puede autoalojar n8n para automatización de flujos de trabajo y desplegar Windmill con Docker para un control operativo completo.
Las herramientas que utilizamos
Comencemos por entender qué hace cada pieza en nuestra arquitectura integral de 16 contenedores:
Docker: su base para el uso de contenedores
Docker proporciona el aislamiento y la consistencia que necesitamos para entornos multiinquilino. Cada cliente obtiene sus propios contenedores con configuraciones idénticas, lo que ayuda a que lo que funciona en desarrollo funcionará en producción. Es como tener varios servidores separados que se ejecutan en el mismo hardware, aunque siguen compartiendo su CPU, memoria, disco y kernel.
¿La ventaja clave? Un aislamiento sólido entre clientes. Los datos, las configuraciones y las personalizaciones de cada cliente se alojan en contenedores, redes y volúmenes separados, por lo que no interfieren con los de otro. Esto importa al gestionar varios clientes empresariales con distintos requisitos y necesidades de seguridad.
Traefik: proxy inverso y balanceador de carga inteligente
Traefik actúa como un gestor de tráfico inteligente que enruta automáticamente las solicitudes al entorno del cliente correcto según el nombre de dominio. En lugar de configurar manualmente reglas complejas de Apache o Nginx, Traefik lee las etiquetas de sus contenedores Docker y configura el enrutamiento automáticamente.
Piense en Traefik como un recepcionista que sabe exactamente a qué oficina (contenedor) debe ir cada visitante (solicitud), sin que usted tenga que dar indicaciones cada vez. En nuestra configuración, Traefik gestiona la terminación SSL y el descubrimiento automático de servicios, y proporciona paneles de monitoreo detallados.
Cloudflare Tunnels: acceso externo seguro
Cloudflare Tunnels proporciona acceso seguro a su stack de desarrollo local sin configuraciones complejas de firewall ni VPN. Cada dominio de cliente tiene su propio túnel, lo que mantiene separada la ruta de acceso externo de cada cliente.
La ventaja es que sus entornos de desarrollo permanecen locales y seguros, pero los clientes pueden acceder a sus servicios específicos desde cualquier lugar con la autenticación adecuada, como en la configuración de acceso externo seguro de nuestra guía de alojamiento de n8n.
El stack completo de servicios: todo lo que sus clientes necesitan
Nuestro stack multiinquilino incluye cuatro categorías principales de servicios distribuidas en 16 contenedores por cliente:
Automatización de flujos de trabajo y lógica de negocio:
- n8n: Plataforma completa de automatización de flujos de trabajo para la automatización de procesos empresariales
- Authentik: Inicio de sesión único y gestión de identidad de nivel empresarial (3 contenedores: servidor, worker, caché Redis)
Servicios de base de datos y backend:
- PostgreSQL: Backend de base de datos robusto que da soporte a todos los servicios con una gestión optimizada del conjunto de conexiones
- Supabase Stack: Backend como servicio completo con 6 contenedores especializados (Studio, Meta, Auth, REST API, Realtime, Kong Gateway)
- NocoDB: Interfaz de base de datos sin código para la gestión de datos de clientes
IA e inteligencia:
- Ollama: Modelos de lenguaje de IA ejecutados localmente, con aceleración por GPU para la automatización inteligente
- Qdrant (opcional): Base de datos vectorial para flujos de trabajo de IA avanzados y búsqueda por similitud
Infraestructura y monitoreo:
- Cloudflare Tunnel: Conectividad externa segura
- Traefik: Proxy inverso con SSL automático y panel de monitoreo
Cómo funciona todo en conjunto
Aquí está el flujo completo cuando un cliente accede a su entorno:
- El cliente navega a su dominio personalizado (por ejemplo,
workflows.client-a.com) - Cloudflare Tunnel enruta la solicitud a su instancia local de Traefik
- Traefik lee el dominio, aplica middleware (autenticación, SSL y límites de solicitudes) y reenvía la solicitud al contenedor del cliente correcto
- Authentik gestiona la autenticación SSO en todos los servicios si está configurado
- El cliente obtiene su propio entorno aislado con sus datos y configuraciones
- Los entornos de otros clientes no son accesibles a través de las rutas de este cliente
Todo se mantiene organizado y separado, y cada cliente tiene su propia estructura de subdominios, como auth.client-a.com, database.client-a.com, backend.client-a.com, etc.
Configuración: los pasos prácticos
Preparar la base
Primero, necesitará Docker Desktop instalado y un sistema de gestión de dominios. Recomendamos configurar una estructura DNS con comodín para facilitar la incorporación de clientes:
# Install Docker Desktop (macOS)
brew install --cask docker
# Verify installation
docker --version
docker-compose --version
# Ensure sufficient resources for multi-container environments
# Recommended: 16GB RAM, 8+ CPU cores, 500GB+ SSD storage
Crear el sistema de plantillas
La clave es un enfoque basado en plantillas. En lugar de configurar manualmente cada cliente, creamos plantillas que se pueden desplegar al instante con configuraciones específicas para cada uno.
Cree la estructura completa de directorios:
mkdir -p development-stack/{template,deployments}
cd development-stack/template
# Create service-specific configuration directories
mkdir -p {traefik,authentik,supabase,init}
Configuración completa de la plantilla para varios servicios
Cree una plantilla completa de docker-compose.yml con los 16 servicios. Es una plantilla de desarrollo: el panel de Traefik funciona sin autenticación, por lo que la plantilla lo publica, junto con PostgreSQL, solo en 127.0.0.1, donde otras máquinas no pueden acceder a ellos (consulte la sección sobre refuerzo de la seguridad para producción más abajo):
version: '3.8'
networks:
default:
name: ${TENANT_NETWORK}
driver: bridge
services:
# External Connectivity
cloudflare-tunnel:
image: cloudflare/cloudflared:latest
container_name: ${TENANT_PREFIX}-tunnel
command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TOKEN}
networks:
- default
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "cloudflared tunnel info ${TUNNEL_ID} || exit 1"]
interval: 30s
timeout: 10s
retries: 3
# Reverse Proxy & Load Balancer
traefik:
image: traefik:v3.0
container_name: ${TENANT_PREFIX}-traefik
command:
- "--api.dashboard=true"
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.network=${TENANT_NETWORK}"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=${ADMIN_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "${TRAEFIK_PORT}:80"
- "${TRAEFIK_SECURE_PORT}:443"
- "127.0.0.1:${TRAEFIK_DASHBOARD_PORT}:8080"
networks:
- default
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/acme.json:/acme.json
healthcheck:
test: ["CMD", "traefik", "healthcheck"]
interval: 30s
timeout: 10s
retries: 3
# Database Backend
postgres:
image: postgres:15-alpine
container_name: ${TENANT_PREFIX}-postgres
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_MULTIPLE_DATABASES: n8n,supabase,authentik,nocodb
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
networks:
- default
ports:
- "127.0.0.1:${POSTGRES_PORT}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 30s
timeout: 10s
retries: 5
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 1G
# Workflow Automation
n8n:
image: n8nio/n8n:latest
container_name: ${TENANT_PREFIX}-n8n
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: ${POSTGRES_USER}
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_PROTOCOL: ${N8N_PROTOCOL}
N8N_HOST: ${N8N_DOMAIN}
N8N_PORT: 5678
N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE}
WEBHOOK_URL: https://${N8N_DOMAIN}
N8N_EDITOR_BASE_URL: https://${N8N_DOMAIN}
EXECUTIONS_DATA_PRUNE: "true"
EXECUTIONS_DATA_MAX_AGE: 168
volumes:
- n8n_data:/home/node/.n8n
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`${N8N_DOMAIN}`)"
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
- "traefik.http.routers.n8n.middlewares=${AUTH_MIDDLEWARE}"
depends_on:
postgres:
condition: service_healthy
# No-Code Database Interface
nocodb:
image: nocodb/nocodb:latest
container_name: ${TENANT_PREFIX}-nocodb
environment:
NC_DB: "pg://postgres:${POSTGRES_PASSWORD}@postgres:5432/nocodb"
NC_PUBLIC_URL: https://${NOCODB_DOMAIN}
NC_DISABLE_TELE: "true"
NC_ADMIN_EMAIL: ${ADMIN_EMAIL}
NC_ADMIN_PASSWORD: ${NOCODB_ADMIN_PASSWORD}
volumes:
- nocodb_data:/usr/app/data
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.nocodb.rule=Host(`${NOCODB_DOMAIN}`)"
- "traefik.http.routers.nocodb.tls.certresolver=letsencrypt"
- "traefik.http.services.nocodb.loadbalancer.server.port=8080"
- "traefik.http.routers.nocodb.middlewares=${AUTH_MIDDLEWARE}"
depends_on:
postgres:
condition: service_healthy
# Supabase Backend Stack (5 containers)
supabase-studio:
image: supabase/studio:latest
container_name: ${TENANT_PREFIX}-supabase-studio
environment:
STUDIO_PG_META_URL: http://supabase-meta:8080
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DEFAULT_ORGANIZATION_NAME: ${CLIENT_NAME}
DEFAULT_PROJECT_NAME: ${CLIENT_NAME} Project
SUPABASE_PUBLIC_URL: https://${SUPABASE_DOMAIN}
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.supabase-studio.rule=Host(`${SUPABASE_DOMAIN}`)"
- "traefik.http.routers.supabase-studio.tls.certresolver=letsencrypt"
- "traefik.http.services.supabase-studio.loadbalancer.server.port=3000"
- "traefik.http.routers.supabase-studio.middlewares=${AUTH_MIDDLEWARE}"
healthcheck:
disable: true
depends_on:
postgres:
condition: service_healthy
supabase-meta:
image: supabase/postgres-meta:latest
container_name: ${TENANT_PREFIX}-supabase-meta
environment:
PG_META_PORT: 8080
PG_META_DB_HOST: postgres
PG_META_DB_PORT: 5432
PG_META_DB_NAME: supabase
PG_META_DB_USER: ${POSTGRES_USER}
PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
networks:
- default
depends_on:
postgres:
condition: service_healthy
supabase-auth:
image: supabase/gotrue:latest
container_name: ${TENANT_PREFIX}-supabase-auth
environment:
GOTRUE_API_HOST: 0.0.0.0
GOTRUE_API_PORT: 9999
GOTRUE_DB_DRIVER: postgres
GOTRUE_DB_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/supabase
GOTRUE_SITE_URL: https://${SUPABASE_DOMAIN}
GOTRUE_JWT_SECRET: ${SUPABASE_JWT_SECRET}
GOTRUE_JWT_EXP: 3600
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
networks:
- default
depends_on:
postgres:
condition: service_healthy
supabase-rest:
image: postgrest/postgrest:latest
container_name: ${TENANT_PREFIX}-supabase-rest
environment:
PGRST_DB_URI: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/supabase
PGRST_DB_SCHEMAS: public,graphql_public
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${SUPABASE_JWT_SECRET}
PGRST_DB_USE_LEGACY_GUCS: "false"
networks:
- default
depends_on:
postgres:
condition: service_healthy
supabase-realtime:
image: supabase/realtime:latest
container_name: ${TENANT_PREFIX}-supabase-realtime
environment:
PORT: 4000
DB_HOST: postgres
DB_PORT: 5432
DB_USER: ${POSTGRES_USER}
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_NAME: supabase
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
DB_ENC_KEY: supabaserealtime
API_JWT_SECRET: ${SUPABASE_JWT_SECRET}
FLY_ALLOC_ID: fly123
FLY_APP_NAME: realtime
SECRET_KEY_BASE: ${SUPABASE_JWT_SECRET}
ERL_AFLAGS: -proto_dist inet_tcp
ENABLE_TAILSCALE: "false"
DNS_NODES: "''"
networks:
- default
command: >
sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server"
depends_on:
postgres:
condition: service_healthy
supabase-kong:
image: kong:3.2-alpine
container_name: ${TENANT_PREFIX}-supabase-kong
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
KONG_DNS_ORDER: LAST,A,CNAME
KONG_PLUGINS: request-size-limiting,cors,key-auth,rate-limiting
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
volumes:
- ./supabase/kong.yml:/var/lib/kong/kong.yml:ro
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.kong.rule=Host(`api.${CLIENT_DOMAIN}`)"
- "traefik.http.routers.kong.tls.certresolver=letsencrypt"
- "traefik.http.services.kong.loadbalancer.server.port=8000"
- "traefik.http.routers.kong.middlewares=${AUTH_MIDDLEWARE}"
# Local AI Language Models
ollama:
image: ollama/ollama:latest
container_name: ${TENANT_PREFIX}-ollama
environment:
OLLAMA_HOST: 0.0.0.0:11434
OLLAMA_ORIGINS: "*"
volumes:
- ollama_data:/root/.ollama
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.ollama.rule=Host(`ai.${CLIENT_DOMAIN}`)"
- "traefik.http.routers.ollama.tls.certresolver=letsencrypt"
- "traefik.http.services.ollama.loadbalancer.server.port=11434"
- "traefik.http.routers.ollama.middlewares=${AUTH_MIDDLEWARE}"
deploy:
resources:
limits:
memory: 16G
reservations:
memory: 8G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 30s
timeout: 10s
retries: 3
# Enterprise SSO Authentication (3 containers)
authentik-redis:
image: redis:alpine
container_name: ${TENANT_PREFIX}-authentik-redis
command: --save 60 1 --loglevel warning
networks:
- default
volumes:
- authentik_redis_data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 30s
timeout: 3s
retries: 3
authentik-server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: ${TENANT_PREFIX}-authentik-server
command: server
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__USER: ${POSTGRES_USER}
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${POSTGRES_PASSWORD}
AUTHENTIK_REDIS__HOST: authentik-redis
volumes:
- authentik_media:/media
- authentik_templates:/templates
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.authentik.rule=Host(`auth.${CLIENT_DOMAIN}`)"
- "traefik.http.routers.authentik.tls.certresolver=letsencrypt"
- "traefik.http.services.authentik.loadbalancer.server.port=9000"
depends_on:
postgres:
condition: service_healthy
authentik-redis:
condition: service_healthy
authentik-worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: ${TENANT_PREFIX}-authentik-worker
command: worker
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__USER: ${POSTGRES_USER}
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${POSTGRES_PASSWORD}
AUTHENTIK_REDIS__HOST: authentik-redis
volumes:
- authentik_media:/media
- authentik_templates:/templates
- /var/run/docker.sock:/var/run/docker.sock
networks:
- default
depends_on:
postgres:
condition: service_healthy
authentik-redis:
condition: service_healthy
# Test Service for Health Monitoring
whoami:
image: traefik/whoami:latest
container_name: ${TENANT_PREFIX}-whoami
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`test.${CLIENT_DOMAIN}`)"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
volumes:
postgres_data:
n8n_data:
nocodb_data:
ollama_data:
authentik_redis_data:
authentik_media:
authentik_templates:
Plantilla completa de variables de entorno
Cree .env.template con todas las variables específicas del cliente:
# Client Configuration
CLIENT_NAME=CLIENT_NAME_PLACEHOLDER
CLIENT_DOMAIN=CLIENT_DOMAIN_PLACEHOLDER
TENANT_PREFIX=CLIENT_PREFIX_PLACEHOLDER
TENANT_NETWORK=CLIENT_NETWORK_PLACEHOLDER
# Service Domains (Subdomain-based routing)
N8N_DOMAIN=workflows.CLIENT_DOMAIN_PLACEHOLDER
NOCODB_DOMAIN=database.CLIENT_DOMAIN_PLACEHOLDER
SUPABASE_DOMAIN=backend.CLIENT_DOMAIN_PLACEHOLDER
AUTHENTIK_DOMAIN=auth.CLIENT_DOMAIN_PLACEHOLDER
# Infrastructure Ports
TRAEFIK_PORT=80
TRAEFIK_SECURE_PORT=443
TRAEFIK_DASHBOARD_PORT=8080
POSTGRES_PORT=5432
# Admin Configuration
ADMIN_EMAIL=admin@CLIENT_DOMAIN_PLACEHOLDER
# Database Configuration
POSTGRES_DB=main_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=SECURE_PASSWORD_PLACEHOLDER
# Service-Specific Passwords
NOCODB_ADMIN_PASSWORD=NOCODB_PASSWORD_PLACEHOLDER
SUPABASE_JWT_SECRET=SUPABASE_JWT_PLACEHOLDER
# Authentik SSO Configuration
AUTHENTIK_SECRET_KEY=AUTHENTIK_SECRET_PLACEHOLDER
AUTHENTIK_TAG=2024.8.3
# n8n Configuration
N8N_PROTOCOL=https
N8N_SECURE_COOKIE=true
# Cloudflare Integration
CLOUDFLARE_TOKEN=CLOUDFLARE_TOKEN_PLACEHOLDER
TUNNEL_ID=TUNNEL_ID_PLACEHOLDER
# Authentication Middleware (set to 'auth-global' for SSO, leave empty for no auth)
AUTH_MIDDLEWARE=
Scripts de inicialización de la base de datos
Defina la inicialización completa de la base de datos en init/01-create-multiple-databases.sql:
-- Create databases for all services
CREATE DATABASE n8n;
CREATE DATABASE nocodb;
CREATE DATABASE supabase;
CREATE DATABASE authentik;
-- Grant permissions
GRANT ALL PRIVILEGES ON DATABASE n8n TO postgres;
GRANT ALL PRIVILEGES ON DATABASE nocodb TO postgres;
GRANT ALL PRIVILEGES ON DATABASE supabase TO postgres;
GRANT ALL PRIVILEGES ON DATABASE authentik TO postgres;
-- Enable required extensions for Supabase
\c supabase;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- Enable required extensions for Authentik
\c authentik;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
\echo 'Multiple databases and extensions created successfully'
Configuración de Kong Gateway de Supabase
Cree supabase/kong.yml para el enrutamiento de la puerta de enlace de API:
_format_version: "3.0"
services:
- name: auth-v1-open
url: http://supabase-auth:9999/verify
plugins:
- name: cors
routes:
- name: auth-v1-open
strip_path: true
paths:
- /auth/v1/verify
methods:
- POST
- OPTIONS
- name: auth-v1-open-callback
url: http://supabase-auth:9999/callback
plugins:
- name: cors
routes:
- name: auth-v1-open-callback
strip_path: true
paths:
- /auth/v1/callback
methods:
- GET
- POST
- OPTIONS
- name: auth-v1
_comment: "GoTrue: /auth/v1/* -> http://supabase-auth:9999/*"
url: http://supabase-auth:9999/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
routes:
- name: auth-v1-all
strip_path: true
paths:
- /auth/v1/
methods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
- name: rest-v1
_comment: "PostgREST: /rest/v1/* -> http://supabase-rest:3000/*"
url: http://supabase-rest:3000/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: true
routes:
- name: rest-v1-all
strip_path: true
paths:
- /rest/v1/
methods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
- name: realtime-v1
_comment: "Realtime: /realtime/v1/* -> ws://supabase-realtime:4000/socket/*"
url: http://supabase-realtime:4000/socket/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
routes:
- name: realtime-v1-all
strip_path: true
paths:
- /realtime/v1/
methods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
consumers:
- username: anon
keyauth_credentials:
- key: your-anon-key-here
- username: service_role
keyauth_credentials:
- key: your-service-role-key-here
plugins:
- name: cors
config:
origins:
- "*"
methods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
headers:
- Accept
- Accept-Version
- Content-Length
- Content-MD5
- Content-Type
- Date
- X-Auth-Token
- Authorization
- X-Forwarded-For
- X-Forwarded-Proto
- X-Forwarded-Port
exposed_headers:
- X-Auth-Token
credentials: true
max_age: 3600
Script de despliegue automatizado
Use este script de despliegue para crear nuevos entornos de cliente en minutos. Calcula todos los puertos del host (HTTP, HTTPS y panel de Traefik, además de PostgreSQL) a partir de una suma de verificación del prefijo del cliente, para que varios clientes puedan compartir un host. Compruebe que los desplazamientos de dos clientes no colisionen:
#!/bin/bash
# deploy-client.sh - Complete Multi-Tenant Deployment
CLIENT_DOMAIN=$1
CLIENT_NAME=$2
CLOUDFLARE_TOKEN=$3
if [ -z "$CLIENT_DOMAIN" ] || [ -z "$CLIENT_NAME" ] || [ -z "$CLOUDFLARE_TOKEN" ]; then
echo "Usage: ./deploy-client.sh example.com 'Client Name' 'cloudflare-token'"
echo ""
echo "Example: ./deploy-client.sh client-a.com 'Client A Corporation' 'your-cloudflare-token'"
exit 1
fi
CLIENT_PREFIX=$(echo $CLIENT_DOMAIN | sed 's/[.-]//g' | tr '[:upper:]' '[:lower:]')
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "Deploying complete multi-tenant environment..."
echo "Configuration:"
echo " Domain: $CLIENT_DOMAIN"
echo " Name: $CLIENT_NAME"
echo " Prefix: $CLIENT_PREFIX"
echo " Timestamp: $TIMESTAMP"
echo ""
# Create deployment directory
DEPLOY_DIR="../deployments/$CLIENT_DOMAIN"
mkdir -p "$DEPLOY_DIR"/{traefik,authentik,supabase,init,logs}
echo "Created deployment directory structure"
# Copy template files
cp docker-compose.yml "$DEPLOY_DIR/"
cp -r {traefik,authentik,supabase,init}/ "$DEPLOY_DIR/" 2>/dev/null || true
echo "Copied configuration templates"
# Generate secure passwords and secrets
POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25)
NOCODB_PASSWORD=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-16)
SUPABASE_JWT_SECRET=$(openssl rand -base64 64 | tr -d "=+/" | cut -c1-64)
AUTHENTIK_SECRET=$(openssl rand -hex 32)
echo "Generated secure credentials"
# Calculate unique ports to avoid conflicts
PORT_OFFSET=$(($(echo "$CLIENT_PREFIX" | cksum | cut -f1 -d' ') % 1000))
TRAEFIK_DASHBOARD_PORT=$((8080 + PORT_OFFSET))
POSTGRES_PORT=$((5432 + PORT_OFFSET))
TRAEFIK_PORT=$((10000 + PORT_OFFSET))
TRAEFIK_SECURE_PORT=$((11000 + PORT_OFFSET))
# Create comprehensive environment file
cat .env.template | \
sed "s/CLIENT_NAME_PLACEHOLDER/$CLIENT_NAME/g" | \
sed "s/CLIENT_DOMAIN_PLACEHOLDER/$CLIENT_DOMAIN/g" | \
sed "s/CLIENT_PREFIX_PLACEHOLDER/$CLIENT_PREFIX/g" | \
sed "s/CLIENT_NETWORK_PLACEHOLDER/${CLIENT_PREFIX}-network/g" | \
sed "s/SECURE_PASSWORD_PLACEHOLDER/$POSTGRES_PASSWORD/g" | \
sed "s/NOCODB_PASSWORD_PLACEHOLDER/$NOCODB_PASSWORD/g" | \
sed "s/SUPABASE_JWT_PLACEHOLDER/$SUPABASE_JWT_SECRET/g" | \
sed "s/AUTHENTIK_SECRET_PLACEHOLDER/$AUTHENTIK_SECRET/g" | \
sed "s/CLOUDFLARE_TOKEN_PLACEHOLDER/$CLOUDFLARE_TOKEN/g" | \
sed "s/^TRAEFIK_PORT=80$/TRAEFIK_PORT=$TRAEFIK_PORT/" | \
sed "s/^TRAEFIK_SECURE_PORT=443$/TRAEFIK_SECURE_PORT=$TRAEFIK_SECURE_PORT/" | \
sed "s/8080/$TRAEFIK_DASHBOARD_PORT/g" | \
sed "s/5432/$POSTGRES_PORT/g" \
> "$DEPLOY_DIR/.env"
echo "Generated environment configuration"
# Create ACE file for Traefik SSL
touch "$DEPLOY_DIR/traefik/acme.json"
chmod 600 "$DEPLOY_DIR/traefik/acme.json"
# Initialize deployment
cd "$DEPLOY_DIR"
echo "Starting Docker containers..."
echo " This may take several minutes for first-time image downloads"
# Start core infrastructure first
docker-compose up -d cloudflare-tunnel traefik postgres
echo "Waiting for database to be ready..."
sleep 30
# Start all remaining services
docker-compose up -d
echo ""
echo "Multi-tenant environment deployed successfully!"
echo ""
echo "Access URLs:"
echo " Traefik Dashboard: http://localhost:$TRAEFIK_DASHBOARD_PORT"
echo " Workflows (n8n): https://workflows.$CLIENT_DOMAIN"
echo " Database (NocoDB): https://database.$CLIENT_DOMAIN"
echo " Backend (Supabase): https://backend.$CLIENT_DOMAIN"
echo " AI (Ollama): https://ai.$CLIENT_DOMAIN"
echo " Authentication: https://auth.$CLIENT_DOMAIN"
echo " API Gateway: https://api.$CLIENT_DOMAIN"
echo " Test Service: https://test.$CLIENT_DOMAIN"
echo ""
echo "Container Status:"
docker-compose ps
echo ""
echo "Generated Credentials (save these securely):"
echo " Client: $CLIENT_NAME"
echo " PostgreSQL Password: $POSTGRES_PASSWORD"
echo " NocoDB Admin Password: $NOCODB_PASSWORD"
echo " Supabase JWT Secret: [hidden - check .env file]"
echo ""
echo "Next Steps:"
echo " 1. Configure Cloudflare DNS: *.${CLIENT_DOMAIN} -> tunnel"
echo " 2. Wait 2-3 minutes for all services to initialize"
echo " 3. Access services via the URLs above"
echo " 4. Configure SSO via auth.$CLIENT_DOMAIN if needed"
echo ""
echo "Documentation: Visit tva.sg for setup guides and troubleshooting"
echo "Support: Contact us via tva.sg/contact for assistance"
Uso de su stack multiinquilino
Desplegar entornos para nuevos clientes
Crear un nuevo entorno de cliente resulta sencillo con nuestro script completo:
# Deploy Client A with full enterprise stack
./deploy-client.sh client-a.com "Client A Corporation" "your-cloudflare-token"
# Deploy Client B with different domain
./deploy-client.sh client-b.org "Client B Industries" "your-cloudflare-token"
# Deploy Startup C
./deploy-client.sh startup-c.io "Startup C" "your-cloudflare-token"
Cada despliegue crea:
- Red Docker dedicada con 16 contenedores
- Volúmenes de datos separados para almacenamiento persistente
- Contenedores específicos para cada servicio con monitoreo de su estado
- Configuración individual de túnel de Cloudflare
- Enrutamiento de dominio personalizado con certificados SSL
- Infraestructura SSO de nivel empresarial lista para activación
Gestionar varios entornos
Monitoree todos los entornos de clientes desde una ubicación central:
# Check all running environments across clients
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(client|startup)"
# View comprehensive logs for specific client
cd deployments/client-a.com
docker-compose logs -f --tail=50 n8n
# Health check all services for a client
docker-compose ps
docker-compose exec postgres pg_isready
# Restart specific services
docker-compose restart nocodb supabase-studio
# Update all services to latest images
docker-compose pull && docker-compose up -d
Análisis detallado de la arquitectura de contenedores
Nuestra arquitectura completa de 16 contenedores por cliente incluye:
Capa de infraestructura (4 contenedores):
cloudflare-tunnel: Conectividad externa seguratraefik: Proxy inverso con SSL automático y descubrimiento de serviciospostgres: Base de datos central con un conjunto de conexiones reutilizableswhoami: Monitoreo del estado y verificación del enrutamiento
Capa de aplicación (7 contenedores):
n8n: Automatización de flujos de trabajo con backend PostgreSQLnocodb: Interfaz de base de datos sin códigosupabase-studio: Panel de desarrollo backendsupabase-meta: Servicio de introspección de base de datossupabase-auth: Autenticación y gestión de usuariossupabase-rest: API REST autogeneradasupabase-realtime: Suscripciones y actualizaciones en tiempo real
Capa de IA y puerta de enlace (2 contenedores):
ollama: IA local con soporte de aceleración GPUsupabase-kong: Puerta de enlace de API con límites de solicitudes y CORS
Capa de seguridad empresarial (3 contenedores):
authentik-server: Servidor de autenticación SSOauthentik-worker: Tareas en segundo plano y notificacionesauthentik-redis: Gestión de sesiones y caché
Escalar los recursos por cliente
Ajuste los recursos según las necesidades y los patrones de uso del cliente:
# High-performance client configuration
services:
n8n:
deploy:
resources:
limits:
cpus: '4.0'
memory: 8G
reservations:
cpus: '2.0'
memory: 4G
postgres:
deploy:
resources:
limits:
cpus: '2.0'
memory: 4G
reservations:
cpus: '1.0'
memory: 2G
environment:
- POSTGRES_MAX_CONNECTIONS=200
- POSTGRES_SHARED_BUFFERS=1GB
- POSTGRES_EFFECTIVE_CACHE_SIZE=3GB
ollama:
deploy:
resources:
limits:
memory: 32G
reservations:
memory: 16G
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
Beneficios reales para su negocio
Aislamiento completo de clientes con funciones empresariales
Cada cliente obtiene su propio entorno completo, con SSO de nivel empresarial, capacidades de IA e infraestructura backend. Los datos, las configuraciones, las personalizaciones y las políticas de seguridad se mantienen separados. Un problema a nivel de aplicación en el entorno de un cliente queda dentro de ese entorno, pero, como todos los clientes comparten un host, los problemas a nivel del host, como el agotamiento de la CPU, la memoria o el disco, pueden afectar a todos. Es similar al aislamiento que logramos con nuestros despliegues individuales de n8n.
Incorporación rápida de clientes con todas las funciones
Los nuevos clientes pueden estar operativos con un stack completo de desarrollo y automatización en menos de 10 minutos. El script de despliegue automatiza la configuración, la generación de credenciales y la inicialización de servicios; los registros DNS de Cloudflare y el SSO opcional siguen siendo pasos manuales breves, como muestran las instrucciones finales del script.
Costos empresariales predecibles
Después de la configuración inicial, no hay costos de alojamiento por cliente más allá de su infraestructura base. A diferencia de las soluciones SaaS que cobran por usuario, por flujo de trabajo o por llamada a la API, usted paga un importe fijo por el servidor y puede ejecutar tantos entornos de cliente con todas las funciones empresariales como permitan sus recursos.
Coherencia de marca profesional
Cada cliente obtiene sus propios dominios de marca con subdominios profesionales (workflows.client.com, auth.client.com, etc.) y puede personalizar completamente sus entornos. No hay pies de página con la mención «con tecnología de» ni interfaces compartidas que diluyan la identidad de su marca.
Integración con n8n: automatización de flujos de trabajo empresariales a escala
Aquí es donde la configuración resulta especialmente potente. Como hemos mostrado al explicar cómo autoalojar n8n para automatización de flujos de trabajo, esta configuración multiinquilino proporciona a cada cliente su propia instancia completa de n8n integrada con un stack empresarial completo.
Cada cliente puede construir flujos de trabajo sofisticados que:
- Se conecten a sus propias bases de datos (NocoDB, PostgreSQL de Supabase)
- Usen sus propios modelos de IA (Ollama) para automatización inteligente
- Se autentiquen a través de SSO empresarial (Authentik)
- Se integren con las herramientas y API específicas de su negocio
- Procesen sus datos dentro de su propio entorno aislado
Esta combinación crea una potente plataforma para prestar servicios a clientes que le permite:
- Desplegar capacidades de automatización estandarizadas rápidamente
- Personalizar flujos de trabajo por cliente sin afectar a otros
- Escalar la prestación de servicios sin un aumento lineal de los costos
- Mantener la soberanía completa sobre los datos de cada cliente
- Ofrecer control de acceso basado en SSO como base para el trabajo de seguridad y cumplimiento normativo
Este enfoque se basa en los mismos principios que usamos en nuestra guía de configuración de Windmill con Docker, pero los amplía a una arquitectura multiinquilino completa.
Opciones de configuración avanzada
Implementar SSO empresarial con Authentik
Habilite el inicio de sesión único en todos los servicios del cliente configurando la autenticación delegada (forward authentication) de Authentik:
# Add to Traefik middleware configuration
middlewares:
auth-global:
forwardAuth:
address: "http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
Después, actualice las etiquetas de sus servicios para usar el middleware:
labels:
- "traefik.http.routers.n8n.middlewares=auth-global"
- "traefik.http.routers.nocodb.middlewares=auth-global"
- "traefik.http.routers.supabase-studio.middlewares=auth-global"
Añadir una base de datos vectorial para IA avanzada
Mejore las capacidades de IA con la base de datos vectorial Qdrant:
qdrant:
image: qdrant/qdrant:latest
container_name: ${TENANT_PREFIX}-qdrant
environment:
QDRANT__SERVICE__HTTP_PORT: 6333
QDRANT__SERVICE__GRPC_PORT: 6334
volumes:
- qdrant_data:/qdrant/storage
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.qdrant.rule=Host(`vector.${CLIENT_DOMAIN}`)"
- "traefik.http.routers.qdrant.tls.certresolver=letsencrypt"
- "traefik.http.services.qdrant.loadbalancer.server.port=6333"
- "traefik.http.routers.qdrant.middlewares=${AUTH_MIDDLEWARE}"
Implementar una arquitectura de IA híbrida
Para obtener un rendimiento óptimo, considere un enfoque híbrido que combine IA en contenedores e IA ejecutada de forma nativa:
# Install Ollama natively on host for GPU acceleration
brew install ollama
# Configure containers to use native Ollama
# In docker-compose.yml, services can access via host.docker.internal:11434
n8n:
environment:
- OLLAMA_HOST=host.docker.internal:11434
En macOS, esto es relevante porque Docker Desktop no admite GPU en contenedores, mientras que Ollama ejecutado de forma nativa utiliza directamente la aceleración por GPU. En nuestra configuración, esto mejoró el rendimiento aproximadamente entre 5 y 6 veces, mientras los demás servicios seguían ejecutándose en contenedores.
Stack de monitoreo y observabilidad
Añada un monitoreo completo por cliente:
prometheus:
image: prom/prometheus:latest
container_name: ${TENANT_PREFIX}-prometheus
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.prometheus.rule=Host(`metrics.${CLIENT_DOMAIN}`)"
grafana:
image: grafana/grafana:latest
container_name: ${TENANT_PREFIX}-grafana
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/dashboards:/var/lib/grafana/dashboards
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=Host(`monitoring.${CLIENT_DOMAIN}`)"
- "traefik.http.routers.grafana.middlewares=${AUTH_MIDDLEWARE}"
Problemas comunes y soluciones
Errores «Service Unavailable» o HTTP 502
Normalmente, esto significa que Traefik no puede acceder al contenedor de destino. Compruebe lo siguiente:
# Verify container is running and healthy
docker-compose ps
docker-compose logs traefik --tail=20
# Check container is on correct network
docker network ls
docker network inspect ${CLIENT_PREFIX}-network
# Verify Traefik labels are correct
docker-compose config --services
Problemas de resolución DNS
La configuración DNS con comodín es crucial para el enrutamiento por subdominios:
# Correct Cloudflare DNS configuration
*.client-a.com CNAME tunnel-uuid.cfargotunnel.com
*.client-b.org CNAME tunnel-uuid2.cfargotunnel.com
# Test DNS resolution
nslookup workflows.client-a.com
dig workflows.client-a.com
Agotamiento de recursos entre varios clientes
Monitoree el uso de recursos en todos los entornos de clientes:
# Check overall system resource usage
docker stats --no-stream
htop
# Check disk usage per client
du -sh deployments/*/
df -h
# Monitor container memory usage
docker-compose -f deployments/*/docker-compose.yml ps --format "table {{.Name}}\t{{.Size}}"
Agotamiento del conjunto de conexiones de la base de datos
Con muchos clientes, pueden alcanzarse los límites de conexiones de PostgreSQL. Configure lo siguiente en cada despliegue:
-- Connect to client database
docker-compose exec postgres psql -U postgres
-- Increase connection limit
ALTER SYSTEM SET max_connections = 300;
ALTER SYSTEM SET shared_buffers = '256MB';
ALTER SYSTEM SET effective_cache_size = '1GB';
-- Reload configuration
SELECT pg_reload_conf();
Problemas de configuración de SSO con Authentik
Problemas comunes de configuración de SSO y soluciones:
# Check Authentik containers are running
docker-compose ps | grep authentik
# Verify database initialization
docker-compose exec postgres psql -U postgres -d authentik -c "\dt"
# Check Authentik logs for startup issues
docker-compose logs authentik-server --tail=50
# Reset Authentik admin password if needed
docker-compose exec authentik-server ak create_admin_group
docker-compose exec authentik-server ak bootstrap_tasks
Problemas de conexión del túnel de Cloudflare
Depure los problemas de conectividad del túnel:
# Check tunnel status
docker-compose logs cloudflare-tunnel --tail=20
# Verify tunnel configuration in Cloudflare dashboard
# Ensure wildcard routing: *.client-domain.com
# Test tunnel connectivity
curl -I https://test.client-domain.com
Consideraciones de infraestructura
Dimensionar su infraestructura para varios clientes
Para una configuración típica que maneja 10-15 clientes simultáneamente con stacks completos de 16 contenedores:
Requisitos mínimos:
- CPU: 16-24 núcleos (2 núcleos por entorno de cliente activo)
- RAM: 64-128 GB (4-8 GB por cliente según el uso de IA)
- Almacenamiento: SSD NVMe de 2 TB o más (las bases de datos, los modelos de IA y los registros crecen con el tiempo)
- Red: Conexión Gigabit para que los clientes accedan con rapidez
Recomendado para producción:
- Servidor: Hetzner CCX62 o similar (48 vCPU, 192 GB de RAM)
- Almacenamiento: 4 TB NVMe con sistema automatizado de copias de seguridad
- Red: Múltiples conexiones redundantes
- Monitoreo: Stack completo de observabilidad con alertas
Estrategia de copias de seguridad para entornos de varios clientes
Implemente copias de seguridad automatizadas por cliente:
#!/bin/bash
# backup-all-clients.sh - Comprehensive backup solution
BACKUP_DIR="/opt/backups"
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)
STACK_DIR=$(pwd) # run from the development-stack directory
mkdir -p "$BACKUP_DIR"
for client_dir in "$STACK_DIR"/deployments/*/; do
if [ -d "$client_dir" ]; then
CLIENT_DOMAIN=$(basename "$client_dir")
echo "Backing up client: $CLIENT_DOMAIN"
cd "$client_dir"
# Backup databases with compression
docker-compose exec -T postgres pg_dumpall -U postgres | gzip > "${BACKUP_DIR}/${CLIENT_DOMAIN}_db_${BACKUP_DATE}.sql.gz"
# Backup persistent volumes
docker run --rm \
-v "${BACKUP_DIR}":/backup \
-v "${CLIENT_DOMAIN//.}_n8n_data":/data/n8n:ro \
-v "${CLIENT_DOMAIN//.}_nocodb_data":/data/nocodb:ro \
-v "${CLIENT_DOMAIN//.}_ollama_data":/data/ollama:ro \
alpine tar czf "/backup/${CLIENT_DOMAIN}_volumes_${BACKUP_DATE}.tar.gz" -C /data .
# Backup configuration files
tar czf "${BACKUP_DIR}/${CLIENT_DOMAIN}_config_${BACKUP_DATE}.tar.gz" \
docker-compose.yml .env traefik/ supabase/ authentik/
echo "Backup completed for $CLIENT_DOMAIN"
fi
done
# Cleanup old backups (keep 30 days)
find "$BACKUP_DIR" -name "*.gz" -mtime +30 -delete
# Optional: Upload to cloud storage
# rclone sync "$BACKUP_DIR" s3:backup-bucket/multi-tenant/
Refuerzo de la seguridad para producción
Implemente un conjunto completo de buenas prácticas de seguridad:
# Enhanced Traefik security configuration
traefik:
command:
- "--api.dashboard=true"
- "--api.debug=false"
- "--log.level=WARN"
- "--accesslog=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--providers.docker.exposedbydefault=false"
labels:
# Security headers middleware
- "traefik.http.middlewares.security.headers.customRequestHeaders.X-Forwarded-Proto=https"
- "traefik.http.middlewares.security.headers.customResponseHeaders.X-Frame-Options=DENY"
- "traefik.http.middlewares.security.headers.customResponseHeaders.X-Content-Type-Options=nosniff"
- "traefik.http.middlewares.security.headers.customResponseHeaders.Strict-Transport-Security=max-age=31536000"
- "traefik.http.middlewares.security.headers.customResponseHeaders.Content-Security-Policy=default-src 'self'"
# Rate limiting middleware
- "traefik.http.middlewares.ratelimit.ratelimit.burst=100"
- "traefik.http.middlewares.ratelimit.ratelimit.average=50"
Aplique el middleware de seguridad a todos los servicios del cliente:
labels:
- "traefik.http.routers.n8n.middlewares=security,ratelimit,${AUTH_MIDDLEWARE}"
Análisis de costos: las cifras que importan
Costos tradicionales de SaaS (10 clientes empresariales con todas las funciones)
Costos mensuales por cliente:
- n8n Pro: $50/mes por cliente = $500/mes
- Supabase Pro: $25/mes por cliente = $250/mes
- Plataforma sin código (Airtable): $20/mes por cliente = $200/mes
- SSO empresarial (Auth0): $23/mes por cliente = $230/mes
- Costos de API de IA (OpenAI): $50/mes por cliente = $500/mes
- Total: $1 680/mes = $20 160/año
Costos del stack empresarial multiinquilino autoalojado
Costos anuales de infraestructura:
- Servidor en la nube con vCPU dedicadas (Hetzner CCX62): $350/mes = $4 200/año
- Costos de dominio (10 clientes): $120/año
- Cloudflare Pro (opcional): $240/año
- Total: $4 560/año
Ahorro anual: $15 600 (77 % de reducción de costos)
Estas cifras reflejan los precios vigentes cuando se publicó esta guía en agosto de 2025. A octubre de 2026, el plan B2C Essentials de Auth0 parte de $35/mes y Hetzner ya no incluye el CCX62 en su oferta; su sucesor, el CCX63, con 48 vCPU y 192 GB, figura a €853,99/mes sin IVA, incluida una dirección IPv4 primaria. Recalcule ambos lados con los precios actuales antes de decidir.
Además, obtiene:
- Soberanía y privacidad completas de los datos
- Personalización ilimitada y marca blanca
- Sin dependencia de proveedores ni límites de solicitudes de los planes SaaS
- Controles de seguridad y cumplimiento normativo que usted administra
- Capacidad de ofrecer servicios de reventa
- Control total sobre actualizaciones y funciones
Esto resulta especialmente valioso si se tiene en cuenta que nuestra configuración ofrece funciones empresariales que normalmente costarían mucho más en suscripciones SaaS, como los beneficios económicos que mostramos en nuestro análisis de autoalojamiento de n8n.
Integración con WordPress: agilizar los flujos de trabajo de contenido
Para agencias y equipos que gestionan sitios WordPress junto con sus entornos de desarrollo, este stack multiinquilino se integra muy bien con los flujos de automatización de WordPress. Al igual que nuestro plugin tva Duplicate Pro agiliza la gestión de contenido en WordPress, este entorno en contenedores puede automatizar flujos de trabajo complejos entre los sitios WordPress y su infraestructura de desarrollo.
Posibilidades de integración con WordPress:
- Sindicación de contenido: Flujos de trabajo de n8n que envían automáticamente contenido de WordPress a los sistemas del cliente
- Despliegues automatizados: Los cambios en el sitio WordPress activan despliegues en los entornos del cliente
- Sincronización de datos: Los cambios en la base de datos del cliente (vía NocoDB) actualizan automáticamente el contenido de WordPress
- Contenido con IA: Los modelos Ollama generan contenido que fluye hacia los sitios WordPress
- Informes para clientes: Informes automatizados de WordPress generados a partir de métricas del entorno de desarrollo
Esto crea un ecosistema integral en el que la gestión de contenido de WordPress, los flujos de trabajo de desarrollo y la prestación de servicios a clientes funcionan de forma coordinada.
¿Vale la pena el tiempo de configuración?
Si gestiona entornos de desarrollo para varios clientes, está creando un negocio SaaS o dirige una agencia que ofrece soluciones técnicas, sin duda. La configuración inicial lleva aproximadamente un día, pero le proporciona:
Beneficios inmediatos:
- Incorporación automatizada de clientes en menos de 10 minutos con un stack empresarial completo
- Aislamiento sólido entre entornos de clientes con marca profesional
- Ahorro masivo de costos frente a los servicios gestionados (reducción del 75 % o más)
- Control total sobre los datos, las personalizaciones y el cumplimiento normativo
- Arquitectura escalable que crece con su negocio
- Control de acceso basado en SSO con Authentik
Valor a largo plazo:
- Retención de clientes mediante una prestación de servicios superior y una presentación profesional
- Crecimiento de ingresos gracias a la capacidad de atender a más clientes de forma eficiente
- Ventaja competitiva al ofrecer funciones empresariales a precios competitivos
- Experiencia técnica que le distingue en el mercado
Para las agencias que atienden a varios clientes o las startups SaaS que quieren mantener el control mientras crecen, esta configuración ofrece capacidades de nivel empresarial sin costos de nivel empresarial. La combinación de contenedores, despliegue automatizado, enrutamiento adecuado de dominios y seguridad empresarial crea una base para un crecimiento sólido del negocio que permanece completamente bajo su control.
La configuración resulta aún más valiosa al considerar las posibilidades de integración con herramientas existentes, como la automatización de WordPress y la estabilidad comprobada de las plataformas de automatización autoalojadas.
¿Qué sigue?
Estamos trabajando activamente en mejoras para esta arquitectura multiinquilino. Los próximos tutoriales cubrirán:
Opciones de despliegue avanzadas:
- Guía de migración a Kubernetes para escalabilidad máxima y despliegue empresarial
- Gestión automatizada de certificados SSL con flujos de trabajo integrados de Let's Encrypt
- Monitoreo y alertas avanzadas con Prometheus, Grafana y paneles personalizados
- Automatización de recuperación ante desastres con estrategias de copias de seguridad en varias regiones
Mejoras en la experiencia del cliente:
- Portal de autoservicio para clientes para gestionar sus propios entornos y configuraciones
- Plantillas de personalización de marca blanca para la identidad de marca de las agencias
- Plantillas avanzadas de flujos de trabajo para procesos empresariales comunes
- Guías de integración para herramientas y API empresariales populares
Funciones empresariales:
- Refuerzo avanzado de la seguridad con WAF y detección de intrusiones
- Marcos de cumplimiento normativo para GDPR, SOC2 y otras regulaciones
- Estrategias de despliegue en varias regiones para clientes de todo el mundo
- Guías de optimización de rendimiento para entornos de alto tráfico
El futuro de la prestación de servicios a clientes consiste en construir sistemas que combinen control y comodidad, permitan crecer de forma eficiente y mantengan estándares profesionales.
Obtenga soporte profesional
Configurar un entorno multiinquilino con 16 servicios en contenedores implica coordinar muchos componentes. Aunque hemos proporcionado documentación completa, cada negocio tiene requisitos propios y condicionantes de infraestructura existente.
Si está implementando esta configuración para producción o necesita adaptarla a sus necesidades específicas de prestación de servicios a clientes, nuestro equipo puede ayudarle con:
- Estrategias de despliegue personalizadas y adaptadas a su infraestructura
- Integración con sistemas y flujos de trabajo existentes
- Optimización del rendimiento para la carga específica de sus clientes
- Refuerzo de la seguridad para cumplir los requisitos normativos
- Capacitación del personal en la gestión de entornos multiinquilino
- Estrategias de mantenimiento y monitoreo continuo
Contáctenos a través de tva.sg para hablar sobre sus necesidades de arquitectura multiinquilino y obtener orientación profesional para la implementación.
Tanto si está ampliando una agencia existente como si está lanzando una nueva plataforma SaaS o desarrollando capacidades de prestación de servicios a clientes de nivel empresarial, le ayudamos a lograrlo con soluciones autoalojadas y en contenedores que mantienen su independencia y ofrecen resultados profesionales.