Infrastructure¶
Cloud Resources, Docker, and Terraform
Production Environment¶
Oracle Cloud Infrastructure (OCI)¶
VitaraVox production runs on Oracle Cloud in Toronto region.
| Resource | Specification | Cost |
|---|---|---|
| Compute | VM.Standard.A1.Flex | Free tier |
| vCPU | 4 | - |
| Memory | 24 GB | - |
| Storage | 200 GB | - |
| Network | Public IP | - |
AWS (OSCAR Development)¶
OSCAR EMR development instance for testing.
| Resource | Specification | Cost |
|---|---|---|
| Instance | t3a.medium | ~$30/month |
| vCPU | 2 | - |
| Memory | 4 GB | - |
| Storage | 30 GB gp3 | - |
| Region | ca-central-1 (Montreal) | - |
Note: Production clinics will connect their own OSCAR instances.
Docker Configuration¶
docker-compose.yml¶
Location: /opt/vitara-platform/docker-compose.yml
version: '3.8'
services:
vitara-db:
image: postgres:16-alpine
container_name: vitara-db
environment:
POSTGRES_USER: vitara
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: vitara
volumes:
- vitara-db-data:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- vitara-network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U vitara"]
interval: 10s
timeout: 5s
retries: 5
vitara-voice-agent:
build:
context: ./voice-agent
dockerfile: Dockerfile
container_name: vitara-voice-agent
environment:
NODE_ENV: ${NODE_ENV:-production}
PORT: 3001
DATABASE_URL: postgresql://vitara:${POSTGRES_PASSWORD}@vitara-db:5432/vitara
OSCAR_API_URL: ${OSCAR_API_URL}
OSCAR_API_KEY: ${OSCAR_API_KEY}
VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET}
TRANSFER_NUMBER: ${TRANSFER_NUMBER}
depends_on:
vitara-db:
condition: service_healthy
networks:
- vitara-network
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3001/health"]
interval: 30s
timeout: 10s
retries: 3
vitara-nginx:
image: nginx:alpine
container_name: vitara-nginx
ports:
- "9080:80"
- "9443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
depends_on:
- vitara-voice-agent
networks:
- vitara-network
restart: unless-stopped
volumes:
vitara-db-data:
networks:
vitara-network:
driver: bridge
Voice Agent Dockerfile¶
Location: /opt/vitara-platform/voice-agent/Dockerfile
FROM node:18-alpine
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install production dependencies only
RUN npm ci --only=production
# Copy application code
COPY --chown=nodejs:nodejs . .
# Switch to non-root user
USER nodejs
# Expose port
EXPOSE 3001
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget --spider -q http://localhost:3001/health || exit 1
# Start application
CMD ["node", "server.js"]
NGINX Configuration¶
nginx.conf¶
Location: /opt/vitara-platform/nginx/nginx.conf
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format json escape=json '{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"body_bytes":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_time":"$upstream_response_time"'
'}';
access_log /var/log/nginx/access.log json;
# Performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Security
server_tokens off;
# Gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain application/json application/javascript;
# Rate limiting zones
limit_req_zone $binary_remote_addr zone=api_general:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=api_booking:10m rate=20r/m;
# Upstream
upstream voice_agent {
server vitara-voice-agent:3001;
keepalive 32;
}
include /etc/nginx/conf.d/*.conf;
}
API Route Configuration¶
Location: /opt/vitara-platform/nginx/conf.d/api.conf
# HTTPS Server
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name api.vitaravox.ca api-dev.vitaravox.ca localhost;
# SSL
ssl_certificate /etc/letsencrypt/live/vitaravox.ca/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vitaravox.ca/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Request size limit
client_max_body_size 1M;
# Health check (no rate limit)
location /health {
proxy_pass http://voice_agent/health;
proxy_http_version 1.1;
proxy_set_header Connection "";
access_log off;
}
# Main API endpoints
location /api/vapi/ {
limit_req zone=api_general burst=20 nodelay;
proxy_pass http://voice_agent/api/vapi/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# Booking endpoint (stricter rate limit)
location /api/vapi/create-appointment {
limit_req zone=api_booking burst=5 nodelay;
proxy_pass http://voice_agent/api/vapi/create-appointment;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
# Admin API endpoints (v1.5.0+)
location /api/ {
limit_req zone=api_general burst=20 nodelay;
proxy_pass http://voice_agent/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Authorization $http_authorization;
proxy_set_header X-Request-ID $request_id;
}
# Admin UI static files
location /admin {
proxy_pass http://voice_agent/admin;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP redirect
server {
listen 80;
listen [::]:80;
server_name api.vitaravox.ca api-dev.vitaravox.ca;
location /health {
proxy_pass http://voice_agent/health;
access_log off;
}
location / {
return 301 https://$host$request_uri;
}
}
Terraform Configuration¶
AWS OSCAR Instance¶
Location: /home/ubuntu/vitara-platform/terraform/oscar-ec2.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ca-central-1"
default_tags {
tags = {
Project = "VitaraPlatform"
Environment = "development"
ManagedBy = "terraform"
}
}
}
# Security Group
resource "aws_security_group" "oscar_sg" {
name_prefix = "oscar-emr-"
description = "Security group for OSCAR EMR"
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.admin_ip]
}
ingress {
description = "HTTP"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# EC2 Instance
resource "aws_instance" "oscar" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3a.medium"
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.oscar_sg.id]
root_block_device {
volume_size = 30
volume_type = "gp3"
}
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y docker.io docker-compose-v2
systemctl enable docker
systemctl start docker
# Clone OSCAR
git clone https://github.com/open-osp/open-osp.git /opt/oscar
cd /opt/oscar
./openosp setup
./openosp bootstrap
./openosp start
EOF
tags = {
Name = "oscar-emr-dev"
}
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-*-24.04-amd64-server-*"]
}
}
output "oscar_public_ip" {
value = aws_instance.oscar.public_ip
}
SSL Configuration¶
Let's Encrypt Setup Script¶
Location: /opt/vitara-platform/scripts/ssl-setup.sh
#!/bin/bash
set -e
DOMAIN="vitaravox.ca"
EMAIL="admin@vitaravox.com"
# Install certbot
apt-get update
apt-get install -y certbot
# Stop NGINX temporarily
docker stop vitara-nginx || true
# Get wildcard certificate
certbot certonly \
--standalone \
-d "$DOMAIN" \
-d "*.$DOMAIN" \
--email "$EMAIL" \
--agree-tos \
--non-interactive \
--preferred-challenges dns
# Restart NGINX
docker start vitara-nginx
# Setup auto-renewal
cat > /etc/cron.d/certbot-renewal << 'EOF'
0 3 * * * root certbot renew --quiet --post-hook "docker restart vitara-nginx"
EOF
echo "SSL certificate installed for $DOMAIN"
Deployment Script¶
Location: /opt/vitara-platform/scripts/deploy.sh
#!/bin/bash
set -e
echo "=== VitaraPlatform Deployment ==="
echo "Started at: $(date)"
cd /opt/vitara-platform
# Pull latest changes
echo "Pulling latest code..."
git pull origin main
# Copy admin dashboard from source
echo "Updating admin dashboard..."
cp -r /home/ubuntu/vitara-platform/admin-dashboard/* ./admin-dashboard/
# Verify required files
echo "Verifying files..."
for file in docker-compose.yml .env nginx/nginx.conf; do
if [ ! -f "$file" ]; then
echo "ERROR: Missing $file"
exit 1
fi
done
# Test NGINX config
echo "Testing NGINX configuration..."
docker run --rm -v $(pwd)/nginx:/etc/nginx:ro nginx:alpine nginx -t
# Stop existing containers
echo "Stopping containers..."
docker compose down
# Build images
echo "Building images..."
docker compose build --no-cache
# Start services
echo "Starting services..."
docker compose up -d
# Wait for startup
echo "Waiting for services..."
sleep 15
# Verify health
echo "Checking health..."
for service in vitara-db vitara-voice-agent vitara-nginx; do
status=$(docker inspect --format='{{.State.Health.Status}}' $service 2>/dev/null || echo "no-health")
echo " $service: $status"
done
# Test health endpoint
echo "Testing API..."
curl -s -o /dev/null -w "%{http_code}" http://localhost:9080/health
echo ""
echo "=== Deployment Complete ==="
echo "API: https://api.vitaravox.ca:9443"
echo "Docs: https://vitdocs.vitaravox.ca"
Environment Variables¶
Required Variables¶
Location: /opt/vitara-platform/.env
# Database
POSTGRES_PASSWORD=<secure-password>
# Environment
NODE_ENV=production
# OSCAR Integration
OSCAR_API_URL=http://15.222.50.48:3000
OSCAR_API_KEY=<api-key>
# Vapi Integration
VAPI_WEBHOOK_SECRET=<webhook-secret>
# Clinic
TRANSFER_NUMBER=+1-604-555-0100
# Admin Dashboard
ADMIN_JWT_SECRET=<jwt-secret>
ENCRYPTION_KEY=<encryption-key>
Monitoring¶
Health Check Endpoints¶
| Endpoint | Purpose | Frequency |
|---|---|---|
/health |
Middleware health | 30s |
pg_isready |
Database health | 10s |
| NGINX status | Proxy health | 30s |
Container Health Checks¶
# Check all containers
docker compose ps
# Check specific logs
docker logs vitara-voice-agent --tail 100
# Check database
docker exec vitara-db pg_isready -U vitara