Install Arize Phoenix with Docker for Self-Hosted LLM and AI Agent Observability

Updated on Aug 24, 2026
Mila H
8 MINS READ
Table of Contents
self-host Arize Phoenix with Docker

If you want to understand what your LLM agents are doing, you need tracing set up before things break in production. This guide shows you how to self-host Arize Phoenix with Docker.

By the end, you'll have a persistent, HTTPS-secured Phoenix instance running on your own Linux VPS. It captures real traces from a Python agent, retrieval steps, tool calls, and LLM spans, not just a single model call.

What Is Arize Phoenix

Arize Phoenix is an open-source tool for monitoring and evaluating AI systems. Built on OpenTelemetry and OpenInference, it lets you trace LLM calls, RAG retrieval, and agent tool calls, all in one dashboard. Teams self-host Phoenix with Docker instead of using the managed cloud version, mainly to keep prompts, embeddings, and customer data inside their own infrastructure.

Running your own observability stack has real benefits:

  • No vendor lock-in, sensitive data stays off third-party servers.
  • You control retention and storage costs.

A dedicated Linux VPS with enough RAM and disk gives you the isolation this needs, since tracing volume can spike fast during heavy agent testing.

Prerequisites to Self-Host Arize Phoenix with Docker

Anyone planning to self-host Arize Phoenix with Docker in production should confirm these requirements first:

  • A Linux VPS running Ubuntu 24.04 with at least 2 vCPU, 4GB RAM, and 40GB SSD storage.
  • Docker Engine and Docker Compose v2 are installed and running.
  • A domain name pointed at your VPS if you plan to expose Phoenix over HTTPS.
  • Root or sudo access to configure firewall rules and the reverse proxy.
  • An OpenAI or other LLM provider API key for the demo agent will be used later.

Step 1. Prepare the Environment

Update your packages and confirm Docker is installed on your server:

Bash
sudo apt update && sudo apt upgrade -ydocker infodocker compose version

Create a dedicated working directory to keep this deployment isolated from other services:

Bash
mkdir -p ~/phoenix-stack/data && cd ~/phoenix-stack

Step 2. Create Persistent Storage and the Compose File

Persistent storage is essential if you want to self-host Arize Phoenix with Docker without losing traces on every restart. This setup uses PostgreSQL for the trace database and a named volume for working files.

First, create two different 32-character secrets for Phoenix with:

Bash
openssl rand -hex 32

Create a .env file to hold secrets outside version control:

Bash
cat > .env << 'EOF'POSTGRES_USER=phoenixPOSTGRES_PASSWORD=change_this_passwordPOSTGRES_DB=phoenixPHOENIX_SECRET=put_a_32_char_random_secret_herePHOENIX_ADMIN_SECRET=put_a_different_32_char_secretEOF

Now, create docker-compose.yml file with your desired text editor:

Bash
nano docker-compose.yml

Add:

YAML
services:  phoenix:    image: arizephoenix/phoenix:latest    restart: unless-stopped    depends_on:      - db    ports:      - "6006:6006"   # UI and OTLP HTTP collector      - "4317:4317"   # OTLP gRPC collector    environment:      - PHOENIX_SQL_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}      - PHOENIX_ENABLE_AUTH=True      - PHOENIX_SECRET=${PHOENIX_SECRET}      - PHOENIX_ADMIN_SECRET=${PHOENIX_ADMIN_SECRET}      - PHOENIX_WORKING_DIR=/mnt/data    volumes:      - phoenix_data:/mnt/data  db:    image: postgres:16    restart: unless-stopped    environment:      - POSTGRES_USER=${POSTGRES_USER}      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}      - POSTGRES_DB=${POSTGRES_DB}    volumes:      - phoenix_db_data:/var/lib/postgresql/data volumes:  phoenix_data:    driver: local  phoenix_db_data:    driver: local

Save and close the file. This compose file is the core step to self-host Arize Phoenix with Docker and a database that survives container recreation, upgrades, or a host reboot.

Note: Authentication is off by default in Phoenix, so setting PHOENIX_ENABLE_AUTH=True alongside a strong PHOENIX_SECRET is essential before exposing the server to the internet.

