feat: add devcontainer for local setup

This commit is contained in:
Abhishek Kumar 2026-05-25 15:58:26 +05:30
parent a725fda274
commit 6b33addb25
26 changed files with 671 additions and 130 deletions

102
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,102 @@
# =============================================================================
# Stage 1: venv-builder
# Minimal image whose only job is to populate the venv. Uses the same Python
# source as the runtime stage (deadsnakes) so the symlinks inside the venv
# (e.g. venv/bin/python -> /usr/bin/python3.13) stay valid after COPY --from.
# Everything in this stage except the venv itself is discarded.
# =============================================================================
FROM ubuntu:24.04 AS venv-builder
RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
ca-certificates \
git \
libpq-dev \
pkg-config \
software-properties-common \
&& add-apt-repository -y ppa:deadsnakes/ppa \
&& apt-get install -y --no-install-recommends \
python3.13 \
python3.13-venv \
python3.13-dev \
&& rm -rf /var/lib/apt/lists/*
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
# Build the venv at the path it will live at in the final image, so shebangs
# and console-scripts inside the venv reference the correct runtime location
# once the seed step rsyncs them into the named volume.
ENV VIRTUAL_ENV=/workspaces/dograh/venv \
PATH=/workspaces/dograh/venv/bin:$PATH
RUN mkdir -p /workspaces/dograh && python3.13 -m venv "$VIRTUAL_ENV"
# Layer 1: API deps. Cache invalidates only when these two files change.
RUN --mount=type=bind,source=api/requirements.txt,target=/tmp/req.txt \
--mount=type=bind,source=api/requirements.dev.txt,target=/tmp/req.dev.txt \
--mount=type=cache,target=/root/.cache/uv \
uv pip install -r /tmp/req.txt -r /tmp/req.dev.txt
# Layer 2: pipecat deps. Cache invalidates when pipecat source changes.
# After installing pipecat, two hardening tweaks (mirrored from api/Dockerfile):
# 1. Swap opencv-python (pulled by pipecat[webrtc]) for opencv-python-headless.
# The non-headless build links against X11/Qt (libxcb*); without those
# shared libs in the image, `import cv2` fails at runtime.
# 2. Pre-download NLTK's punkt_tab tokenizer so pipecat's text processing
# doesn't hit the network on first agent run. NLTK auto-finds it under
# sys.prefix/nltk_data, so it travels with the venv on COPY/rsync.
RUN --mount=type=bind,source=pipecat,target=/tmp/pipecat,rw \
--mount=type=cache,target=/root/.cache/uv \
uv pip install '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp]' \
&& uv pip install --group /tmp/pipecat/pyproject.toml:dev \
&& uv pip uninstall opencv-python \
&& uv pip install opencv-python-headless \
&& python -c "import nltk; nltk.download('punkt_tab', download_dir='/workspaces/dograh/venv/nltk_data', quiet=True)"
# =============================================================================
# Stage 2: runtime devcontainer image
# Inherits the devcontainer base (vscode user, sudo, etc.) and brings only the
# populated venv across from the builder stage.
# =============================================================================
FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04
RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
ffmpeg \
git \
jq \
libpq-dev \
pkg-config \
postgresql-client \
procps \
redis-tools \
rsync \
software-properties-common \
&& add-apt-repository -y ppa:deadsnakes/ppa \
&& apt-get install -y --no-install-recommends \
python3.13 \
python3.13-venv \
python3.13-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# uv is still needed at runtime so post-create.sh can do the editable
# pipecat install (and any ad-hoc `uv pip install` users might run).
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
# Bring the populated venv across. At runtime, the named volume in
# docker-compose.yml shadows /workspaces/dograh/venv; post-create.sh
# rsyncs from /opt/venv-template into the (initially empty) volume,
# comparing build-stamps so an image rebuild that changed deps re-seeds.
COPY --from=venv-builder --chown=vscode:vscode /workspaces/dograh/venv /opt/venv-template
RUN date -u +%s > /opt/venv-template/.build-stamp \
&& chown vscode:vscode /opt/venv-template/.build-stamp
ENV VIRTUAL_ENV=/workspaces/dograh/venv \
PATH=/workspaces/dograh/venv/bin:$PATH

View file

@ -0,0 +1,9 @@
{
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "1.7.1",
"resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6",
"integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6"
}
}
}

View file

@ -0,0 +1,71 @@
{
"name": "Dograh",
"dockerComposeFile": [
"../docker-compose-local.yaml",
"docker-compose.yml"
],
"service": "workspace",
"runServices": [
"workspace",
"postgres",
"redis",
"minio"
],
"workspaceFolder": "/workspaces/dograh",
"shutdownAction": "stopCompose",
"overrideCommand": false,
"remoteUser": "vscode",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "24"
}
},
"initializeCommand": "git submodule update --init --recursive",
"postCreateCommand": "bash .devcontainer/scripts/post-create.sh",
"postStartCommand": "bash .devcontainer/scripts/post-start.sh",
"forwardPorts": [
5432,
6379,
9000,
9001
],
"portsAttributes": {
"3000": {
"label": "Dograh UI",
"onAutoForward": "ignore"
},
"8000": {
"label": "Dograh API",
"onAutoForward": "ignore"
},
"5432": {
"label": "Postgres"
},
"6379": {
"label": "Redis"
},
"9000": {
"label": "MinIO API"
},
"9001": {
"label": "MinIO Console"
}
},
"customizations": {
"vscode": {
"settings": {
"python.defaultInterpreterPath": "/workspaces/dograh/venv/bin/python",
"terminal.integrated.defaultProfile.linux": "bash"
},
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.debugpy",
"ms-azuretools.vscode-docker",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss"
]
}
}
}

View file

@ -0,0 +1,31 @@
services:
workspace:
build:
context: .
dockerfile: .devcontainer/Dockerfile
command: sleep infinity
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
minio:
condition: service_healthy
environment:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PYTHONUNBUFFERED: "1"
extra_hosts:
- "host.docker.internal:host-gateway"
init: true
networks:
- app-network
volumes:
- .:/workspaces/dograh:cached
- dograh-venv:/workspaces/dograh/venv
- dograh-ui-node_modules:/workspaces/dograh/ui/node_modules
- dograh-ts-validator-node_modules:/workspaces/dograh/api/mcp_server/ts_validator/node_modules
volumes:
dograh-venv:
dograh-ui-node_modules:
dograh-ts-validator-node_modules:

View file

@ -0,0 +1,119 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="/workspaces/dograh"
UI_ENV_EXAMPLE="$ROOT_DIR/ui/.env.example"
UI_ENV_FILE="$ROOT_DIR/ui/.env"
VENV_PATH="$ROOT_DIR/venv"
VENV_TEMPLATE="/opt/venv-template"
TOTAL_STEPS=5
STEP=0
STEP_START=$SECONDS
SCRIPT_START=$SECONDS
step() {
STEP=$((STEP + 1))
STEP_START=$SECONDS
printf '\n==> [%d/%d] %s\n' "$STEP" "$TOTAL_STEPS" "$1"
}
step_done() {
printf ' done in %ds\n' "$((SECONDS - STEP_START))"
}
fail() {
printf '\n!! FAILED at step %d/%d (%s) after %ds\n' \
"$STEP" "$TOTAL_STEPS" "${1:-unknown}" "$((SECONDS - SCRIPT_START))" >&2
exit 1
}
trap 'fail "exit $?"' ERR
copy_if_missing() {
local src=$1
local dst=$2
if [[ -f "$dst" ]]; then
echo "Keeping existing $dst"
return
fi
cp "$src" "$dst"
echo "Created $dst from $src"
}
# Copy an api/.env*.example template to its target, rewriting infra hostnames
# from `localhost` to the docker service names defined in
# docker-compose-local.yaml. MINIO_PUBLIC_ENDPOINT stays on localhost — that
# URL ends up in UI responses and is loaded by the host browser via the
# forwarded port. No-op if the target already exists.
copy_env_with_docker_hostnames() {
local src=$1
local dst=$2
if [[ -f "$dst" ]]; then
echo "Keeping existing $dst"
return
fi
cp "$src" "$dst"
sed -i \
-e 's|@localhost:5432|@postgres:5432|g' \
-e 's|@localhost:6379|@redis:6379|g' \
-e 's|^MINIO_ENDPOINT=localhost:9000|MINIO_ENDPOINT=minio:9000|' \
"$dst"
echo "Created $dst from $src (rewrote service hostnames for docker network)"
}
# Seed the venv named volume from the image-baked template, but only when
# the template's build-stamp differs from what's currently in the volume
# (first start, or any rebuild that changed requirements.txt / pipecat).
seed_venv() {
local image_stamp venv_stamp
image_stamp=$(cat "$VENV_TEMPLATE/.build-stamp" 2>/dev/null || echo missing)
venv_stamp=$(cat "$VENV_PATH/.build-stamp" 2>/dev/null || echo none)
if [[ "$image_stamp" == "$venv_stamp" ]]; then
echo "Venv already in sync with image template (stamp=$venv_stamp)"
return
fi
echo "Re-seeding venv: image=$image_stamp, volume=$venv_stamp"
rsync -a --delete "$VENV_TEMPLATE/" "$VENV_PATH/"
}
cd "$ROOT_DIR"
step "Fixing ownership of named volume mountpoints"
# Named volumes are created owned by root; postCreateCommand runs as the
# remote user. Chown the mountpoint roots so the steps below can write.
sudo chown "$(id -u):$(id -g)" \
"$VENV_PATH" \
"$ROOT_DIR/ui/node_modules" \
"$ROOT_DIR/api/mcp_server/ts_validator/node_modules"
step_done
step "Seeding venv from image template"
seed_venv
step_done
step "Copying example env files into place"
copy_env_with_docker_hostnames "$ROOT_DIR/api/.env.example" "$ROOT_DIR/api/.env"
copy_env_with_docker_hostnames "$ROOT_DIR/api/.env.test.example" "$ROOT_DIR/api/.env.test"
copy_if_missing "$UI_ENV_EXAMPLE" "$UI_ENV_FILE"
step_done
step "Switching pipecat to editable install from workspace"
# pipecat's deps are already in the seeded venv as a frozen snapshot from
# the build context. Re-register editable from the bind-mounted workspace
# so source edits take effect. --no-deps skips re-resolving transitive
# dependencies (already present from the seeded image template).
uv pip install -e "$ROOT_DIR/pipecat" --no-deps
step_done
step "Installing npm dependencies (ui + ts_validator in parallel)"
npm ci --prefix ui &
ui_pid=$!
npm ci --prefix api/mcp_server/ts_validator &
ts_pid=$!
wait "$ui_pid" || fail "npm ci ui"
wait "$ts_pid" || fail "npm ci ts_validator"
step_done
printf '\nDevcontainer bootstrap complete in %ds.\n' "$((SECONDS - SCRIPT_START))"

View file

@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
# Intentionally no `http://localhost:PORT` URLs below — VS Code's terminal
# URL detector adds any printed URL to its auto-forwarded-ports list and
# then polls it, which produces ECONNREFUSED log spam every ~20s for ports
# that aren't bound yet. The Ports panel auto-detects bound ports anyway.
cat <<'EOF'
Dograh devcontainer ready.
Start the backend:
bash scripts/start_services_dev.sh
Start the UI in another terminal:
cd ui && npm run dev -- --hostname 0.0.0.0
URLs and other workflow notes: docs/contribution/setup.mdx
EOF

View file

@ -65,10 +65,10 @@ jobs:
with:
submodules: recursive
- name: Set up Python 3.12
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
cache: pip
cache-dependency-path: |
api/requirements.txt

View file

@ -27,10 +27,10 @@ jobs:
with:
submodules: recursive
- name: Set up Python 3.12
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
cache: pip
cache-dependency-path: |
api/requirements.txt

2
.gitignore vendored
View file

@ -11,6 +11,7 @@ infrastructure/
prd/
.vercel
api/.env.devcontainer
venv/
.venv/
.playwright-mcp
@ -18,4 +19,3 @@ coturn/
*.wav
dograh_pcm_cache/
node_modules/
.vscode

View file

@ -1 +1 @@
3.13.7
3.13.7

104
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,104 @@
// Debug configurations for Dograh contributors.
//
// Prerequisites:
// - Python interpreter selected in VS Code (devcontainer sets this
// automatically; otherwise pick `./venv/bin/python` via the
// "Python: Select Interpreter" command).
// - api/.env exists (copy from api/.env.example, or api/.env.devcontainer
// is created automatically by the devcontainer post-create script).
// - api/.env.test exists for the test configurations (copy from
// api/.env.example and point at a throwaway database).
//
// All Python configs set justMyCode=false so the debugger steps into
// library code (useful for tracing through pipecat/fastapi/etc.).
{
"version": "0.2.0",
"configurations": [
{
"name": "API: Uvicorn (reload)",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": [
"api.app:app",
"--reload",
"--host", "0.0.0.0",
"--port", "8000"
],
"cwd": "${workspaceFolder}",
"envFile": "${workspaceFolder}/api/.env",
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"justMyCode": false
},
{
"name": "API: Arq worker (watch)",
"type": "debugpy",
"request": "launch",
"module": "arq",
"args": [
"api.tasks.arq.WorkerSettings",
"--watch", "${workspaceFolder}/api",
"--custom-log-dict", "api.tasks.arq.LOG_CONFIG"
],
"cwd": "${workspaceFolder}",
"envFile": "${workspaceFolder}/api/.env",
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"justMyCode": false
},
{
"name": "Tests: API (pytest, full suite)",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": ["tests", "-xvs"],
"cwd": "${workspaceFolder}/api",
"envFile": "${workspaceFolder}/api/.env.test",
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"justMyCode": false
},
{
"name": "Tests: API (pytest, current file)",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": ["${file}", "-xvs"],
"cwd": "${workspaceFolder}/api",
"envFile": "${workspaceFolder}/api/.env.test",
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"justMyCode": false
},
{
"name": "Tests: Pipecat (pytest, current file)",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": ["${file}", "-xvs"],
"cwd": "${workspaceFolder}/pipecat",
"envFile": "${workspaceFolder}/api/.env",
"env": {
"PYTHONPATH": "${workspaceFolder}/pipecat/src"
},
"justMyCode": false
},
{
"name": "Python: Current file",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"cwd": "${workspaceFolder}",
"envFile": "${workspaceFolder}/api/.env",
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"justMyCode": false
}
]
}

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"python.defaultInterpreterPath": "${workspaceFolder}/venv/bin/python"
}

17
api/.env.test.example Normal file
View file

@ -0,0 +1,17 @@
# Test environment. Read by pytest runs and the "Tests: API" launch
# configurations in .vscode/launch.json.
#
# Tests target a separate database (`test_db`) so they don't clobber dev
# data. Create it once after the postgres container is up:
# docker compose -f docker-compose-local.yaml exec postgres \
# createdb -U postgres test_db
ENVIRONMENT="test"
LOG_LEVEL="DEBUG"
UI_APP_URL=http://localhost:3000
DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/test_db"
REDIS_URL="redis://:redissecret@localhost:6379/0"
MINIO_PUBLIC_ENDPOINT=http://localhost:9000

View file

@ -1,6 +1,6 @@
# Multi-stage Dockerfile
# Stage 1: Builder - Install Python dependencies
FROM python:3.12-slim AS builder
FROM python:3.13-slim AS builder
WORKDIR /app
@ -56,7 +56,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& chmod +x /usr/local/bin/ffmpeg /usr/local/bin/ffprobe
# Stage 4: Runtime - Minimal image with only runtime dependencies
FROM python:3.12-slim AS runner
FROM python:3.13-slim AS runner
WORKDIR /app
@ -65,7 +65,7 @@ COPY --from=ffmpeg-static /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=ffmpeg-static /usr/local/bin/ffprobe /usr/local/bin/ffprobe
# Node.js 22 binary only (ts_validator subprocess needs node >=22.6 for
# native TypeScript stripping; see api/mcp_server/ts_bridge.py). python:3.12-slim
# native TypeScript stripping; see api/mcp_server/ts_bridge.py). python:3.13-slim
# already provides libstdc++6, libgcc-s1, and ca-certificates that node needs.
COPY --from=node:22-slim /usr/local/bin/node /usr/local/bin/node
@ -104,4 +104,4 @@ ENV LOG_TO_FILE=false
EXPOSE 8000
# Run the FastAPI app with uvicorn
CMD ["./scripts/start_services_docker.sh"]
CMD ["./scripts/start_services_docker.sh"]

View file

@ -2,4 +2,4 @@
name = "dograh-api"
version = "1.31.0"
description = "Backend API for Dograh voice AI platform"
requires-python = ">=3.12"
requires-python = ">=3.13,<3.14"

View file

@ -2,4 +2,3 @@ mypy==2.0.0
watchfiles==1.1.1
datamodel-code-generator==0.56.1
twine==6.2.0
-e ./sdk/python

View file

@ -1,4 +1,4 @@
---
title: Contribution
description: If you would like to set up the development environment and use a coding agent like Claude to make changes to the codebase, you can follow this document to help setup the right development environment for yourself.
description: If you would like to set up Dograh development environment in your IDE and use a coding agent like Claude/ Codex to make changes to the codebase, you can follow this document to help setup the right development environment for yourself.
---

View file

@ -1,29 +1,83 @@
---
title: Setup
description: You can use this document to setup the dev environment for yourself.
description: Use this page to set up the Dograh contributor environment, with a devcontainer-first workflow.
---
<Note>
If the below steps do not work out for you, it would be great if you can open an issue on [Github](https://github.com/dograh-hq/dograh/issues).
If the steps below do not work for you, please open an issue on [GitHub](https://github.com/dograh-hq/dograh/issues).
</Note>
### System Requirements
- git to clone the forked repository
- Node.js 24 to run the UI (we recommend using [NVM](https://github.com/nvm-sh/nvm) on macOS/Linux or [NVM for Windows](https://github.com/coreybutler/nvm-windows) on Windows to manage your node versions locally)
- Python 3.13 to run the backend
- Docker to run the database and redis cache locally
### Recommended: Devcontainer setup
#### System Requirements
- Git
- Docker Desktop or another local Docker engine
- A Dev Container-compatible editor. The most tested path is VS Code with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
<Note>
All commands below are shown for **macOS / Linux**. Expand the **Windows** tab for the PowerShell equivalent where it differs.
The checked-in `.devcontainer/` follows the Dev Containers specification. VS Code is the default recommendation because its support is the most mature and best documented, but the same setup should also work in tools like Cursor and JetBrains IDEs that support Dev Containers.
</Note>
### Steps
1. Fork the Dograh repository by going to https://github.com/dograh-hq/dograh
2. Clone **your fork** on your machine. You can skip `--recurse-submodules` here — the bootstrap script in the next step will initialize submodules for you.
```
1. Fork the Dograh repository at https://github.com/dograh-hq/dograh
2. Clone **your fork**:
```bash
git clone https://github.com/<GITHUB_HANDLE>/dograh
cd dograh
```
3. Run the contributor bootstrap. It configures `origin` (your fork) and `upstream` (`dograh-hq/dograh`), initializes the pipecat submodule, creates the Python venv, and copies the `.env` templates. Re-running it is safe — already-configured pieces are skipped.
If you cloned `dograh-hq/dograh` directly instead of your fork, run `bash scripts/setup_fork.sh` once inside the container after it boots to reset `origin` and `upstream`.
3. Open the repository in your Dev Container-compatible editor and start the devcontainer.
If you are using VS Code, run **Dev Containers: Reopen in Container**.
4. Wait for the first container boot to finish. The post-create bootstrap will:
- initialize the `pipecat` submodule
- create `venv/` with Python 3.13
- install backend dependencies with `scripts/setup_requirements.sh --dev`
- install `ui/` dependencies and `api/mcp_server/ts_validator` dependencies
- create `api/.env` and `ui/.env` if they do not already exist (rewriting docker network hostnames in `api/.env`)
- start `postgres`, `redis`, and `minio` via Docker Compose
5. Start the backend from a terminal inside the container:
```bash
bash scripts/start_services_dev.sh
```
6. Start the UI from another terminal inside the container:
```bash
cd ui
npm run dev -- --hostname 0.0.0.0
```
7. Verify that the backend is healthy:
```bash
curl -X GET localhost:8000/api/v1/health
```
8. Open the app at `http://localhost:3000`.
You can tail backend logs from inside the container with:
```bash
tail -f logs/latest/*.log
```
The backend reads `api/.env` both on the host and inside the devcontainer. The post-create bootstrap rewrites `DATABASE_URL`, `REDIS_URL`, and `MINIO_ENDPOINT` to docker network aliases (`postgres`, `redis`, `minio`) when it creates the file. If you already had an `api/.env` from host development with `localhost` hosts for those, edit it before starting the backend inside the devcontainer.
#### Restarting the backend
Re-run the same start script to restart. It reads the PID files under `run/`, terminates the previous services along with their descendants, and starts fresh ones.
```bash
bash scripts/start_services_dev.sh
```
<Note>
`uvicorn` runs with `--reload --reload-dir api`, so edits under `api/` are picked up automatically. The other services (`ari_manager`, `campaign_orchestrator`, `arq`) do not auto-reload; re-run the start script after changing code they execute.
</Note>
### Fallback: host-managed setup
Use this only if you do not want to use the devcontainer.
#### System Requirements
- Git
- Node.js 24 to run the UI
- Python 3.13 to run the backend
- Docker to run Postgres, Redis, and MinIO locally
1. Run the contributor bootstrap. It configures `origin` (your fork) and `upstream` (`dograh-hq/dograh`), initializes the pipecat submodule, creates the Python venv, and copies the `.env` templates.
<CodeGroup>
```bash macOS/Linux
bash scripts/setup_fork.sh
@ -32,7 +86,7 @@ bash scripts/setup_fork.sh
.\scripts\setup_fork.ps1
```
</CodeGroup>
Activate the venv (the bootstrap script created it but won't activate it for you):
2. Activate the virtual environment:
<CodeGroup>
```bash macOS/Linux
source venv/bin/activate
@ -41,45 +95,28 @@ source venv/bin/activate
.\venv\Scripts\Activate.ps1
```
</CodeGroup>
4. Ensure you are on right version of Node.js using `node --version`
```
3. Ensure your local Node version is 24:
```bash
nvm use 24
```
5. Install UI dependencies
```
4. Install UI dependencies:
```bash
cd ui && npm install && cd ..
```
6. Start local docker services
<Note>Please ensure you dont have any other instance of conflicting services running by checking `docker ps`</Note>
```
5. Start the local Docker services:
```bash
docker compose -f docker-compose-local.yaml up -d
```
Verify that the processes have started by running `docker ps`
```
abhishek$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9066b7244b2f postgres:17 "docker-entrypoint.s…" 18 seconds ago Up 18 seconds (healthy) 0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp dograh-postgres-1
6c7cb8afdf18 redis:7 "docker-entrypoint.s…" 18 seconds ago Up 18 seconds (healthy) 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp dograh-redis-1
a57e3e92b02c minio/minio "/usr/bin/docker-ent…" 18 seconds ago Up 18 seconds (healthy) 127.0.0.1:9000-9001->9000-9001/tcp dograh-minio-1
```
7. Install Python requirements. The script installs `api/requirements.txt` and pipecat with the required extras. Add the dev flag if you also want the pipecat dev dependency group (pytest, ruff, pre-commit, etc.).
6. Install Python requirements:
<CodeGroup>
```bash macOS/Linux
# Default (runtime only)
bash scripts/setup_requirements.sh
# Include pipecat dev dependencies
bash scripts/setup_requirements.sh --dev
```
```powershell Windows
# Default (runtime only)
.\scripts\setup_requirements.ps1
# Include pipecat dev dependencies
.\scripts\setup_requirements.ps1 -Dev
```
</CodeGroup>
8. Start backend services
7. Start the backend services:
<CodeGroup>
```bash macOS/Linux
bash scripts/start_services_dev.sh
@ -88,44 +125,11 @@ bash scripts/start_services_dev.sh
.\scripts\start_services_dev.ps1
```
</CodeGroup>
Verify that your backend server is running
<CodeGroup>
```bash macOS/Linux
curl -X GET localhost:8000/api/v1/health
```
```powershell Windows
curl.exe http://localhost:8000/api/v1/health
```
</CodeGroup>
You would be able to see the logs in logs/ directory.
<CodeGroup>
```bash macOS/Linux
tail -f logs/latest/*.log
```
```powershell Windows
Get-Content logs/latest/*.log -Wait
```
</CodeGroup>
#### Restarting the backend
Re-run the same start script to restart. It reads the PID files under `run/`, terminates the previous services along with their descendants, and starts fresh ones.
<CodeGroup>
```bash macOS/Linux
bash scripts/start_services_dev.sh
```
```powershell Windows
.\scripts\start_services_dev.ps1
```
</CodeGroup>
<Note>
`uvicorn` runs with `--reload --reload-dir api`, so edits under `api/` are picked up automatically — no restart needed. The other services (`ari_manager`, `campaign_orchestrator`, `arq`) do **not** auto-reload; re-run the start script after changing code they execute.
</Note>
9. Start the UI
```
8. Start the UI:
```bash
cd ui && npm run dev
```
10. You should be able to open the application on `localhost:3000` now
9. Open the application on `http://localhost:3000`.
### Keeping your fork in sync with upstream
The bootstrap script configures two remotes: `origin` (your fork, where you push) and `upstream` (`dograh-hq/dograh`, where new commits land). To pull in upstream changes:
@ -133,12 +137,13 @@ The bootstrap script configures two remotes: `origin` (your fork, where you push
```bash
git fetch upstream
git checkout main
git merge upstream/main # or: git rebase upstream/main
git merge upstream/main
git push origin main
```
Check your remotes any time with `git remote -v`. You should see:
```
```bash
origin https://github.com/<YOUR_HANDLE>/dograh.git (fetch/push)
upstream https://github.com/dograh-hq/dograh.git (fetch/push)
```
@ -148,4 +153,4 @@ Always push feature branches to **`origin`** (your fork), then open a pull reque
</Note>
### Next Steps
We ship with AGENTS.md and CLAUDE.md which will help the Coding Agents get started quickly with the codebase. This should help your favourite coding agents to be able to navigate the codebase quickly and you can make changes to it and suit your specification better.
The repository ships with `AGENTS.md` and `CLAUDE.md` so coding agents can navigate the codebase quickly. Adjust them locally if you need agent-specific guidance for your workflow.

View file

@ -125,6 +125,13 @@
"tab": "Developer",
"icon": "code",
"groups": [
{
"group": "Contribution",
"pages": [
"contribution/introduction",
"contribution/setup"
]
},
{
"group": "Guides",
"pages": [
@ -152,13 +159,6 @@
"deployment/update",
"deployment/heroku"
]
},
{
"group": "Contribution",
"pages": [
"contribution/introduction",
"contribution/setup"
]
}
]
},

View file

@ -102,18 +102,33 @@ Write-Host '[3/4] Python virtual environment' -ForegroundColor Blue
$VenvPath = Join-Path $BaseDir 'venv'
$VenvActivate = Join-Path $VenvPath 'Scripts/Activate.ps1'
if (Test-Path $VenvActivate) {
Write-Host "OK venv already exists at $VenvPath" -ForegroundColor Green
} else {
$py = $null
foreach ($candidate in @('python3.13', 'python', 'python3')) {
if (Get-Command $candidate -ErrorAction SilentlyContinue) {
$py = $candidate
break
function Get-Python313Command {
foreach ($candidate in @('python3.13', 'python3', 'python')) {
if (-not (Get-Command $candidate -ErrorAction SilentlyContinue)) {
continue
}
$version = & $candidate -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>$null
if ($LASTEXITCODE -eq 0 -and $version -eq '3.13') {
return $candidate
}
}
return $null
}
if (Test-Path $VenvActivate) {
$venvPython = Join-Path $VenvPath 'Scripts/python.exe'
$venvVersion = & $venvPython -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>$null
if ($LASTEXITCODE -ne 0 -or $venvVersion -ne '3.13') {
Write-Host "Error: existing venv uses Python $venvVersion. Remove $VenvPath and re-run with Python 3.13." -ForegroundColor Red
exit 1
}
Write-Host "OK venv already exists at $VenvPath (Python $venvVersion)" -ForegroundColor Green
} else {
$py = Get-Python313Command
if (-not $py) {
Write-Host 'Error: no python interpreter found on PATH. Install Python 3.13.' -ForegroundColor Red
Write-Host 'Error: no Python 3.13 interpreter found on PATH. Install Python 3.13.' -ForegroundColor Red
exit 1
}
& $py -m venv $VenvPath
@ -128,8 +143,9 @@ Write-Host ''
Write-Host '[4/4] Environment files' -ForegroundColor Blue
$pairs = @(
@{ Src = 'api/.env.example'; Dst = 'api/.env' },
@{ Src = 'ui/.env.example'; Dst = 'ui/.env' }
@{ Src = 'api/.env.example'; Dst = 'api/.env' },
@{ Src = 'api/.env.test.example'; Dst = 'api/.env.test' },
@{ Src = 'ui/.env.example'; Dst = 'ui/.env' }
)
foreach ($p in $pairs) {
if (Test-Path $p.Dst) {

View file

@ -102,18 +102,36 @@ echo ""
echo -e "${BLUE}[3/4] Python virtual environment${NC}"
VENV_PATH="$BASE_DIR/venv"
if [[ -d "$VENV_PATH" && -f "$VENV_PATH/bin/activate" ]]; then
echo -e "${GREEN}✓ venv already exists at $VENV_PATH${NC}"
else
PY=""
find_python_313() {
local candidate=""
local version=""
for candidate in python3.13 python3 python; do
if command -v "$candidate" >/dev/null 2>&1; then
PY="$candidate"
break
if ! command -v "$candidate" >/dev/null 2>&1; then
continue
fi
version=$("$candidate" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || true)
if [[ "$version" == "3.13" ]]; then
echo "$candidate"
return 0
fi
done
return 1
}
if [[ -d "$VENV_PATH" && -f "$VENV_PATH/bin/activate" ]]; then
VENV_VERSION=$("$VENV_PATH/bin/python" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || true)
if [[ "$VENV_VERSION" != "3.13" ]]; then
echo -e "${RED}Error: existing venv uses Python ${VENV_VERSION:-unknown}. Remove $VENV_PATH and re-run with Python 3.13.${NC}"
exit 1
fi
echo -e "${GREEN}✓ venv already exists at $VENV_PATH (Python $VENV_VERSION)${NC}"
else
PY="$(find_python_313 || true)"
if [[ -z "$PY" ]]; then
echo -e "${RED}Error: no python interpreter found on PATH. Install Python 3.13.${NC}"
echo -e "${RED}Error: no Python 3.13 interpreter found on PATH. Install Python 3.13.${NC}"
exit 1
fi
"$PY" -m venv "$VENV_PATH"
@ -126,7 +144,7 @@ echo ""
###############################################################################
echo -e "${BLUE}[4/4] Environment files${NC}"
for pair in "api/.env.example|api/.env" "ui/.env.example|ui/.env"; do
for pair in "api/.env.example|api/.env" "api/.env.test.example|api/.env.test" "ui/.env.example|ui/.env"; do
src="${pair%|*}"
dst="${pair#*|}"
if [[ -f "$dst" ]]; then

View file

@ -0,0 +1,13 @@
# Devcontainer contributor setup
`setup_local.sh` and `setup_local.ps1` provision the OSS Docker stack for local
deployments. They are not the recommended contributor workflow for this
repository.
For day-to-day development, use the checked-in devcontainer under
`.devcontainer/`. The full contributor instructions live in
`../docs/contribution/setup.mdx`.
The devcontainer flow pins Python 3.13, installs backend and frontend
dependencies in-container, creates a container-specific API env file, and
starts Postgres, Redis, and MinIO automatically.

View file

@ -25,24 +25,30 @@ if (-not $Dev) {
git submodule update --init --recursive
}
# Use uv (https://github.com/astral-sh/uv) for ~5-10x faster installs.
if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
Write-Host "Installing uv..."
Invoke-RestMethod https://astral.sh/uv/install.ps1 | Invoke-Expression
$env:Path = "$env:USERPROFILE\.local\bin;$env:Path"
}
# Install dograh API requirements first so pipecat's extras win on any
# shared transitive dependencies (matches api/Dockerfile and CI workflow).
Write-Host "Installing dograh API requirements..."
pip install -r api/requirements.txt
uv pip install -r api/requirements.txt
if ($Dev) {
Write-Host "Installing dograh API dev requirements..."
pip install -r api/requirements.dev.txt
uv pip install -r api/requirements.dev.txt
}
# Install pipecat in editable mode with all extras
Write-Host "Installing pipecat dependencies..."
pip install -e './pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb]'
uv pip install -e './pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb]'
if ($Dev) {
Write-Host "Installing pipecat dev dependencies..."
pip install --upgrade pip
pip install --group pipecat/pyproject.toml:dev
uv pip install --group pipecat/pyproject.toml:dev
}
Write-Host "Setup complete! Requirements are installed."

View file

@ -39,24 +39,32 @@ if [ "$DEV_MODE" -eq 0 ]; then
git submodule update --init --recursive
fi
# Use uv (https://github.com/astral-sh/uv) for ~5-10x faster installs.
# The devcontainer Dockerfile pre-installs uv; this fallback handles CI runners
# and contributor laptops that don't have it yet.
if ! command -v uv >/dev/null 2>&1; then
echo "Installing uv..."
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
fi
# Install dograh API requirements first so pipecat's extras win on any
# shared transitive dependencies (matches api/Dockerfile and CI workflow).
echo "Installing dograh API requirements..."
pip install -r api/requirements.txt
uv pip install -r api/requirements.txt
if [ "$DEV_MODE" -eq 1 ]; then
echo "Installing dograh API dev requirements..."
pip install -r api/requirements.dev.txt
uv pip install -r api/requirements.dev.txt
fi
# Install pipecat in editable mode with all extras
echo "Installing pipecat dependencies..."
pip install -e ./pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp]
uv pip install -e ./pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp]
if [ "$DEV_MODE" -eq 1 ]; then
echo "Installing pipecat dev dependencies..."
pip install --upgrade pip
pip install --group pipecat/pyproject.toml:dev
uv pip install --group pipecat/pyproject.toml:dev
fi
echo "Setup complete! Requirements are installed."

View file

@ -21,7 +21,7 @@ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$BaseDir = Split-Path -Parent $ScriptDir
Set-Location $BaseDir
$EnvFile = Join-Path $BaseDir 'api/.env'
$EnvFile = if ($env:DOGRAH_ENV_FILE) { $env:DOGRAH_ENV_FILE } else { Join-Path $BaseDir 'api/.env' }
$RunDir = Join-Path $BaseDir 'run'
$LogsRoot = Join-Path $BaseDir 'logs'
$LatestDir = Join-Path $LogsRoot 'latest'
@ -29,6 +29,7 @@ $VenvPath = Join-Path $BaseDir 'venv'
Write-Host "Starting Dograh Services (DEV MODE) in BASE_DIR: $BaseDir"
Write-Host "Auto-reload enabled for api/ directory changes"
Write-Host "Environment file: $EnvFile"
###############################################################################
### 1) Load environment variables

View file

@ -8,7 +8,7 @@ set -e # Exit on error
# Determine BASE_DIR as parent of the scripts directory
BASE_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)"
ENV_FILE="$BASE_DIR/api/.env"
ENV_FILE="${DOGRAH_ENV_FILE:-$BASE_DIR/api/.env}"
RUN_DIR="$BASE_DIR/run" # Where we keep *.pid
BASE_LOG_DIR="$BASE_DIR/logs" # Base logs directory
@ -26,6 +26,7 @@ HEALTH_INTERVAL=${HEALTH_INTERVAL:-2}
cd "$BASE_DIR"
echo "Starting Dograh Services (DEV MODE) at $(date) in BASE_DIR: ${BASE_DIR}"
echo "Auto-reload enabled for api/ directory changes"
echo "Environment file: $ENV_FILE"
###############################################################################
### 1) Load environment variables