Docker Build System#
The AI-Q blueprint uses a multi-stage Dockerfile (deploy/Dockerfile) that produces two build targets: a development image with the CLI and a lean release image for production.
Multi-Stage Architecture#
The build consists of four stages:
Stage |
Base image |
Purpose |
|---|---|---|
|
|
Installs Python 3.12, system dependencies, and all application packages. |
|
|
Extends builder with the CLI and debug UI packages. |
|
|
Development runtime – copies from |
|
|
Production runtime – copies from |
Builder Stage#
The builder stage handles all compilation and package installation:
System dependencies – Installs build tools, curl, git, and Python 3.12 from the
deadsnakesPPA.Virtual environment – Creates a venv at
/app/.venvusinguv.Dependency installation – Runs
uv sync --frozen --no-dev --no-install-workspaceto install locked dependencies.Workspace packages – Installs application packages with
uv pip install -e(the root package uses--no-deps):Root workspace package (
aiq-agent) usinguv pip install --no-deps -e .sources/google_scholar_paper_search– Google Scholar searchsources/tavily_web_search– Tavily web searchsources/knowledge_layer[all]– Knowledge layer with all extrasfrontends/aiq_api– FastAPI frontendpsycopg[binary]>=3.0.0– PostgreSQL driver (psycopg v3, installed non-editable)
File setup – Makes startup scripts executable, creates
/app/data, and sets ownership to UID 1000.
Only runtime scripts (deploy/entrypoint.py and deploy/start_web.py) are copied from the deploy/ directory. The full deploy/ directory is excluded to avoid leaking .env files, Helm charts, compose files, or other development artifacts into the image.
Dev Stage#
The development image extends the builder with additional packages:
frontends/cli– Command-line interface (natCLI commands)frontends/debug– Debug UI for local development
Build#
docker build --target dev -t aiq:dev -f deploy/Dockerfile .
What Is Included#
All application packages plus CLI and debug UI.
Python 3.12 runtime from the NVIDIA distroless base image.
Startup scripts (
entrypoint.py,start_web.py).Runs as non-root user (UID 1000).
Release Stage#
The release image is built from the base builder stage (no CLI or debug packages):
Build#
docker build --target release -t aiq:prod -f deploy/Dockerfile .
What Is Included#
Application packages only (no CLI, no debug UI).
APP_ENV=productionset by default.Same non-root user and distroless base as the dev image.
Build Commands Summary#
Target |
Command |
Use case |
|---|---|---|
|
|
Local development, testing, CLI access |
|
|
Production deployment, CI/CD |
When using Docker Compose, the build target is controlled by the BUILD_TARGET variable:
# Dev build (default)
cd deploy/compose
docker compose --env-file ../.env -f docker-compose.yaml up -d --build
# Release build
BUILD_TARGET=release docker compose --env-file ../.env -f docker-compose.yaml up -d --build
Base Images#
Image |
Used in |
Purpose |
|---|---|---|
|
Builder stages |
Full Ubuntu with package managers for compilation. |
|
Runtime stages ( |
Minimal NVIDIA distroless image with Python 3.12. No shell, no package manager – reduces attack surface. |
Startup Scripts#
The container entrypoint is python /app/deploy/entrypoint.py, which orchestrates the full startup sequence. There are two scripts involved:
entrypoint.py – Dask cluster launcher#
entrypoint.py is the Docker ENTRYPOINT. It performs the following:
Argument pass-through – If command-line arguments are provided, it
execs them directly (useful for running one-off commands in the container).Dask scheduler – Starts a
dask-schedulerprocess on the configured port (default8786) with a dashboard on port8787.Wait for scheduler – Polls the scheduler with a Dask
Clientfor up to 30 attempts (1 second apart).Dask worker – Starts a
dask-workerprocess connected to the scheduler.Environment variable – Sets
NAT_DASK_SCHEDULER_ADDRESSso the web server can submit background jobs.Web server – Launches
start_web.pyas a subprocess.Signal handling – Installs SIGTERM/SIGINT handlers that gracefully shut down all three processes (web, worker, scheduler).
Environment variables:
Variable |
Default |
Description |
|---|---|---|
|
|
Path to the NeMo Agent Toolkit workflow config. |
|
|
Bind address for the web server. |
|
|
Bind port for the web server. |
|
|
Dask scheduler port. |
|
|
Number of Dask workers. |
|
|
Threads per Dask worker. |
start_web.py – FastAPI server (direct uvicorn)#
start_web.py bypasses the standard nat serve command to avoid an asyncio event loop conflict between the NeMo Agent Toolkit runtime and FastAPI/Starlette’s anyio event loop management.
It performs the following:
Configure logging – Sets up structured logging matching
nat servebehavior.Load config – Validates the NeMo Agent Toolkit YAML config using
nat.runtime.loader.load_config().Set environment – Writes
NAT_CONFIG_FILEandNAT_FRONT_END_WORKERso NeMo Agent Toolkit’s FastAPI app can find the config and worker class.Run uvicorn – Starts uvicorn directly with
loop="asyncio", letting uvicorn create and manage its own event loop.
Environment variables:
Variable |
Default |
Description |
|---|---|---|
|
|
Path to the NeMo Agent Toolkit workflow config. |
|
|
Bind address. |
|
|
Bind port. |
|
|
Logging verbosity. |