Overview
This guide covers deploying Tesslate Studio to production with:
Custom domain configuration
Automatic SSL/TLS certificates
Security hardening
Performance optimization
Backup strategies
Prerequisites
Server
Cloud VM or dedicated server
16GB RAM recommended
50GB+ disk space
Ubuntu 22.04 LTS (or similar)
Domain
Custom domain (e.g., studio.yourcompany.com)
DNS access to create records
Wildcard DNS support (*.studio.yourcompany.com)
SSL Certificate
Automatic via Let’s Encrypt (recommended)
Or bring your own certificate
Email
Admin email for Let’s Encrypt notifications
Server Setup
1. Provision Server
Choose a cloud provider:
DigitalOcean
AWS EC2
Hetzner
# Create Droplet
# - Image: Ubuntu 22.04 LTS
# - Size: 4 vCPUs, 16GB RAM
# - Region: Closest to your users
# - Add SSH key
Estimated cost: $80/month# Launch Instance
# - AMI: Ubuntu Server 22.04 LTS
# - Instance Type: t3.xlarge (4 vCPUs, 16GB RAM)
# - Storage: 50GB gp3
# - Security Group: Allow ports 80, 443, 22
Estimated cost: $120/month# Create Server
# - Image: Ubuntu 22.04
# - Type: CPX31 (4 vCPUs, 8GB RAM)
# - Location: Nearest datacenter
Estimated cost: $20/month (best value)
2. Initial Server Configuration
Update System
apt update && apt upgrade -y
Install Docker
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker
Verify: docker --version
docker compose version
Create Non-Root User
adduser tesslate
usermod -aG docker tesslate
usermod -aG sudo tesslate
Switch to new user:
Set up Firewall
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
DNS Configuration
1. Create DNS Records
Add these records to your DNS provider:
Main Domain (A Record)
Wildcard (A Record)
Alternative: CNAME
Type: A
Name: studio
Value: your-server-ip
TTL: 300
2. Verify DNS Propagation
Wait 5-10 minutes, then test:
# Should return your server IP
dig studio.yourcompany.com
dig test.studio.yourcompany.com
Application Deployment
1. Clone Repository
cd /home/tesslate
git clone https://github.com/TesslateAI/Studio.git
cd Studio
cp .env.example .env
nano .env
Update these values:
# Domain Configuration
APP_DOMAIN=studio.yourcompany.com
APP_PROTOCOL=https
FRONTEND_URL=https://studio.yourcompany.com
# Generate strong keys
SECRET_KEY=<generate-with-python>
LITELLM_MASTER_KEY=sk-<generate-with-python>
# AI Provider (at least one)
OPENAI_API_KEY=sk-your-production-key
ANTHROPIC_API_KEY=sk-your-production-key
# Database
DATABASE_URL=postgresql+asyncpg://tesslate:CHANGE_THIS_PASSWORD@postgres:5432/tesslate_db
POSTGRES_PASSWORD=CHANGE_THIS_PASSWORD
# Production Settings
LOG_LEVEL=INFO
AUTO_SEED_DATABASE=false # Set to false after first run
Generate secure keys:
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
python3 -c "import secrets; print('sk-' + secrets.token_urlsafe(32))"
Update docker-compose.yml for production:
services :
traefik :
image : traefik:v3.1
command :
- "--api.dashboard=false" # Disable dashboard in production
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# Redirect HTTP to HTTPS
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
# Let's Encrypt configuration
- "--certificatesresolvers.letsencrypt.acme.email=admin@yourcompany.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports :
- "80:80"
- "443:443"
volumes :
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks :
- tesslate-network
app :
labels :
- "traefik.http.routers.app.rule=Host(`studio.yourcompany.com`)"
- "traefik.http.routers.app.tls=true"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
orchestrator :
labels :
- "traefik.http.routers.orchestrator.rule=Host(`studio.yourcompany.com`) && PathPrefix(`/api`)"
- "traefik.http.routers.orchestrator.tls=true"
- "traefik.http.routers.orchestrator.tls.certresolver=letsencrypt"
4. Deploy
# Create letsencrypt directory
mkdir -p letsencrypt
chmod 600 letsencrypt
# Start services
docker compose up -d
# Monitor logs
docker compose logs -f
5. Verify Deployment
Check Services
All services should be “Up”.
Test HTTPS
Open browser to: https://studio.yourcompany.com
Should show:
Valid SSL certificate
Green padlock in browser
No certificate warnings
Test API
curl https://studio.yourcompany.com/api/health
Should return:
Create First User
Sign up at https://studio.yourcompany.com First user is automatically admin.
Database Backup
1. Automated Backup Script
Create /home/tesslate/backup.sh:
#!/bin/bash
BACKUP_DIR = "/home/tesslate/backups"
DATE = $( date +%Y%m%d_%H%M%S )
BACKUP_FILE = " $BACKUP_DIR /tesslate_ $DATE .sql.gz"
# Create backup directory
mkdir -p $BACKUP_DIR
# Dump database
docker compose exec -T postgres pg_dump -U tesslate tesslate_db | gzip > $BACKUP_FILE
# Keep only last 7 days
find $BACKUP_DIR -type f -mtime +7 -delete
echo "Backup completed: $BACKUP_FILE "
Make executable:
chmod +x /home/tesslate/backup.sh
2. Schedule Backups
Add to crontab:
Add this line (daily at 2 AM):
0 2 * * * /home/tesslate/backup.sh
3. Test Restore
# Stop services
cd /home/tesslate/Studio
docker compose down
# Restore database
gunzip < /home/tesslate/backups/tesslate_YYYYMMDD_HHMMSS.sql.gz | \
docker compose exec -T postgres psql -U tesslate tesslate_db
# Restart services
docker compose up -d
Monitoring
1. Docker Health Checks
Check container health:
docker compose ps
docker stats
2. Log Management
View logs:
# All services
docker compose logs -f
# Specific service
docker compose logs -f orchestrator
# Last 100 lines
docker compose logs --tail=100 orchestrator
Rotate logs in docker-compose.yml:
services :
orchestrator :
logging :
driver : "json-file"
options :
max-size : "10m"
max-file : "3"
3. Disk Space Monitoring
Create /home/tesslate/monitor.sh:
#!/bin/bash
USAGE = $( df -h / | awk 'NR==2 {print $5}' | sed 's/%//' )
if [ $USAGE -gt 80 ]; then
echo "WARNING: Disk usage is at ${ USAGE }%"
# Clean Docker
docker system prune -f
fi
Add to crontab (hourly):
0 * * * * /home/tesslate/monitor.sh
Security Hardening
Disable Root SSH
Edit /etc/ssh/sshd_config: PermitRootLogin no
PasswordAuthentication no
Restart SSH: sudo systemctl restart ssh
Configure Fail2Ban
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Enable Unattended Upgrades
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Secure PostgreSQL
Change default password in .env: POSTGRES_PASSWORD=very-strong-password-here
Update DATABASE_URL accordingly.
Rate Limiting
Add to Traefik configuration: - "--entrypoints.websecure.http.middlewares=rate-limit"
- "--http.middlewares.rate-limit.rateLimit.average=100"
- "--http.middlewares.rate-limit.rateLimit.burst=50"
1. Enable HTTP/2
Already enabled with Traefik HTTPS.
2. Database Optimization
Add to docker-compose.yml:
services :
postgres :
command :
- "postgres"
- "-c"
- "shared_buffers=256MB"
- "-c"
- "effective_cache_size=1GB"
- "-c"
- "maintenance_work_mem=128MB"
- "-c"
- "checkpoint_completion_target=0.9"
- "-c"
- "wal_buffers=16MB"
- "-c"
- "default_statistics_target=100"
3. Docker Resource Limits
Add to docker-compose.yml:
services :
orchestrator :
deploy :
resources :
limits :
cpus : '2.0'
memory : 4G
reservations :
cpus : '1.0'
memory : 2G
app :
deploy :
resources :
limits :
cpus : '1.0'
memory : 2G
Maintenance
Daily Tasks
# Check logs for errors
docker compose logs --tail=100 | grep -i error
# Check disk space
df -h
# Check Docker stats
docker stats --no-stream
Weekly Tasks
# Update system packages
sudo apt update && sudo apt upgrade -y
# Clean Docker resources
docker system prune -f
# Review backups
ls -lh /home/tesslate/backups/
Monthly Tasks
# Pull latest updates
cd /home/tesslate/Studio
git pull origin main
# Rebuild images
docker compose build --no-cache
# Restart services
docker compose down && docker compose up -d
# Test application
curl https://studio.yourcompany.com/api/health
Kubernetes Deployment (Production)
For high availability and auto-scaling, deploy Tesslate Studio on Kubernetes.
Prerequisites
Kubernetes Cluster
Minikube (local testing)
DigitalOcean Kubernetes
AWS EKS
Google GKE
Required Components
kubectl configured
NGINX Ingress Controller
cert-manager (for SSL)
Container registry access
Quick Deploy
Configure Environment
cd k8s
cp .env.example .env
# Edit .env with your credentials
Key variables: K8S_DEVSERVER_IMAGE=your-registry/tesslate-devserver:latest
K8S_IMAGE_PULL_SECRET=your-registry-secret
S3_ENDPOINT_URL=https://s3.amazonaws.com
S3_BUCKET_NAME=tesslate-projects
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
Deploy All Components
./scripts/deployment/deploy-all.sh
This deploys:
Backend (FastAPI orchestrator)
Frontend (React app)
PostgreSQL database
NGINX Ingress rules
SSL certificates via cert-manager
Verify Deployment
kubectl get pods -n tesslate
kubectl get ingress -n tesslate
All pods should be “Running”.
Kubernetes Architecture
┌─────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
├─────────────────────────────────────────────────────┤
│ Namespace: tesslate │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Backend │ │ Frontend │ │
│ │ (Deployment)│ │ (Deployment) │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ └──────┬───────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Ingress │ ← NGINX Controller │
│ │ (SSL/TLS) │ │
│ └──────────────┘ │
├─────────────────────────────────────────────────────┤
│ User Project Namespaces (Dynamic) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ proj-abc123 │ │ proj-xyz789 │ │
│ │ (User Pod) │ │ (User Pod) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘
S3 Sandwich Pattern
Kubernetes deployments use the S3 Sandwich pattern for project storage:
Hydration (Pod Start)
Init container downloads project files from S3 to local PVC.
Runtime
Fast local I/O on ephemeral block storage for file edits and npm install.
Dehydration (Pod Stop)
PreStop hook uploads changed files back to S3 before pod termination.
This pattern provides fast local storage performance while ensuring data persistence across pod restarts.
Management Commands
# View all resources
./scripts/manage-k8s.sh status
# View logs
./scripts/manage-k8s.sh logs backend
./scripts/manage-k8s.sh logs frontend
# Restart a service
./scripts/manage-k8s.sh restart backend
# Scale replicas
./scripts/manage-k8s.sh scale backend 3
# Backup database
./scripts/manage-k8s.sh backup
# Update deployment
./scripts/manage-k8s.sh update
Provider-Specific Setup
Minikube (Local)
DigitalOcean
AWS EKS
# Start cluster
minikube start -p tesslate --driver=docker --memory=4096 --cpus=2
minikube -p tesslate addons enable ingress
# Build and load images
docker build -t tesslate-backend:latest -f orchestrator/Dockerfile orchestrator/
minikube -p tesslate image load tesslate-backend:latest
# Deploy
kubectl apply -k k8s/overlays/minikube
# Start tunnel (separate terminal)
minikube -p tesslate tunnel
# Install doctl and authenticate
doctl auth init
# Connect to cluster
doctl kubernetes cluster kubeconfig save your-cluster-name
# Deploy
kubectl apply -k k8s/overlays/production
Configure DNS in Cloudflare:
A record: studio → Load balancer IP
A record: *.studio → Load balancer IP
# Configure kubectl
aws eks update-kubeconfig --region us-east-1 --name tesslate-cluster
# Login to ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
123456789.dkr.ecr.us-east-1.amazonaws.com
# Deploy
kubectl apply -k k8s/overlays/aws
Troubleshooting
SSL Certificate Not Working
Check Let’s Encrypt logs: docker compose logs traefik | grep -i acme
Common issues:
DNS not propagated (wait 10-15 minutes)
Port 80 not accessible (check firewall)
Invalid email in acme configuration
Rate limit hit (5 certs per week per domain)
Force renewal: rm -rf letsencrypt/acme.json
docker compose restart traefik
Check what’s using memory: Solutions:
Add swap space
Increase server RAM
Add resource limits in docker-compose.yml
Clean up old containers:
Database Connection Errors
Check PostgreSQL is running: docker compose ps postgres
Check logs: docker compose logs postgres
Restart database: docker compose restart postgres
Next Steps