diff --git a/api/Dockerfile b/api/Dockerfile
index 1674a56..a417a10 100644
--- a/api/Dockerfile
+++ b/api/Dockerfile
@@ -20,7 +20,7 @@ RUN pip install --user --no-cache-dir -r requirements.txt && \
# Copy and install pipecat from local submodule
COPY pipecat /tmp/pipecat
-RUN pip install --user --no-cache-dir '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,soundfile,silero,webrtc]' && \
+RUN pip install --user --no-cache-dir '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,soundfile,silero,webrtc,local-smart-turn-v3]' && \
# Clean up pip cache and temporary pipecat directory
rm -rf /root/.cache/pip /tmp/pipecat
diff --git a/api/routes/webrtc_signaling.py b/api/routes/webrtc_signaling.py
index 8bc294d..8d940d8 100644
--- a/api/routes/webrtc_signaling.py
+++ b/api/routes/webrtc_signaling.py
@@ -10,9 +10,11 @@ Uses the SmallWebRTC API contract:
"""
import asyncio
+import os
from datetime import UTC, datetime
-from typing import Dict
+from typing import Dict, List
+from aiortc import RTCIceServer
from aiortc.sdp import candidate_from_sdp
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
from loguru import logger
@@ -28,8 +30,34 @@ from pipecat.utils.context import set_current_run_id
router = APIRouter(prefix="/ws")
+
+def get_ice_servers() -> List[RTCIceServer]:
+ """Build ICE servers configuration including TURN if configured."""
+ servers: List[RTCIceServer] = [RTCIceServer(urls="stun:stun.l.google.com:19302")]
+
+ # Add TURN server if configured
+ turn_host = os.getenv("TURN_HOST")
+ turn_username = os.getenv("TURN_USERNAME")
+ turn_password = os.getenv("TURN_PASSWORD")
+
+ if turn_host and turn_username and turn_password:
+ servers.append(
+ RTCIceServer(
+ urls=[
+ f"turn:{turn_host}:3478",
+ f"turn:{turn_host}:3478?transport=tcp",
+ ],
+ username=turn_username,
+ credential=turn_password,
+ )
+ )
+ logger.info(f"TURN server configured: {turn_host}:3478")
+
+ return servers
+
+
# ICE servers configuration
-ice_servers = ["stun:stun.l.google.com:19302"]
+ice_servers = get_ice_servers()
class SignalingManager:
diff --git a/docker-compose.yaml b/docker-compose.yaml
index ca1e2c9..292bb67 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -109,6 +109,11 @@ services:
ENABLE_TELEMETRY: "${ENABLE_TELEMETRY:-true}"
SENTRY_DSN: "https://3acdb63d5f1f70430953353b82de61e0@o4509486225096704.ingest.us.sentry.io/4510152922693632"
+ # TURN server configuration (for WebRTC NAT traversal in remote server)
+ TURN_HOST: "${TURN_HOST:-}"
+ TURN_USERNAME: "${TURN_USERNAME:-}"
+ TURN_PASSWORD: "${TURN_PASSWORD:-}"
+
ports:
- "8000:8000"
depends_on:
@@ -149,6 +154,11 @@ services:
# Sentry
SENTRY_DSN: "https://d9387fed5f80e90781f1dbd9b2c0994c@o4509486225096704.ingest.us.sentry.io/4510124708200448"
+
+ # TURN server configuration (for WebRTC NAT traversal in remote server)
+ NEXT_PUBLIC_TURN_HOST: "${TURN_HOST:-}"
+ NEXT_PUBLIC_TURN_USERNAME: "${TURN_USERNAME:-}"
+ NEXT_PUBLIC_TURN_PASSWORD: "${TURN_PASSWORD:-}"
ports:
- "3010:3010"
depends_on:
@@ -176,6 +186,32 @@ services:
networks:
- app-network
+ coturn:
+ image: coturn/coturn:4.6.3
+ container_name: coturn
+ restart: unless-stopped
+ profiles: ["remote"]
+ network_mode: host
+ environment:
+ TURN_USERNAME: ${TURN_USERNAME}
+ TURN_PASSWORD: ${TURN_PASSWORD}
+ command: >
+ -n
+ --listening-port=3478
+ --tls-listening-port=5349
+ --min-port=49152
+ --max-port=49200
+ --realm=${TURN_REALM:-dograh.com}
+ --user=${TURN_USERNAME}:${TURN_PASSWORD}
+ --lt-cred-mech
+ --fingerprint
+ --no-cli
+ --log-file=stdout
+ --no-multicast-peers
+ --no-tlsv1
+ --no-tlsv1_1
+ ${TURN_HOST:+--external-ip=$TURN_HOST}
+
volumes:
postgres_data:
redis_data:
diff --git a/docs/deployment/docker.mdx b/docs/deployment/docker.mdx
index 9a13695..a849eb8 100644
--- a/docs/deployment/docker.mdx
+++ b/docs/deployment/docker.mdx
@@ -38,150 +38,55 @@ You can disable telemetry by setting `ENABLE_TELEMETRY=false` in the command abo
## Option 2: Remote Server Deployment
-Deploy Dograh AI on a remote server to make it accessible from anywhere using your server's IP address. This setup includes HTTPS support via nginx reverse proxy with self-signed certificates.
+Deploy Dograh AI on a remote server to make it accessible from anywhere using your server's IP address. This setup includes HTTPS support via nginx reverse proxy with self-signed certificates. We need to serve the application over HTTPS, since modern browsers only allow microphone permissions for websites being served over HTTPS.
+
+**We highly recommend you set up the platform on a fresh server, so that there are less chances of confliciting dependencies, and ports from other applications.**
### Prerequisites
-- A server with Docker and Docker Compose installed
+- A server with Docker and Docker Compose installed. It should have minimum of 8 GB RAM and 2 vCPUs.
- Public IP address for your server
-- Ports 80 and 443 accessible from the internet
+- TCP Ports 80, 443, 3478, 5349 and UDP Ports 3478, 5349 and 49152:49200 reachable from Internet (Port 80 and 443 to access the UI and rest of the ports for WebRTC Signaling)
-### Step 1: Create Project Directory
+Please refer to your server hosting provider's documentaion on how you can open these ports in the firewall of the server.
-Open your terminal and create a directory for Dograh:
+### Quick Setup
+
+Run the automated setup script that will configure everything for you:
+
+```bash
+curl -o setup_remote.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_remote.sh && chmod +x setup_remote.sh && ./setup_remote.sh
+```
+
+The script will prompt you for:
+- Your server's public IP address
+- A password for the TURN server (optional, press Enter for default)
+
+It will automatically:
+- Download the docker-compose.yaml file
+- Create and configure nginx.conf with your IP address
+- Generate SSL certificates
+- Create an environment file with TURN server configuration
+
+### Start the Application
+
+After the setup script completes, start Dograh:
```bash
-mkdir dograh
cd dograh
-```
-
-### Step 2: Download Docker Compose File
-
-Download the docker-compose.yaml file:
-
-```bash
-curl -o docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml
-```
-
-### Step 3: Generate SSL Certificates
-
-Create a script to generate self-signed certificates for HTTPS:
-
-```bash
-nano generate_certificate.sh
-```
-
-Copy the following content into the file:
-
-```bash
-mkdir -p certs
-openssl req -x509 -nodes -newkey rsa:2048 \
- -keyout certs/local.key \
- -out certs/local.crt \
- -days 365 \
- -subj "/CN=YOUR_SERVER_IP"
-```
-
-
-Replace `YOUR_SERVER_IP` with your server's public IP address.
-
-
-Make the script executable and run it:
-
-```bash
-chmod +x generate_certificate.sh
-./generate_certificate.sh
-```
-
-### Step 4: Configure nginx
-
-Create an nginx configuration file:
-
-```bash
-nano nginx.conf
-```
-
-Copy the following content into the file:
-
-```nginx
-server {
- listen 80;
- server_name YOUR_SERVER_IP;
-
- # Optional: redirect all HTTP to HTTPS
- return 301 https://$host$request_uri;
-}
-
-server {
- listen 443 ssl;
- server_name YOUR_SERVER_IP;
-
- ssl_certificate /etc/nginx/certs/local.crt;
- ssl_certificate_key /etc/nginx/certs/local.key;
-
- # Basic TLS settings for dev
- ssl_protocols TLSv1.2 TLSv1.3;
- ssl_prefer_server_ciphers on;
-
- location / {
- proxy_pass http://ui:3010;
- proxy_http_version 1.1;
-
- # Important for WebSockets / hot reload etc.
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
-
- 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 https;
-
- # Rewrite localhost MinIO URLs in API responses to use current domain
- sub_filter 'http://localhost:9000/voice-audio/' 'https://$host/voice-audio/';
- sub_filter_once off;
- sub_filter_types application/json text/html;
- }
-
- location /voice-audio/ {
- proxy_pass http://minio:9000/voice-audio/;
-
- proxy_http_version 1.1;
-
- # Headers for file downloads from MinIO
- 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 https;
-
- # Allow large file downloads
- proxy_buffering off;
- client_max_body_size 100M;
- }
-}
-```
-
-
-Replace `YOUR_SERVER_IP` with your server's public IP address in both locations (lines 3 and 11).
-
-
-### Step 5: Deploy with Remote Profile
-
-Start the application with the remote profile to include nginx:
-
-```bash
docker compose --profile remote up -d
```
-This command starts all services including the nginx reverse proxy with HTTPS support.
-
-### Step 6: Access Your Application
+### Access Your Application
Your application will be available at:
```
https://YOUR_SERVER_IP
```
-Replace `YOUR_SERVER_IP` with your actual server IP address.
+
+Since we are using a self-signed certificate, your browser will show a security warning. You can safely accept it to proceed.
+
### Configuration Notes
@@ -189,4 +94,18 @@ Replace `YOUR_SERVER_IP` with your actual server IP address.
- File downloads (transcripts, recordings) are automatically routed through nginx
- WebSocket connections for real-time features are properly proxied
- The setup uses self-signed certificates - browsers will show a security warning that you can safely accept for testing
+- The TURN server (coturn) is configured for WebRTC NAT traversal
- For production deployments with proper SSL and domain names, see the [Custom Domain](custom-domain) documentation
+
+### Files Created
+
+The setup script creates the following files in the `dograh/` directory:
+
+| File | Purpose |
+|------|---------|
+| `docker-compose.yaml` | Main Docker Compose configuration |
+| `nginx.conf` | nginx reverse proxy configuration with your IP |
+| `generate_certificate.sh` | Script to regenerate SSL certificates |
+| `certs/local.crt` | Self-signed SSL certificate |
+| `certs/local.key` | SSL private key |
+| `.env` | Environment variables for TURN server |
diff --git a/docs/deployment/index.mdx b/docs/deployment/index.mdx
new file mode 100644
index 0000000..2a90a4b
--- /dev/null
+++ b/docs/deployment/index.mdx
@@ -0,0 +1,12 @@
+---
+title: "Deployments"
+description: "Options to deploy Dograh Voice Agent Platform on your own infrastructure."
+---
+
+### Deployment Options
+You can deploy Dograh Platform using Docker on a remote server using Docker, either using terminal commands, or using PaaS like Heroku, Coolify.
+
+- [Docker](deployment/docker)
+- [Custom Domain](deployment/custom-domain)
+- [Heroku](deployment/heroku)
+- [Coolify](deployment/coolify)
diff --git a/docs/docs.json b/docs/docs.json
index 2d99170..53ee7b7 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -46,17 +46,6 @@
"features/campaigns",
"features/looptalk"
]
- }
- ]
- },
- {
- "tab": "Integrations",
- "groups": [
- {
- "group": "Overview",
- "pages": [
- "integrations/overview"
- ]
},
{
"group": "Telephony",
diff --git a/docs/getting-started/index.mdx b/docs/getting-started/index.mdx
index 9063ca5..4fe6d1d 100644
--- a/docs/getting-started/index.mdx
+++ b/docs/getting-started/index.mdx
@@ -1,11 +1,11 @@
---
title: "Introduction"
-description: "The open-source alternative to Vapi - build voice AI agents with full control and transparency"
+description: "Open-source alternative to Vapi - build voice AI agents with full control and transparency"
---
## About Dograh
-**The open-source alternative to Vapi** - Dograh helps you build voice AI agents with an easy drag-and-drop workflow builder. Unlike proprietary solutions like Vapi, Dograh gives you:
+**Dograh is an open-source alternative to Vapi** - Dograh helps you build voice AI agents with an easy drag-and-drop workflow builder. Unlike proprietary solutions like Vapi, Dograh gives you:
- **100% open source** - no vendor lock-in, full transparency
- **Self-hostable** - deploy anywhere, own your infrastructure
@@ -14,7 +14,7 @@ description: "The open-source alternative to Vapi - build voice AI agents with f
## Setting up
-Get the stack up and running using Docker with a single command.
+Get the platform up and running using Docker with a single command on your local computer. If you are looking to deploy the platform on a server, please check the [Deployment](deployment) section.
We collect anonymous usage data to improve the product. You can opt out by setting the `ENABLE_TELEMETRY` to `false` in the below command.
diff --git a/scripts/setup_remote.sh b/scripts/setup_remote.sh
new file mode 100755
index 0000000..002b670
--- /dev/null
+++ b/scripts/setup_remote.sh
@@ -0,0 +1,171 @@
+#!/bin/bash
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+echo -e "${BLUE}"
+echo "╔══════════════════════════════════════════════════════════════╗"
+echo "║ Dograh Remote Setup ║"
+echo "║ Automated HTTPS deployment with TURN server ║"
+echo "╚══════════════════════════════════════════════════════════════╝"
+echo -e "${NC}"
+
+# Get the public IP address
+echo -e "${YELLOW}Enter your server's public IP address:${NC}"
+read -p "> " SERVER_IP
+
+if [[ -z "$SERVER_IP" ]]; then
+ echo -e "${RED}Error: IP address cannot be empty${NC}"
+ exit 1
+fi
+
+# Validate IP address format (basic validation)
+if ! [[ "$SERVER_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo -e "${RED}Error: Invalid IP address format${NC}"
+ exit 1
+fi
+
+# Get the TURN password
+echo -e "${YELLOW}Enter a password for the TURN server (press Enter for default 'dograh-turn-secret'):${NC}"
+read -sp "> " TURN_PASSWORD
+echo ""
+
+if [[ -z "$TURN_PASSWORD" ]]; then
+ TURN_PASSWORD="dograh-turn-secret"
+ echo -e "${BLUE}Using default TURN password${NC}"
+fi
+
+echo ""
+echo -e "${GREEN}Configuration:${NC}"
+echo -e " Server IP: ${BLUE}$SERVER_IP${NC}"
+echo -e " TURN Password: ${BLUE}********${NC}"
+echo ""
+
+# Create project directory if it doesn't exist
+mkdir -p dograh 2>/dev/null || true
+cd dograh
+
+echo -e "${BLUE}[1/5] Downloading docker-compose.yaml...${NC}"
+curl -sS -o docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml
+echo -e "${GREEN}✓ docker-compose.yaml downloaded${NC}"
+
+echo -e "${BLUE}[2/5] Creating nginx.conf...${NC}"
+cat > nginx.conf << 'NGINX_EOF'
+server {
+ listen 80;
+ server_name SERVER_IP_PLACEHOLDER;
+
+ # Redirect all HTTP to HTTPS
+ return 301 https://$host$request_uri;
+}
+
+server {
+ listen 443 ssl;
+ server_name SERVER_IP_PLACEHOLDER;
+
+ ssl_certificate /etc/nginx/certs/local.crt;
+ ssl_certificate_key /etc/nginx/certs/local.key;
+
+ # Basic TLS settings
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_prefer_server_ciphers on;
+
+ location / {
+ proxy_pass http://ui:3010;
+ proxy_http_version 1.1;
+
+ # Important for WebSockets / hot reload etc.
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+
+ 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 https;
+
+ # Rewrite localhost MinIO URLs in API responses to use current domain
+ sub_filter 'http://localhost:9000/voice-audio/' 'https://$host/voice-audio/';
+ sub_filter_once off;
+ sub_filter_types application/json text/html;
+ }
+
+ location /voice-audio/ {
+ proxy_pass http://minio:9000/voice-audio/;
+
+ proxy_http_version 1.1;
+
+ # Headers for file downloads from MinIO
+ 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 https;
+
+ # Allow large file downloads
+ proxy_buffering off;
+ client_max_body_size 100M;
+ }
+}
+NGINX_EOF
+
+# Replace placeholder with actual IP
+sed -i.bak "s/SERVER_IP_PLACEHOLDER/$SERVER_IP/g" nginx.conf && rm -f nginx.conf.bak
+echo -e "${GREEN}✓ nginx.conf created${NC}"
+
+echo -e "${BLUE}[3/5] Creating SSL certificate generation script...${NC}"
+cat > generate_certificate.sh << CERT_EOF
+#!/bin/bash
+mkdir -p certs
+openssl req -x509 -nodes -newkey rsa:2048 \\
+ -keyout certs/local.key \\
+ -out certs/local.crt \\
+ -days 365 \\
+ -subj "/CN=$SERVER_IP"
+CERT_EOF
+chmod +x generate_certificate.sh
+echo -e "${GREEN}✓ generate_certificate.sh created${NC}"
+
+echo -e "${BLUE}[4/5] Generating SSL certificates...${NC}"
+./generate_certificate.sh
+echo -e "${GREEN}✓ SSL certificates generated${NC}"
+
+echo -e "${BLUE}[5/5] Creating environment file...${NC}"
+cat > .env << ENV_EOF
+# TURN Server Configuration
+TURN_HOST=$SERVER_IP
+TURN_USERNAME=dograh
+TURN_PASSWORD=$TURN_PASSWORD
+
+# Telemetry (set to false to disable)
+ENABLE_TELEMETRY=true
+ENV_EOF
+echo -e "${GREEN}✓ .env file created${NC}"
+
+echo ""
+echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}"
+echo -e "${GREEN}║ Setup Complete! ║${NC}"
+echo -e "${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}"
+echo ""
+echo -e "Files created in ${BLUE}$(pwd)${NC}:"
+echo " - docker-compose.yaml"
+echo " - nginx.conf"
+echo " - generate_certificate.sh"
+echo " - certs/local.crt"
+echo " - certs/local.key"
+echo " - .env"
+echo ""
+echo -e "${YELLOW}To start Dograh, run:${NC}"
+echo ""
+echo -e " ${BLUE}docker compose --profile remote up -d${NC}"
+echo ""
+echo -e "${YELLOW}Your application will be available at:${NC}"
+echo ""
+echo -e " ${BLUE}https://$SERVER_IP${NC}"
+echo ""
+echo -e "${YELLOW}Note:${NC} Your browser will show a security warning for the self-signed"
+echo "certificate. You can safely accept it to proceed."
+echo ""
diff --git a/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx b/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx
index 21d6539..4218fb6 100644
--- a/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx
+++ b/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx
@@ -39,6 +39,12 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia
const useAudio = true;
const audioCodec = 'default';
+ // TURN server configuration from environment variables (matching backend pattern)
+ const turnHost = process.env.NEXT_PUBLIC_TURN_HOST;
+ const turnUsername = process.env.NEXT_PUBLIC_TURN_USERNAME;
+ const turnPassword = process.env.NEXT_PUBLIC_TURN_PASSWORD;
+ const useTurn = !!(turnHost && turnUsername && turnPassword);
+
const audioRef = useRef(null);
const pcRef = useRef(null);
const wsRef = useRef(null);
@@ -67,8 +73,29 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia
}, [workflowId, workflowRunId, accessToken]);
const createPeerConnection = () => {
+ // Build ICE servers list
+ const iceServers: RTCIceServer[] = [];
+
+ if (useStun) {
+ iceServers.push({ urls: ['stun:stun.l.google.com:19302'] });
+ }
+
+ if (useTurn && turnHost && turnUsername && turnPassword) {
+ // Add TURN server with credentials from environment variables
+ iceServers.push({
+ urls: [
+ `turn:${turnHost}:3478`, // TURN over UDP
+ `turn:${turnHost}:3478?transport=tcp`, // TURN over TCP
+ ],
+ username: turnUsername,
+ credential: turnPassword
+ });
+
+ logger.info(`TURN server configured: ${turnHost}:3478`);
+ }
+
const config: RTCConfiguration = {
- iceServers: useStun ? [{ urls: ['stun:stun.l.google.com:19302'] }] : []
+ iceServers
};
const pc = new RTCPeerConnection(config);