diff --git a/docker-compose.yaml b/docker-compose.yaml
index 653427b9..307c6d6b 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -107,8 +107,10 @@ services:
MINIO_BUCKET: "voice-audio"
MINIO_SECURE: "false"
- # FastAPI workers count
- FASTAPI_WORKERS: 1
+ # Number of uvicorn worker processes (each is its own process bound to a
+ # distinct port starting at 8000). nginx load-balances across them with
+ # least_conn — see setup_remote.sh.
+ FASTAPI_WORKERS: "${FASTAPI_WORKERS:-1}"
# Langfuse — credentials can be set here or per-organization via the UI
# at /settings. Tracing is automatically active when credentials are
diff --git a/docs/deployment/docker.mdx b/docs/deployment/docker.mdx
index 9c2fa5b9..3a17a93b 100644
--- a/docs/deployment/docker.mdx
+++ b/docs/deployment/docker.mdx
@@ -6,7 +6,7 @@ description: "Deploy Dograh AI using Docker for local development and remote ser
Dograh AI can be deployed using Docker in two main configurations. Choose the option that best fits your needs:
- **Option 1**: For local development and testing on your own machine
-- **Option 2**: For remote server deployment with HTTPS (using IP address)
+- **Option 2**: For remote server deployment with HTTPS (using IP address). If you also have a custom domain, you can first deploy Dograh stack on your server using steps in this document and then proceed to the [Custom Domain](deployment/custom-domain) section.
## Option 1: Local Docker Deployment
@@ -52,7 +52,7 @@ The Quick Start above relies on direct peer-to-peer WebRTC between your browser
- The call connects but no audio flows in either direction
- The browser console reports `iceConnectionState: failed`
-- You are testing from a phone or another device on your LAN against the laptop running Docker
+- You are testing from a browser on your smartphone or another device on your LAN against the laptop running Docker
- A VPN, corporate firewall, or strict NAT sits between the browser and Docker
For these cases, use the alternate local setup script which configures a coturn TURN server alongside the rest of the stack:
@@ -90,17 +90,17 @@ Watch the video tutorial below for a step-by-step walkthrough of deploying Dogra
allowFullScreen
>
-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.
+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.**
+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. It should have minimum of 8 GB RAM and 4 vCPUs.
-- Public IP address for your server
+- Public IP address for your server. You can also access the server using a local IP address in your VPC as long as its reachable from your browser.
- 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)
-Please refer to your server hosting provider's documentaion on how you can open these ports in the firewall of the server.
+IMPORTANT: Please double check the ports on your firewall. Please refer to your server hosting provider's documentaion on how you can open these ports in the firewall of the server.
### Quick Setup
@@ -141,7 +141,7 @@ sudo docker compose --profile remote up -d --build
```
-First boot in build mode takes several minutes — Docker has to build both the API and UI images before the stack comes up.
+First boot in build mode takes several minutes — Docker has to build both the API and UI images before the stack comes up.
### Access Your Application
@@ -190,14 +190,57 @@ In this mode the script:
2. If you run the script from inside an existing dograh checkout, offers to use that checkout instead of cloning.
3. Generates a `docker-compose.override.yaml` next to `docker-compose.yaml` that swaps the `image:` directives for local `build:` directives. Docker Compose auto-loads the override file — you don't need to pass any `-f` flags.
-After editing `api/` or `ui/` code, rebuild and restart the affected service:
+#### Updating after a code change
+
+Whenever the codebase changes — a `git pull`, a local edit, or a submodule bump — rebuild the `api` and `ui` images and recreate the running containers so they pick up the new code.
+
+**TL;DR (rebuilds both services and restarts in one shot):**
```bash
cd dograh
-sudo docker compose --profile remote build api # or ui
+sudo docker compose --profile remote up -d --build
+```
+
+`--build` rebuilds any service whose `build:` context has changed (both `api` and `ui` are wired through the `docker-compose.override.yaml` written by `setup_remote.sh`), and `up -d` recreates the affected containers so they run off the new image.
+
+**Step-by-step** — use this when you're pulling fresh code from a remote, or want to be explicit:
+
+```bash
+cd dograh
+
+# 1. Pull the latest source.
+git pull
+git submodule update --init --recursive # picks up pipecat submodule bumps
+
+# 2. Rebuild both images.
+sudo docker compose --profile remote build api ui
+
+# 3. Recreate the running containers so they use the new images.
sudo docker compose --profile remote up -d
```
+If you update `pipecat` submodule, you must do `git submodule update --init --recursive`, or the Docker build step will not pick up `pipecat` changes
+
+**Rebuild a single service** — faster when you only changed one side:
+
+```bash
+sudo docker compose --profile remote build api # or: build ui
+sudo docker compose --profile remote up -d api # or: up -d ui
+```
+
+**Force a clean rebuild** — needed when a base image changed, a `pip install` was added, or you suspect a stale layer:
+
+```bash
+sudo docker compose --profile remote build --no-cache api ui
+sudo docker compose --profile remote up -d
+```
+
+**Verify the containers were actually recreated** — check the `CREATED` column shows a recent timestamp, not minutes/hours ago:
+
+```bash
+sudo docker compose --profile remote ps
+```
+
To revert to pulling official images, delete `docker-compose.override.yaml` and start the stack with `--pull always` as in the [prebuilt flow](#start-the-application).
diff --git a/docs/getting-started/index.mdx b/docs/getting-started/index.mdx
index 14d9cd2d..7dfebff4 100644
--- a/docs/getting-started/index.mdx
+++ b/docs/getting-started/index.mdx
@@ -1,11 +1,10 @@
---
title: "Introduction"
-description: "Open-source alternative to Vapi - build voice AI agents with full control & transparency"
---
## About Dograh
-**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:
+**Dograh is an open-source alternative to platforms like Vapi and Retell** - 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
- **Complete control** - every line of code is open and customizable
@@ -25,7 +24,7 @@ Watch the following video to learn about Dograh’s capabilities.
## Setting up
-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/introduction) section.
+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 different than your local machine, please check the [Deployment](deployment/introduction) 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
index 1abe9f2f..334a516d 100755
--- a/scripts/setup_remote.sh
+++ b/scripts/setup_remote.sh
@@ -116,6 +116,26 @@ fi
# Telemetry opt-out (default: true)
ENABLE_TELEMETRY="${ENABLE_TELEMETRY:-true}"
+# Number of uvicorn worker processes. Each runs as its own process on a
+# distinct port (8000, 8001, ...) and nginx balances across them with
+# least_conn. Better than uvicorn --workers for long-lived WebSocket
+# connections, which would otherwise stick to whichever worker accepted them.
+if [[ -z "$FASTAPI_WORKERS" ]]; then
+ if [[ -t 0 ]]; then
+ echo ""
+ echo -e "${YELLOW}Number of FastAPI workers (uvicorn processes nginx will load-balance):${NC}"
+ read -p "[4]: " FASTAPI_WORKERS
+ FASTAPI_WORKERS="${FASTAPI_WORKERS:-4}"
+ else
+ FASTAPI_WORKERS="4"
+ fi
+fi
+
+if ! [[ "$FASTAPI_WORKERS" =~ ^[1-9][0-9]*$ ]]; then
+ echo -e "${RED}Error: FASTAPI_WORKERS must be a positive integer (got: $FASTAPI_WORKERS)${NC}"
+ exit 1
+fi
+
# Where setup artifacts (.env, certs, nginx.conf, etc.) will land. Build mode
# with an existing repo writes them next to docker-compose.yaml in cwd;
# everything else writes into a fresh dograh/ subdirectory.
@@ -165,9 +185,10 @@ fi
echo ""
echo -e "${GREEN}Configuration:${NC}"
-echo -e " Server IP: ${BLUE}$SERVER_IP${NC}"
-echo -e " TURN Secret: ${BLUE}********${NC}"
-echo -e " Deploy mode: ${BLUE}$DEPLOY_MODE${NC}"
+echo -e " Server IP: ${BLUE}$SERVER_IP${NC}"
+echo -e " TURN Secret: ${BLUE}********${NC}"
+echo -e " Deploy mode: ${BLUE}$DEPLOY_MODE${NC}"
+echo -e " FastAPI workers: ${BLUE}$FASTAPI_WORKERS${NC} (ports 8000..$((8000 + FASTAPI_WORKERS - 1)))"
if [[ "$DEPLOY_MODE" == "build" ]]; then
if [[ "$REPO_SOURCE" == "clone" ]]; then
echo -e " Source: ${BLUE}clone $FORK_REPO@$BRANCH${NC}"
@@ -209,7 +230,22 @@ else
fi
echo -e "${BLUE}[2/$TOTAL] Creating nginx.conf...${NC}"
-cat > nginx.conf << 'NGINX_EOF'
+# Build the upstream block first (needs shell interpolation for the server
+# lines), then append the static server blocks via a quoted heredoc. The
+# SERVER_IP_PLACEHOLDER gets replaced by sed below.
+{
+ echo "# Backend API workers — one uvicorn process per port, balanced by least_conn."
+ echo "# Generated by setup_remote.sh; regenerate to change worker count."
+ echo "upstream dograh_api {"
+ echo " least_conn;"
+ for ((i=0; i nginx.conf
# Replace placeholder with actual IP
sed -i.bak "s/SERVER_IP_PLACEHOLDER/$SERVER_IP/g" nginx.conf && rm -f nginx.conf.bak
@@ -364,6 +405,12 @@ OSS_JWT_SECRET=$OSS_JWT_SECRET
# Telemetry (set to false to disable)
ENABLE_TELEMETRY=$ENABLE_TELEMETRY
+
+# Number of uvicorn worker processes; nginx load-balances across them
+# (ports 8000..$((8000 + FASTAPI_WORKERS - 1))) with least_conn.
+# Must match the upstream block in nginx.conf — re-run setup_remote.sh
+# (with DOGRAH_FORCE_OVERWRITE=1) to change.
+FASTAPI_WORKERS=$FASTAPI_WORKERS
ENV_EOF
echo -e "${GREEN}✓ .env file created${NC}"
@@ -382,14 +429,14 @@ services:
build:
context: .
dockerfile: api/Dockerfile
- image: dograh-local/dograh-api:dev
+ image: dograh-local/dograh-api:local
pull_policy: never
ui:
build:
context: .
dockerfile: ui/Dockerfile
- image: dograh-local/dograh-ui:dev
+ image: dograh-local/dograh-ui:local
pull_policy: never
OVERRIDE_EOF
echo -e "${GREEN}✓ docker-compose.override.yaml created${NC}"
diff --git a/scripts/start_services_docker.sh b/scripts/start_services_docker.sh
index 48965c91..435c1cbf 100755
--- a/scripts/start_services_docker.sh
+++ b/scripts/start_services_docker.sh
@@ -61,7 +61,16 @@ start() {
start ari_manager python -m api.services.telephony.ari_manager
start campaign_orchestrator python -m api.services.campaign.campaign_orchestrator
-start uvicorn uvicorn api.app:app --host 0.0.0.0 --port "$UVICORN_BASE_PORT" --workers "$FASTAPI_WORKERS"
+
+# Spawn FASTAPI_WORKERS independent uvicorn processes on consecutive ports
+# starting at UVICORN_BASE_PORT. nginx upstream (configured in setup_remote.sh)
+# balances across them with least_conn — better than uvicorn --workers for
+# long-lived WebSocket connections, which would otherwise stick to whichever
+# worker accepted them first.
+for ((i=0; i