Skip to main content

Production Deployment

Best practices for deploying Kaltura Avatar in production.

Security

1. API Key Protection

Never Expose API Keys

Never include your Kaltura Session in client-side code. Always use backend servers.

Backend only:

// ✅ Good - Backend (Node.js)
const KS = process.env.AVATAR_KS;

// ❌ Bad - Frontend
const KS = 'abc123'; // Exposed to users!

Environment variables:

# .env (never commit this file)
AVATAR_KS=your-secret-key
AVATAR_BASE_URL=https://api.avatar.us.kaltura.ai/v1/avatar-session

.gitignore:

.env
.env.local
.env.production

2. Use HTTPS

Always serve your application over HTTPS:

// ✅ Good
const baseUrl = 'https://api.avatar.example.com';

// ❌ Bad
const baseUrl = 'http://api.avatar.example.com';

3. Token Security

  • Tokens are session-specific
  • Don't store tokens long-term
  • Clear tokens when session ends
  • Don't share tokens between users
// ✅ Good - Clear after use
const { sessionId, token } = await createSession();
// ... use session ...
await endSession();
sessionStorage.removeItem('token');

// ❌ Bad - Storing indefinitely
localStorage.setItem('token', token);

4. Input Validation

Validate all user input:

// Backend validation
app.post('/api/avatar/say-text', async (req, res) => {
const { text } = req.body;

// Validate
if (!text || typeof text !== 'string') {
return res.status(400).json({ error: 'Invalid text' });
}

if (text.length > 1000) {
return res.status(400).json({ error: 'Text too long' });
}

// Sanitize
const cleanText = text.trim();

// Call API
// ...
});

5. Rate Limiting

Implement rate limiting to prevent abuse:

import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later',
});

app.use('/api/avatar/', limiter);

Architecture

┌─────────────┐      ┌─────────────┐      ┌──────────────┐
│ Browser │◄────►│ Backend │◄────►│ Avatar API │
│ │ │ │ │ │
│ Client SDK │ │ Express/ │ │ (Kaltura) │
│ (Display) │ │ FastAPI │ │ │
└─────────────┘ └─────────────┘ └──────────────┘

Benefits:

  • Kaltura Session stays secure
  • Server-side validation
  • Audit logging
  • Rate limiting
  • User authentication

Backend Structure

// server.js
import express from 'express';
import { avatarRoutes } from './routes/avatar.js';
import { authMiddleware } from './middleware/auth.js';
import { errorHandler } from './middleware/error.js';

const app = express();

// Middleware
app.use(express.json());
app.use(authMiddleware);

// Routes
app.use('/api/avatar', avatarRoutes);

// Error handling
app.use(errorHandler);

// Start server
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

Performance

1. Session Management

Use Redis for session storage:

import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

// Store session
await redis.setex(
`avatar:${sessionId}`,
3600, // 1 hour TTL
JSON.stringify({ token, createdAt: Date.now() })
);

// Retrieve session
const sessionData = await redis.get(`avatar:${sessionId}`);

2. Connection Pooling

Reuse HTTP connections:

import fetch from 'node-fetch';
import http from 'http';
import https from 'https';

const httpAgent = new http.Agent({ keepAlive: true });
const httpsAgent = new https.Agent({ keepAlive: true });

const response = await fetch(url, {
agent: url.startsWith('https') ? httpsAgent : httpAgent,
});

3. Caching

Cache avatar/voice IDs:

const cache = new Map();

async function getAvatarConfig(avatarId) {
if (cache.has(avatarId)) {
return cache.get(avatarId);
}

const config = await fetchAvatarConfig(avatarId);
cache.set(avatarId, config);

return config;
}

4. Load Balancing

Use load balancers for high traffic:

# nginx.conf
upstream backend {
server backend1:3001;
server backend2:3001;
server backend3:3001;
}

server {
listen 80;
location /api/ {
proxy_pass http://backend;
}
}

Monitoring

1. Logging

Log all avatar operations:

import winston from 'winston';

const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' })],
});

// Log session creation
logger.info('Session created', {
sessionId,
avatarId,
userId,
timestamp: new Date(),
});

2. Error Tracking

Use error tracking services:

import * as Sentry from '@sentry/node';

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
});

// Catch errors
app.use(Sentry.Handlers.errorHandler());

3. Metrics

Track key metrics:

import { Counter, Histogram } from 'prom-client';

const sessionCreations = new Counter({
name: 'avatar_sessions_created_total',
help: 'Total avatar sessions created',
});

const sessionDuration = new Histogram({
name: 'avatar_session_duration_seconds',
help: 'Avatar session duration',
});

// Increment
sessionCreations.inc();

// Record duration
const end = sessionDuration.startTimer();
// ... session lifetime ...
end();

Scaling

Horizontal Scaling

Run multiple backend instances:

# PM2 cluster mode
pm2 start server.js -i max

Stateless Design

Keep servers stateless:

  • Store sessions in Redis
  • Use JWT for authentication
  • Don't store state in memory

CDN

Serve static assets via CDN:

  • Frontend bundle
  • Images and assets
  • Not API endpoints

Error Handling

Graceful Degradation

try {
await session.createSession({ ... });
} catch (error) {
// Log error
logger.error('Session creation failed', { error, userId });

// Show user-friendly message
showError('Avatar unavailable. Please try again later.');

// Fallback: Show static image or text chat
enableFallbackMode();
}

Retry Logic

async function createSessionWithRetry(maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await createSession();
} catch (error) {
if (attempt === maxRetries) throw error;

// Wait with exponential backoff
await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, attempt)));
}
}
}

Health Checks

Backend Health Endpoint

app.get('/health', async (req, res) => {
try {
// Check database
await redis.ping();

// Check Avatar API
const response = await fetch(`${AVATAR_BASE_URL}/health`);

if (!response.ok) {
throw new Error('Avatar API unhealthy');
}

res.json({
status: 'healthy',
timestamp: new Date(),
uptime: process.uptime(),
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: error.message,
});
}
});

Deployment Checklist

Before Deployment

  • Kaltura Sessions stored in environment variables
  • HTTPS enabled
  • Input validation implemented
  • Rate limiting configured
  • Error handling in place
  • Logging configured
  • Health checks implemented
  • Load testing completed

After Deployment

  • Monitor error rates
  • Check performance metrics
  • Verify logs are working
  • Test from multiple locations
  • Confirm HTTPS works
  • Verify health checks
  • Monitor session creation success rate

Docker Example

# Dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --production

COPY . .

EXPOSE 3001

CMD ["node", "server.js"]
# docker-compose.yml
version: '3.8'

services:
backend:
build: .
ports:
- '3001:3001'
environment:
- AVATAR_KS=${AVATAR_KS}
- REDIS_URL=redis://redis:6379
depends_on:
- redis

redis:
image: redis:alpine
ports:
- '6379:6379'

See Also