Skip to content

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

Next Steps