Analyze current differences between development and production environments
and provide three options for improving parity:
1. Conservative: Minimal changes, maximum benefit
- 2 replicas for critical services
- Resource limits at 50% of prod
- Specific CORS origins
- Resource impact: +30% RAM
2. High Parity: Maximum similarity
- Match all prod replica counts
- Production resource limits
- Enable SSL and monitoring
- Resource impact: +200% RAM
3. Hybrid: Balanced approach
- 2 replicas for stateful services
- Resources at 60% of prod
- Production configs with dev features
- Resource impact: +100% RAM
Recommendation: Start with Option 1 for best cost/benefit ratio.
This commit adds complete documentation and tooling for migrating from
local development (Kind/Colima on macOS) to production deployment
(MicroK8s on Ubuntu VPS at Clouding.io).
Documentation added:
- K8S-MIGRATION-GUIDE.md: Comprehensive step-by-step migration guide
covering all phases from VPS setup to post-deployment operations
- MIGRATION-CHECKLIST.md: Quick reference checklist for migration tasks
- MIGRATION-SUMMARY.md: High-level overview and key changes summary
Configuration updates:
- Added storage-patch.yaml for MicroK8s storage class compatibility
(changes from 'standard' to 'microk8s-hostpath')
- Updated prod/kustomization.yaml to include storage patch
Helper scripts:
- deploy-production.sh: Interactive deployment script with validation
- tag-and-push-images.sh: Automated image tagging and registry push
- backup-databases.sh: Database backup script for production
Key differences addressed:
- Ingress: MicroK8s addon vs custom NGINX
- Storage: MicroK8s hostpath vs Kind standard storage
- Registry: Container registry configuration for production
- SSL: Let's Encrypt production certificates
- Domains: Real domain configuration vs localhost
- Resources: Production-grade resource limits and scaling
The migration guide covers:
- VPS setup and MicroK8s installation
- Configuration adaptations required
- Container registry setup options
- SSL certificate configuration
- Monitoring and backup setup
- Troubleshooting common issues
- Security hardening checklist
- Rollback procedures
All existing Kubernetes manifests remain unchanged and compatible.
Clear auth store when exiting demo session to prevent unauthorized page redirect.
## Problem
When users clicked "Salir" (Exit) from the demo session, they were redirected to the unauthorized page (`/unauthorized`) instead of the demo landing page (`/demo`).
## Root Cause
The `handleExpiration()` function in DemoBanner.tsx was clearing localStorage and navigating to `/demo`, but was NOT clearing the auth store. This created an inconsistent state:
- `isDemoMode = false` (localStorage cleared)
- `demoSessionId = null` (localStorage cleared)
- `isAuthenticated = true` (auth store NOT cleared - still has demo user)
The `useHasAccess()` hook checks:
```typescript
return isAuthenticated || (isDemoMode && !!demoSessionId);
```
After clearing localStorage but not auth:
- `isAuthenticated = true` but the demo session is invalid
- `isDemoMode = false` and `demoSessionId = null`
- Result: `useHasAccess()` returns `false`
When navigating to `/demo`, the ProtectedRoute checked access and found it was `false`, redirecting to `/unauthorized`.
## Solution
Call `logout()` on the auth store before navigating to clear the demo user session completely. This ensures:
- Auth store is cleared (`isAuthenticated = false`)
- User is properly logged out from demo session
- Navigation to `/demo` succeeds without authentication check
## Additional Improvements
- Also clear `virtual_tenant_id` and `subscription_tier` from localStorage
- Updated comment to clarify navigation intent
## Files Changed
- frontend/src/components/layout/DemoBanner/DemoBanner.tsx:73-74
- Added auth store logout before navigation
- Added clearing of virtual_tenant_id and subscription_tier
- Updated comment for clarity
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Prevent loadUserTenants from being called too early for demo sessions by excluding demo mode from the authenticated user auto-load logic.
## Problem
In enterprise demo sessions, loadUserTenants() was being called BEFORE child tenants were created, resulting in only the parent tenant (1 tenant) being returned instead of all 6 tenants (parent + 5 children).
Timeline of the race condition:
- 15:37:20: Session created, parent tenant cloning started
- 15:37:21: loadUserTenants() called by first useEffect → returned 1 tenant ❌
- 15:37:22: Child tenants created (5 tenants)
- 15:38:03: Session status changed to 'ready'
The first useEffect (for authenticated users) triggered immediately when isAuthenticated became true, calling loadUserTenants() at 15:37:21. This happened BEFORE the session polling logic in the second useEffect could wait for status='ready' and BEFORE child tenants were created.
## Solution
Added `!isDemoMode` condition to the first useEffect that auto-loads tenants for authenticated users. This ensures demo sessions use only the special initialization logic with session status polling (second useEffect), which waits for the session to be fully ready before loading tenants.
## Files Changed
- frontend/src/stores/useTenantInitializer.ts:61
- Added `&& !isDemoMode` to prevent early tenant loading for demo users
- Added dependency `isDemoMode` to useEffect dependency array
- Updated comment to clarify demo users have separate initialization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The gateway middleware was not including demo_account_type in the
request.state.user context, causing the tenant API to filter with
an empty account type.
## The Bug:
Gateway middleware set:
- demo_session_id ✅
- is_demo ✅
- demo_account_type ❌ MISSING!
This caused get_virtual_tenants_for_session() to be called with
demo_account_type="" (empty string), which returned only the parent
tenant instead of parent + 5 children.
## Log Evidence:
Before fix:
Demo session detected for get_user_tenants
demo_account_type= ← EMPTY!
tenant_count=1 ← Only parent!
After fix (expected):
Demo session detected for get_user_tenants
demo_account_type=enterprise
tenant_count=6 ← Parent + 5 children!
## Fix:
Added line 211 in gateway/app/middleware/demo_middleware.py:
"demo_account_type": session_info.get("demo_account_type", "professional")
This ensures the tenant service knows whether it's an enterprise or
professional demo session and returns the correct tenant list.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>