Deploy Physics Agent Service with Docker Compose
Deploy the physics-agent-service and the bundled OVRTX rendering API locally using Docker Compose. The physics service is CPU-only; the rendering sidecar uses the GPU.
When to Use
- Use when the user wants to run
physics-agent-servicelocally with Docker Compose. - Use when the user needs the bundled OVRTX rendering sidecar for physics classification.
- Use when the user wants to configure VLM provider credentials or run local smoke requests with optimizer flags.
- Use
quickstartfor a shorter first local POC, and usedeploy-collectionwhen running multiple Content Agents together.
Limitations
- The default stack owns host ports 8000 and 8001. Stop overlapping Material, Physics, Texture, or standalone OVRTX stacks before startup.
- The main service waits on OVRTX readiness; OVRTX is ready only when
/healthreportsgpu_initialized: true. - First build and first render are long-running operations. Return logs and health commands rather than holding an agent session open indefinitely.
- Keep secrets out of chat and commits. Tell the user to edit
.env; do not ask them to paste keys.
Prerequisites
Check before deploying:
- Docker Compose v2.24+:
docker compose version-- required forenv_file: required: falselong-form syntax - NVIDIA GPU with ~16 GB+ VRAM:
nvidia-smi - NVIDIA Container Toolkit installed:
docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi - VLM provider API key (at least one): NVIDIA NIM, OpenAI, Anthropic, or Gemini
Instructions
- Confirm Docker, Compose, GPU, NVIDIA Container Toolkit, and port availability before starting the stack.
- Create or update the repo-root
.envwith exactly the VLM provider credentials the selected backend needs. - Start the Physics Agent compose stack from the repo root.
- Wait for both the main service and OVRTX readiness checks before reporting the service ready.
- For optimizer-sensitive smoke assets, use the optimizer form fields below.
- Return service URLs, health state, log commands, and stop commands using the output format below.
Set VLM API Key
Create .env at the repo root (the compose file reads it via env_file: ../../.env):
# Pick ONE provider:
echo 'NVIDIA_API_KEY=nvapi-...' > .env
# OR
echo 'OPENAI_API_KEY=sk-...' > .env
# OR
echo 'ANTHROPIC_API_KEY=sk-ant-...' > .env
# OR
echo 'GOOGLE_API_KEY=...' > .env
Start Services
docker compose -f apps/physics_agent_service/docker-compose.yml up --build
This starts:
- physics-agent-service on port 8000 (REST API)
- ovrtx-rendering-api on port 8001 (GPU rendering, built from source)
First build takes ~10 minutes. First render takes ~5 minutes (shader compilation; cached after).
Access
- Health: http://localhost:8000/health
- API Docs: http://localhost:8000/docs (Swagger UI)
- OpenAPI spec:
apps/physics_agent_service/openapi.yaml
Services
| Service | Port | GPU | Builds From | Always Starts |
|---|---|---|---|---|
| physics-agent-service | 8000 | No | Source | Yes |
| ovrtx-rendering-api | 8001 | 1x | Source | Yes |
The main service depends_on the rendering API's health check passing (which flips gpu_initialized to true). On cold start expect the physics-agent container to sit in "waiting" state for ~5 minutes before it comes up.
Operations
View Logs
# All services
docker compose -f apps/physics_agent_service/docker-compose.yml logs -f
# Specific service
docker logs physics-agent-service
docker logs physics-ovrtx-rendering-api
Stop
# Stop all services
docker compose -f apps/physics_agent_service/docker-compose.yml down
# Stop and remove session data
docker compose -f apps/physics_agent_service/docker-compose.yml down -v
Rebuild After Code Changes
docker compose -f apps/physics_agent_service/docker-compose.yml up --build
# Force full rebuild (no cache)
docker compose -f apps/physics_agent_service/docker-compose.yml build --no-cache
docker compose -f apps/physics_agent_service/docker-compose.yml up
Check Health
curl http://localhost:8000/health # main service
python - <<'PY'
import json
from urllib.request import urlopen
try:
with urlopen("http://localhost:8001/health", timeout=10) as response:
health = json.load(response)
except Exception as exc:
print(f"rendering API unreachable: {exc}")
raise SystemExit(1)
print(json.dumps(health))
if health.get("status") == "unhealthy":
print("rendering API unhealthy")
raise SystemExit(1)
if health.get("gpu_initialized") is True:
print("rendering API ready")
else:
print("rendering API warming")
PY
REST Smoke with Optimizer Flags
For ordinary smoke assets, run the pipeline without optimizer flags. For
instanced USDs, assets that fail apply_physics with an instance-proxy authoring
error, or one combined mesh that needs split-by-component predictions, pass the
optimizer form fields to POST /pipeline:
# Instance-proxy authoring fix.
curl -X POST "http://localhost:8000/pipeline" \
-F "usd_file=@scene.usd" \
-F "optimize_usd=true" \
-F "enable_deinstance=true"
# Also split one combined disjoint mesh into separate component predictions.
curl -X POST "http://localhost:8000/pipeline" \
-F "usd_file=@scene.usd" \
-F "optimize_usd=true" \
-F "enable_deinstance=true" \
-F "enable_split=true"
Use enable_deduplicate=true only when repeated identical geometry should be
collapsed. At least one optimizer operation must be enabled when
optimize_usd=true.
Resource Requirements
| Configuration | GPUs | CPU | Memory |
|---|---|---|---|
| Default (main + rendering) | 1 | 10 | 20 G |
Environment Variables
Configurable via .env at the repo root. Key settings:
| Variable | Default | Description |
|---|---|---|
NVIDIA_API_KEY |
NVIDIA (build.nvidia.com) VLM provider | |
OPENAI_API_KEY |
OpenAI VLM provider | |
ANTHROPIC_API_KEY |
Anthropic VLM provider | |
GOOGLE_API_KEY |
Google Gemini VLM provider | |
PA_VLM_BACKEND |
nim |
Which VLM backend to use |
PA_VLM_MODEL |
qwen/qwen3.5-397b-a17b |
Model id for the selected backend |
PA_VLM_TEMPERATURE |
1.0 |
Sampling temperature |
PA_MAX_ACTIVE_SESSIONS |
1 |
Max concurrent pipelines |
PA_SESSION_TTL_HOURS |
24 |
Session expiry time |
PA_MAX_UPLOAD_SIZE_MB |
500 |
Max USD upload size |
WU_NVCF_GLOBAL_MAX_CONCURRENT_REQUESTS |
1 |
Process-wide outbound render request cap for the local OVRTX sidecar |
OVRTX_NUM_SENSOR_UPDATES |
500 |
Sensor update count before capture (rendering sidecar) |
GPU Configuration
To assign specific GPUs to the rendering API, edit
apps/physics_agent_service/docker-compose.yml:
ovrtx-rendering-api:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1 # number of GPUs
capabilities: [gpu]
Or pin a specific GPU ID:
device_ids: ['0']
Output Format
When handing control back to the user, report:
SERVICE_URL:http://localhost:8000DOCS_URL:http://localhost:8000/docsSERVICE_HEALTH:healthy,starting, orunhealthyOVRTX_HEALTH:healthyonly when/healthcontains"gpu_initialized":true; otherwisewarmingorunhealthyLOGS:docker compose -f apps/physics_agent_service/docker-compose.yml logs -fSTOP:docker compose -f apps/physics_agent_service/docker-compose.yml down- Any missing credentials, port conflicts, GPU/toolkit blockers, or optimizer flags used for smoke validation.
Troubleshooting
OVRTX rendering API not starting
Check GPU access:
docker logs physics-ovrtx-rendering-api
docker exec physics-ovrtx-rendering-api nvidia-smi
Shader compilation on first boot takes ~5 minutes; wait it out.
Main service unhealthy before rendering API ready
The main service depends_on the rendering API's health check. If rendering takes long to start, the physics-agent-service container will stay in "waiting" state. Check docker compose ps to see which container is blocking.
503 / VLM failures under load
PA_MAX_ACTIVE_SESSIONS defaults to 1 because rendering plus a VLM call per prim is the main throughput bottleneck. Raising this requires headroom on both CPU memory and VLM provider quota.
Source: NVIDIA-Omniverse/content-agents — distributed by TomeVault.