This guide covers two ways to get the DNA application stack running locally: the automated bootstrap script (recommended) and step-by-step manual setup.
The bootstrap.sh script handles all setup steps for you. Run it from the repo root:
./bootstrap.shIt will:
- Check that Docker and Node.js v18+ are installed and that the Docker daemon is running
- Copy example config files into their working locations
- Prompt you to choose an LLM provider (OpenAI or Gemini) and enter your API key
- Prompt you to configure the transcription service (remote via vexa.ai, self-hosted, or skip)
- Install frontend npm dependencies
- Start the Vexa services, create a local dev user, and generate a Vexa API key automatically
- Start the full DNA stack with Docker Compose
- Poll the DNA API until it is ready and confirm all services are up
After the script finishes, start the frontend in a new terminal:
cd frontend && npm run devThe app will be available at http://localhost:5173.
Day-to-day use: once you have run the bootstrap once, use --start to bring the stack up without repeating the interactive setup:
./bootstrap.sh --startFollow these steps if you prefer to set up each component yourself, or if you need to understand what the bootstrap script does under the hood.
- Docker and Docker Compose installed and the Docker daemon running
- Node.js (v18+) and npm for the frontend
- Python 3.11+ (optional, for running tests outside Docker)
git clone <repository-url>
cd dnaCopy all three example config files into their working locations. The bootstrap script backs up any existing files before overwriting; do the same if you are re-running setup.
cd backend
cp example.docker-compose.local.yml docker-compose.local.yml
cp example.docker-compose.local.vexa.yml docker-compose.local.vexa.yml
cd ../frontend
cp packages/app/.env.example packages/app/.envEdit backend/docker-compose.local.yml and set your LLM credentials. The bootstrap script writes these values for you when you provide a key interactively.
OpenAI (default): requires OPENAI_API_KEY; optional OPENAI_MODEL and OPENAI_TIMEOUT
services:
api:
environment:
- LLM_PROVIDER=openai
- OPENAI_API_KEY=your-openai-api-key
- OPENAI_MODEL=gpt-4o-miniGemini: requires GEMINI_API_KEY; also set LLM_PROVIDER=gemini
services:
api:
environment:
- LLM_PROVIDER=gemini
- GEMINI_API_KEY=your-gemini-api-key
- GEMINI_MODEL=gemini-2.5-flash
- GEMINI_URL=https://generativelanguage.googleapis.com/v1beta/openai/Vexa requires an OpenAI Whisper-compatible transcription backend. Edit backend/docker-compose.local.vexa.yml for whichever option you choose.
Option 1 — Remote via vexa.ai (recommended, free tier available):
Get a free key at https://staging.vexa.ai/dashboard/transcription, then set:
services:
vexa:
environment:
- TRANSCRIBER_API_KEY=your-transcription-api-key
- TRANSCRIBER_URL=https://transcription.vexa.ai/v1/audio/transcriptionsOption 2 — Self-hosted transcription service:
git clone https://github.com/Vexa-ai/vexa.git
cd vexa/services/transcription-service
cp .env.example .env
# Edit .env: set API_TOKEN and optionally DEVICE=cpu for no GPU
docker compose up -d
# Wait for "Model loaded successfully" in: docker logs <container>Then in backend/docker-compose.local.vexa.yml:
services:
vexa:
environment:
- TRANSCRIBER_URL=http://localhost:8083/v1/audio/transcriptions
- TRANSCRIBER_API_KEY=your-api-token-valueOption 3 — Skip transcription for now:
Add SKIP_TRANSCRIPTION_CHECK=true to backend/docker-compose.local.vexa.yml so Vexa starts without a working transcription backend:
services:
vexa:
environment:
- SKIP_TRANSCRIPTION_CHECK=trueYou can enable transcription later by removing that line and adding your TRANSCRIBER_API_KEY, then restarting with cd backend && make restart-local.
cd frontend
npm installThe bootstrap script automates this via the Vexa admin API. To do it manually:
a. Start only the Vexa services:
cd backend
docker compose -f docker-compose.vexa.yml -f docker-compose.local.vexa.yml up -d vexa vexa-dbb. Wait for the Vexa admin API to become ready (may take ~30 s on first pull):
until curl -sf -H "X-Admin-API-Key: your-admin-token" \
http://localhost:8056/admin/users -o /dev/null; do
echo "Waiting for Vexa..."; sleep 3
donec. Create a local dev user:
curl -s -X POST \
-H "X-Admin-API-Key: your-admin-token" \
-H "Content-Type: application/json" \
-d '{"email":"dna-local@example.com","name":"DNA Local Dev"}' \
http://localhost:8056/admin/usersNote the id field from the response.
d. Generate an API token for that user:
curl -s -X POST \
-H "X-Admin-API-Key: your-admin-token" \
http://localhost:8056/admin/users/<user-id>/tokensNote the token field from the response.
e. Write the token into your local compose file:
In backend/docker-compose.local.yml, set:
- VEXA_API_KEY=<token-from-previous-step>Alternatively, you can retrieve a key from the Vexa Dashboard UI at http://localhost:3001 once the stack is running.
cd backend
make start-localThis runs:
docker compose \
-f docker-compose.yml \
-f docker-compose.vexa.yml \
-f docker-compose.debug.yml \
-f docker-compose.local.yml \
-f docker-compose.local.vexa.yml \
up --build -dServices started:
- MongoDB — database (port 27017)
- DNA API — FastAPI backend (port 8000)
- Vexa — transcription service (port 8056)
- Vexa Dashboard — admin UI (port 3001)
In a new terminal:
cd frontend
npm run devThe React app will be available at http://localhost:5173.
| Service | URL | Description |
|---|---|---|
| DNA API | http://localhost:8000 | Backend API |
| API Docs | http://localhost:8000/docs | Swagger UI |
| Vexa Dashboard | http://localhost:3001 | Transcription admin |
| Frontend | http://localhost:5173 | React application |
| Variable | Required | Default | Description |
|---|---|---|---|
SHOTGRID_URL |
Yes* | - | ShotGrid site URL (required when using ShotGrid) |
SHOTGRID_API_KEY |
Yes* | - | ShotGrid API key (required when using ShotGrid) |
SHOTGRID_SCRIPT_NAME |
Yes* | - | ShotGrid script name (required when using ShotGrid) |
PRODTRACK_PROVIDER |
No | shotgrid |
shotgrid or mock; set to mock to use the read-only mock DB without ShotGrid |
MONGODB_URL |
No | mongodb://mongo:27017 |
MongoDB connection string |
STORAGE_PROVIDER |
No | mongodb |
Storage provider type |
VEXA_API_KEY |
Yes | - | API key for Vexa transcription service |
VEXA_API_URL |
No | http://vexa:8056 |
Vexa REST API URL |
LLM_PROVIDER |
No | openai |
LLM provider (openai or gemini) |
OPENAI_API_KEY |
Yes* | - | OpenAI API key when LLM_PROVIDER=openai |
OPENAI_MODEL |
No | gpt-4o-mini |
OpenAI model to use when LLM_PROVIDER=openai |
OPENAI_TIMEOUT |
No | 30.0 |
Request timeout in seconds when LLM_PROVIDER=openai |
GEMINI_API_KEY |
Yes* | - | Gemini API key when LLM_PROVIDER=gemini |
GEMINI_MODEL |
No | gemini-2.5-flash |
Gemini model to use when LLM_PROVIDER=gemini |
GEMINI_TIMEOUT |
No | 30.0 |
Request timeout in seconds when LLM_PROVIDER=gemini |
GEMINI_URL |
No | https://generativelanguage.googleapis.com/v1beta/openai/ |
Override the Gemini OpenAI-compatible base URL |
DNA_ENABLE_TRANSCRIPT_PUBLISH |
No | false |
Set to true to enable POST /playlists/{id}/publish-transcript. When off, the endpoint returns 404. |
SHOTGRID_TRANSCRIPT_ENTITY |
No | CustomEntity01 |
ShotGrid custom entity slot used when publishing transcripts. Match whichever CustomEntityNN the site admin has enabled. |
PYTHONUNBUFFERED |
No | 1 |
Disable Python output buffering |
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
No | postgresql://vexa:vexa@vexa-db:5432/vexa |
PostgreSQL connection for Vexa |
ADMIN_API_TOKEN |
No | your-admin-token |
Admin token for Vexa management |
TRANSCRIBER_URL |
No | (vexa.ai) | Transcription API endpoint |
TRANSCRIBER_API_KEY |
Yes | - | API key for transcription service |
SKIP_TRANSCRIPTION_CHECK |
No | - | Set to true to start Vexa without a working transcription backend |
cd backend
# Start the stack
make start-local
# Stop the stack
make stop-local
# Restart everything
make restart-local
# View logs
make logs-local
# Run tests
make test
# Run tests with coverage
make test-cov
# Format Python code
make format-python
# Open a shell in the API container
make shell
# Seed mock DB from a ShotGrid project (requires SHOTGRID_* credentials)
SHOTGRID_API_KEY='your-key' make seed-mock-dbcd frontend
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run build
# Run tests
npm run test
# Run tests with coverage
npm run test:coverage
# Format code
npm run format
# Type check
npm run typecheck┌─────────────────────────────────────────────────────────────────────────────┐
│ DNA Stack │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌───────────────┐ │
│ │ Frontend │◀──────▶│ DNA API │───────▶│ ShotGrid │ │
│ │ (React/Vite) │ WS │ (FastAPI) │ │ (external) │ │
│ │ :5173 │ │ :8000 │ │ │ │
│ └─────────────────┘ └────────┬────────┘ └───────────────┘ │
│ │ │
│ ┌─────────────────────────────┴─────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────┐ │
│ │ MongoDB │ │ Vexa │ │
│ │ :27017 │ │ :8056 │ │
│ └─────────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The DNA API serves as the central hub:
- Provides REST API for CRUD operations
- Provides WebSocket endpoint (
/ws) for real-time event streaming - Manages Vexa subscriptions for transcription events
- Broadcasts segment and bot status events to connected frontend clients
When you set PRODTRACK_PROVIDER=mock, the backend uses a read-only mock provider backed by SQLite (backend/src/dna/prodtrack_providers/mock_data/mock.db). The app runs normally with this data so you can develop and test the UI without a ShotGrid seat.
Production tracking (ShotGrid): To run without a ShotGrid seat, set PRODTRACK_PROVIDER=mock in docker-compose.local.yml. The mock provider uses read-only SQLite with pre-seeded data. To use real ShotGrid, set PRODTRACK_PROVIDER=shotgrid (or leave it unset) and add SHOTGRID_URL, SHOTGRID_SCRIPT_NAME, and SHOTGRID_API_KEY.
- In
docker-compose.local.yml, setPRODTRACK_PROVIDER=mock. You do not need to set any ShotGrid variables when using the mock. - The mock provider is used only when explicitly set; there is no automatic fallback if ShotGrid credentials are missing.
If you have ShotGrid access, you can populate the mock database from a real project so the mock data matches your pipeline. Run the seed script with a project ID, URL, script name, and API key:
cd backend
# From your host (requires shotgun_api3); use single quotes so the API key is not interpreted by the shell
SHOTGRID_API_KEY='your-api-key' make seed-mock-db
# Or run the seed script directly in the API container with custom project
docker compose -f docker-compose.yml -f docker-compose.local.yml run --rm api \
python -m dna.prodtrack_providers.mock_data.seed_db \
--project-id YOUR_PROJECT_ID \
--url https://yoursite.shotgrid.autodesk.com \
--script-name YourScript \
--api-key 'YOUR_API_KEY'- This overwrites
mock_data/mock.dbwith entities (projects, users, shots, assets, tasks, versions, playlists, notes) from the given ShotGrid project. - Use
--skip-thumbnailsto skip downloading version thumbnails (faster seed; thumbnails will not work after signed URLs expire). - Without
--skip-thumbnails, thumbnails are downloaded tomock_data/thumbnails/and served by the API at/api/mock-thumbnails/{version_id}so they keep working after ShotGrid signed URLs expire.
The mock provider is read-only: it does not write to ShotGrid or to the SQLite file at runtime. Writes such as publishing notes will raise an error when using the mock provider.
The backend uses multiple compose files that are layered together:
| File | Purpose |
|---|---|
docker-compose.yml |
Base configuration with all services |
docker-compose.vexa.yml |
Vexa transcription service |
docker-compose.debug.yml |
Additional debug services |
docker-compose.local.yml |
Your local overrides (API keys, LLM credentials) |
docker-compose.local.vexa.yml |
Your local Vexa overrides (transcription API key) |
The make start-local command combines these:
docker compose -f docker-compose.yml \
-f docker-compose.vexa.yml \
-f docker-compose.debug.yml \
-f docker-compose.local.yml \
-f docker-compose.local.vexa.yml \
up --build -d# Connect via mongosh
docker exec -it dna-mongo mongosh dna
# Example queries
db.playlist_metadata.find()
db.segments.find()
db.draft_notes.find()Interactive API documentation is available at:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- Backend API: Automatically reloads when you modify files in
src/ - Frontend: Vite provides instant hot module replacement
cd backend
# Run all tests in Docker
make test
# Run specific test file
docker compose -f docker-compose.yml -f docker-compose.local.yml \
run --rm api python -m pytest tests/test_transcription_service.py -vcd frontend
# Run tests in watch mode
npm run test
# Run tests once
npm run test:run
# Run tests with coverage
npm run test:coverage# Check logs
docker logs dna-backend
# Rebuild containers
make build
make start-local-
Check if MongoDB is running:
docker logs dna-mongo
-
Verify the database exists:
docker exec dna-mongo mongosh --eval "show dbs"
- Ensure
frontend/packages/app/.envexists — if not, copy it from.env.example - Ensure the API is running: http://localhost:8000/health
- Check for CORS issues in browser console
- Check browser console for WebSocket errors
- Ensure the API is running and healthy
- The frontend connects to
ws://localhost:8000/wsby default
# Stop all containers
cd backend
make stop-local
# Remove volumes (clean slate)
docker compose -f docker-compose.yml -f docker-compose.local.yml down -v