diff --git a/api/Dockerfile b/api/Dockerfile
index 6e8831a..bcda1a6 100644
--- a/api/Dockerfile
+++ b/api/Dockerfile
@@ -85,7 +85,7 @@ ENV PYTHONUNBUFFERED=1
# Copy application code
COPY ./api ./api
-COPY ./scripts/start_services_dev.sh ./scripts/start_services_dev.sh
+COPY ./scripts/start_services_docker.sh ./scripts/start_services_docker.sh
# ts_validator Node deps (built in ts-deps stage with full node:22-slim image).
# The validator runs as a short-lived subprocess from api/mcp_server/ts_bridge.py.
@@ -104,4 +104,4 @@ ENV LOG_TO_FILE=false
EXPOSE 8000
# Run the FastAPI app with uvicorn
-CMD ["./scripts/start_services_dev.sh"]
\ No newline at end of file
+CMD ["./scripts/start_services_docker.sh"]
\ No newline at end of file
diff --git a/docs/contribution/setup.mdx b/docs/contribution/setup.mdx
index 6dc95d0..a2629ab 100644
--- a/docs/contribution/setup.mdx
+++ b/docs/contribution/setup.mdx
@@ -109,6 +109,21 @@ tail -f logs/latest/*.log
Get-Content logs/latest/*.log -Wait
```
+
+#### 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 macOS/Linux
+bash scripts/start_services_dev.sh
+```
+```powershell Windows
+.\scripts\start_services_dev.ps1
+```
+
+
+
+`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.
+
10. Start the UI
```
cd ui && npm run dev
diff --git a/scripts/start_services_dev.ps1 b/scripts/start_services_dev.ps1
index a9470c4..47e74b6 100644
--- a/scripts/start_services_dev.ps1
+++ b/scripts/start_services_dev.ps1
@@ -1,13 +1,12 @@
#!/usr/bin/env pwsh
# Start Dograh services in development mode (Windows)
-# Usage: .\scripts\start_services_dev.ps1 [-ArqWorkers 2] [-NoMigrations] [-IncludeTelephonyWorkers]
+# Usage: .\scripts\start_services_dev.ps1 [-NoMigrations] [-IncludeTelephonyWorkers]
#
# Note: Telephony workers (ari_manager, campaign_orchestrator) are disabled by
# default on Windows because they use Unix signal handlers not supported by the
# Windows asyncio event loop.
Param(
- [int]$ArqWorkers = 1,
[switch]$NoMigrations,
[switch]$IncludeTelephonyWorkers
)
@@ -61,10 +60,7 @@ if ($IncludeTelephonyWorkers) {
}
$serviceSpecs += @{ Name = 'uvicorn'; Cmd = "uvicorn api.app:app --host 0.0.0.0 --port $($env:UVICORN_BASE_PORT) --reload --reload-dir api" }
-
-for ($i = 1; $i -le $ArqWorkers; $i++) {
- $serviceSpecs += @{ Name = "arq$i"; Cmd = "python -m arq api.tasks.arq.WorkerSettings --custom-log-dict api.tasks.arq.LOG_CONFIG" }
-}
+$serviceSpecs += @{ Name = 'arq'; Cmd = "python -m arq api.tasks.arq.WorkerSettings --custom-log-dict api.tasks.arq.LOG_CONFIG" }
###############################################################################
### 3) Activate virtual environment
diff --git a/scripts/start_services_dev.sh b/scripts/start_services_dev.sh
index 91fdf73..23458aa 100755
--- a/scripts/start_services_dev.sh
+++ b/scripts/start_services_dev.sh
@@ -17,7 +17,6 @@ LOG_DIR="$BASE_LOG_DIR/$TIMESTAMP" # Timestamped log directory
LATEST_LINK="$BASE_LOG_DIR/latest" # Symlink to latest logs
VENV_PATH="$BASE_DIR/venv"
-ARQ_WORKERS=${ARQ_WORKERS:-1}
LOG_TO_FILE=${LOG_TO_FILE:-true}
cd "$BASE_DIR"
@@ -42,20 +41,16 @@ SERVICE_NAMES=(
"ari_manager"
"campaign_orchestrator"
"uvicorn"
+ "arq"
)
SERVICE_COMMANDS=(
"python -m api.services.telephony.ari_manager"
"python -m api.services.campaign.campaign_orchestrator"
"uvicorn api.app:app --host 0.0.0.0 --port $UVICORN_BASE_PORT --reload --reload-dir api"
+ "python -m arq api.tasks.arq.WorkerSettings --custom-log-dict api.tasks.arq.LOG_CONFIG"
)
-# Add ARQ workers dynamically
-for ((i=1; i<=ARQ_WORKERS; i++)); do
- SERVICE_NAMES+=("arq$i")
- SERVICE_COMMANDS+=("python -m arq api.tasks.arq.WorkerSettings --custom-log-dict api.tasks.arq.LOG_CONFIG")
-done
-
###############################################################################
### 3) Activate virtual environment
###############################################################################
@@ -217,6 +212,3 @@ echo "Logs: tail -f $LOG_DIR/*.log"
echo "Rotated logs: ls $LOG_DIR/*.log.*"
echo "To stop: ./scripts/stop_services.sh"
echo "──────────────────────────────────────────────────"
-
-# Keep the script alive (required for Docker - PID 1 must not exit)
-wait
diff --git a/scripts/start_services_docker.sh b/scripts/start_services_docker.sh
new file mode 100755
index 0000000..48965c9
--- /dev/null
+++ b/scripts/start_services_docker.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+set -e
+
+###############################################################################
+### CONFIGURATION
+###############################################################################
+
+BASE_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)"
+ENV_FILE="$BASE_DIR/api/.env"
+
+ARQ_WORKERS=${ARQ_WORKERS:-1}
+FASTAPI_WORKERS=${FASTAPI_WORKERS:-1}
+UVICORN_BASE_PORT=${UVICORN_BASE_PORT:-8000}
+
+cd "$BASE_DIR"
+echo "Starting Dograh Services (DOCKER) at $(date) in BASE_DIR: ${BASE_DIR}"
+
+###############################################################################
+### 1) Load env file if mounted (env normally comes from docker-compose)
+###############################################################################
+
+if [[ -f "$ENV_FILE" ]]; then
+ set -a && . "$ENV_FILE" && set +a
+fi
+
+###############################################################################
+### 2) Run migrations
+###############################################################################
+
+alembic -c "$BASE_DIR/api/alembic.ini" upgrade head
+
+###############################################################################
+### 3) Signal handling — forward TERM/INT to children for clean docker stop
+###############################################################################
+
+pids=()
+
+shutdown() {
+ echo "Received shutdown signal, stopping services..."
+ for pid in "${pids[@]}"; do
+ kill -TERM "$pid" 2>/dev/null || true
+ done
+ wait
+ exit 0
+}
+
+trap shutdown TERM INT
+
+start() {
+ local name=$1
+ shift
+ echo "→ Starting $name"
+ "$@" &
+ pids+=($!)
+ echo " $name PID $!"
+}
+
+###############################################################################
+### 4) Start services (logs go to stdout for `docker logs`)
+###############################################################################
+
+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"
+
+for ((i=1; i<=ARQ_WORKERS; i++)); do
+ start "arq$i" python -m arq api.tasks.arq.WorkerSettings --custom-log-dict api.tasks.arq.LOG_CONFIG
+done
+
+###############################################################################
+### 5) Wait — if any service exits, tear the container down so docker restarts
+###############################################################################
+
+wait -n
+echo "A service exited; tearing down container."
+shutdown