The PHOENIX_ADMIN_SECRET variable serves as a bootstrap bearer token, authenticating as the first system user and allowing your agent to send data immediately without requiring a UI login.

Step 3. Enable HTTPS with a Reverse Proxy

Exposing Phoenix directly on port 6006 without TLS is risky for anything beyond local testing, so front it with Caddy for automatic HTTPS.

From inside ~/phoenix-stack, create the Caddyfile with your actual domain name:

Bash
cat > Caddyfile << 'EOF'phoenix.yourdomain.com {    reverse_proxy phoenix:6006}EOF

Note that phoenix:6006 refers to Caddy reaching the Phoenix container by its service name on the internal Docker network, not localhost. Compose automatically creates a shared network where containers resolve each other by service name.

Then, add the Caddy service to the Compose file. Edit the file:

Bash
nano docker-compose.yml

Add this new caddy service block, indented the same as phoenix and db:

YAML
  caddy:    image: caddy:2    restart: unless-stopped    ports:      - "80:80"      - "443:443"    volumes:      - ./Caddyfile:/etc/caddy/Caddyfile      - caddy_data:/data      - caddy_config:/config

Then add the two new named volumes to the existing volumes: section at the bottom of the file, alongside phoenix_data and phoenix_db_data:

YAML
  caddy_data:    driver: local  caddy_config:    driver: local

Save and close the file once you are done.

Step 4. Configure Network Controls and Firewall

You must restrict access so that only trusted IPs and your application servers can reach Phoenix's ports. On Ubuntu with UFW, run:

Bash
sudo ufw allow 22/tcpsudo ufw allow 80/tcpsudo ufw allow 443/tcpsudo ufw deny 6006/tcpsudo ufw deny 4317/tcpsudo ufw enable

Blocking direct access to ports 6006 and 4317 means all traffic must pass through Caddy's TLS encryption. If your agent runs on a different server, don't block 4317 completely; only allow that server's IP to reach it.

Step 5. Deploy the Phoenix Stack

With the compose file and network rules ready, you are ready for deployment. Run this command to bring up the stack:

Bash
docker compose up -d

Check your Docker Compose status:

Bash
docker compose ps

It must be UP.

Check the logs with:

Bash
docker compose logs -f phoenix

Once the logs show the server is ready, in your browser, open:

Bash
https://phoenix.yourdomain.com

You will see the login screen. Enter the default credentials:

Bash
Email: admin@localhostPassword: admin

Phoenix default login credentials

Then, change the default password immediately and log in:

Change default Phoenix password

You must see the Phoenix UI:

Arize Phoenix Dashboard

You have now completed the core installation with persistent Postgres storage, HTTPS, and firewall rules in place.

Connect a Real AI Agent to Phoenix

A single model call won't show you what real observability looks like. So this section builds a LangChain agent that does retrieval and a tool call, then sends both to your self-hosted Phoenix.

On the machine running your agent, this can be the same VPS or a separate application server, as long as it can reach your Phoenix domain, confirm Python and pip are available:

Bash
python3 --versionpip3 --version

1. Create an Isolated Virtual Environment

Keeping this in a venv avoids polluting your system Python and prevents version conflicts with other projects on the same server:

Bash
mkdir -p ~/phoenix-agent && cd ~/phoenix-agentpython3 -m venv venvsource venv/bin/activate

Now run the actual install, inside the activated venv:

Bash
pip install --upgrade pippip install arize-phoenix-otel \  openinference-instrumentation-langchain \  langchain langchain-openai langchain-core faiss-cpu

Verify the installation with:

Bash
python -c "import phoenix.otel; import langchain; print('OK')"

If it prints OK with no import errors, the environment is ready.

2. Create the Agent Script

At this point, use the command below to create the Agent script file:

Bash
nano ~/phoenix-agent/agent.py

Add:

