Docker Image Configuration#
Phoenix's Docker image uses a three-stage multi-stage build targeting a minimal distroless runtime. Because distroless images contain no shell or package manager, all Python dependencies must be bundled in from the builder stage and made available via PYTHONPATH.
The Dockerfile lives at the repository root and is the single canonical image definition.
Build Stages#
| Stage | Base Image | Purpose |
|---|---|---|
frontend-builder | node:22-slim | Builds the React frontend via pnpm |
backend-builder | python:3.11-bullseye | Installs Python deps; compiles no-shell-safe layout |
| Production | gcr.io/distroless/python3-debian12:nonroot | Minimal runtime; no shell, no package manager |
frontend-builder installs and builds the frontend with pnpm, outputting static assets that the backend stage copies into src/phoenix/server/static/ .
backend-builder performs two important pre-install steps before running pip :
- Copies the built frontend static assets from the previous stage.
- Deletes any symbolic links under
src/(find src/ -xtype l -delete) — symlinks used during local development would break inside the sealed production image.
Production image defaults to gcr.io/distroless/python3-debian12:nonroot . An arm64 variant (nonroot-arm64) is available as a commented-out ARG for Raspberry Pi and Apple Silicon deployments . To build a debug image with a shell (useful for container inspection), replace the tag with the :debug variant per the distroless debug image docs .
pip install --target and PYTHONPATH Resolution#
The core dependency packaging strategy is:
RUN pip install --target ./env ".[container, pg]"
--target ./env installs everything (Phoenix + all transitive dependencies) flat into /phoenix/env inside the builder container — no virtual environment, no site-packages. The entire directory is then copied into the production image:
COPY --from=backend-builder /phoenix/env/ ./env
ENV PYTHONPATH="/phoenix/env:$PYTHONPATH"
Prepending /phoenix/env to PYTHONPATH is the only mechanism that makes packages importable at runtime. Because the distroless image has no pip, no site-packages, and no shell, PYTHONPATH injection replaces everything a normal Python installation would provide.
Binary / console_scripts resolution#
pip install --target does not install console_scripts entry points into a bin/ directory the way a normal install does. Phoenix works around this by never relying on a named binary at runtime: the distroless base image's ENTRYPOINT invokes Python directly, and the CMD passes module flags :
CMD ["-m", "phoenix.server.main", "serve"]
The distroless ENTRYPOINT is defined in the base image itself — see the distroless Python BUILD file referenced in the Dockerfile comment . This means no binary PATH lookups are needed.
Transitive dependency resolution#
pip install ".[container, pg]" resolves the full transitive closure of dependencies at build time using the version constraints in pyproject.toml. There is no lock file in the current pip-based workflow; each Docker build resolves the latest versions satisfying the constraints. For reproducibility, version pins exist for specific packages (e.g., strawberry-graphql[opentelemetry]==0.270.1) .
Upcoming change: PR #11260 replaces
pip install --targetwithuv syncdriven by auv.lockfile, switches the runtime directory from./envto.venv, and updatesPYTHONPATHto point at.venv/lib/python3.11/site-packages. This brings fully reproducible, locked builds but has not yet landed on the pinned source snapshot.
container and pg Extras#
The Docker image installs Phoenix with two optional extras defined in pyproject.toml:
pg — PostgreSQL support :
asyncpgpsycopg[binary,pool]
container — everything needed for a fully-featured production deployment :
- LLM provider SDKs:
anthropic>=0.49.0,openai>=1.0.0,google-generativeai,azure-identity,aiohttp(azure-identity transport) - OpenTelemetry stack:
opentelemetry-sdk,opentelemetry-proto>=1.12.0,opentelemetry-exporter-otlp,opentelemetry-semantic-conventions,opentelemetry-instrumentation-fastapi,opentelemetry-instrumentation-sqlalchemy,opentelemetry-instrumentation-grpc - Observability:
prometheus-client,py-grpc-prometheus,strawberry-graphql[opentelemetry]==0.270.1 - Embeddings / UMAP:
fast-hdbscan>=0.2.0,numba>=0.60.0,umap-learn - Cloud storage:
boto3 - Performance:
uvloop(non-Windows only)
The numba version floor in pyproject.toml includes a comment linking to a known uv resolution bug ; the pin is defensive even though the current Docker build uses pip.