Python
from langchain_core.prompts import ChatPromptTemplatefrom langchain_openai import ChatOpenAI, OpenAIEmbeddingsfrom langchain.vectorstores import FAISSfrom langchain.chains import create_retrieval_chainfrom langchain.chains.combine_documents import create_stuff_documents_chainfrom langchain_core.documents import Documentfrom langchain_core.tools import toolfrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.trace.export import SimpleSpanProcessorfrom openinference.instrumentation.langchain import LangChainInstrumentorimport os endpoint = os.environ["PHOENIX_COLLECTOR_ENDPOINT"]tracer_provider = trace_sdk.TracerProvider()tracer_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter(endpoint)))LangChainInstrumentor().instrument(tracer_provider=tracer_provider) docs = [    Document(page_content="PerLod VPS plans include NVMe storage and root access."),    Document(page_content="Self-hosted observability keeps LLM traces inside your own infrastructure."),]embeddings = OpenAIEmbeddings()vectorstore = FAISS.from_documents(docs, embeddings)retriever = vectorstore.as_retriever() @tooldef get_server_status(hostname: str) -> str:    """Return a mock health status for a given hostname."""    return f"{hostname} is online with 12% CPU load." llm = ChatOpenAI(model_name="gpt-4o-mini")prompt = ChatPromptTemplate.from_messages([    ("system", "Answer using the retrieved context, and call tools when useful.\n\n{context}"),    ("human", "{input}"),])qa_chain = create_stuff_documents_chain(llm, prompt)rag_chain = create_retrieval_chain(retriever, qa_chain) if __name__ == "__main__":    response = rag_chain.invoke({"input": "What storage does PerLod VPS use, and check server-01 status?"})    print(response["answer"])    print(get_server_status.invoke("server-01"))

Save and close the file.

3. Set Environment Variables and Run

Still inside the activated venv, point the OTLP endpoint and auth header at your Phoenix server:

Bash
export PHOENIX_COLLECTOR_ENDPOINT="https://phoenix.yourdomain.com/v1/traces"export PHOENIX_CLIENT_HEADERS="Authorization=Bearer <your PHOENIX_ADMIN_SECRET>"export OPENAI_API_KEY="sk-your-key"

Then, run the script:

Bash
python agent.py

This script runs a retrieval step, an LLM call, and a tool call, giving you the three span types a real agent produces, instead of the single trace you'd get from just calling an LLM API directly.

Checking Traces and Spans in the Phoenix UI

Open your Phoenix UI and click into the project your agent run created. You should see a trace tree with three spans:

  • A retriever span showing the documents pulled from FAISS.
  • An LLM span showing the prompt and completion.
  • A tool span for get_server_status.

If any span is missing, it's usually because the OTLP endpoint or auth header was set up incorrectly on the agent side. Check PHOENIX_COLLECTOR_ENDPOINT and the bearer token first.

To add evaluation spans, run Phoenix's built-in evals on your traces after the run finishes. These score things like retrieval relevance or hallucination. Then log the results back to the same trace ID so they show up alongside the original spans in the UI.

Phoenix Backups and Maintenance

Snapshot your phoenix_db_data and phoenix_data volumes on a schedule, and store copies off the VPS in case you need to roll back after a bad upgrade.

Keeping backups healthy on a Linux VPS with enough storage and root access lets you repair issues safely without losing months of trace history, since Postgres restores sometimes need direct filesystem work.

Bash
docker compose exec db pg_dump -U phoenix phoenix > backup_$(date +%F).sql

If your traffic grows, it's worth comparing this stack against another observability option like the one covered in PerLod's self-hosted SigNoz with Docker guide to decide which fits your team better.

Conclusion

You now have a repeatable way to run this stack with persistent Postgres storage, HTTPS via Caddy, firewall-restricted ports, and a working agent that produces retrieval, LLM, and tool-call spans. From here, add more agents, tune retention with PHOENIX_DEFAULT_RETENTION_POLICY_DAYS, and keep backups current so your trace history survives future upgrades.

We hope you enjoy this guide on how to self-host Arize Phoenix with Docker.

For more deployment options and configurations, check the Arize Phoenix official self-hosting documentation.

Yes, mount a persistent volume and set PHOENIX_WORKING_DIR, though Postgres scales better for teams.

No, you must set PHOENIX_ENABLE_AUTH=True and a PHOENIX_SECRET yourself.

No, Phoenix only stores and displays traces, so a small CPU-only VPS is enough.