//------------------------------------------------------------------- //-------------------------------------------------------------------
self-host Arize Phoenix with Docker

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

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, capturing 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:

sudo apt update && sudo apt upgrade -y
docker info
docker compose version

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

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:

openssl rand -hex 32

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

cat > .env << 'EOF'
POSTGRES_USER=phoenix
POSTGRES_PASSWORD=change_this_password
POSTGRES_DB=phoenix
PHOENIX_SECRET=put_a_32_char_random_secret_here
PHOENIX_ADMIN_SECRET=put_a_different_32_char_secret
EOF

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

nano docker-compose.yml

Add:

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:

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:

nano docker-compose.yml

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

  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:

  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:

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 6006/tcp
sudo ufw deny 4317/tcp
sudo 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:

docker compose up -d

Check your Docker Compose status:

docker compose ps

It must be UP.

Check the logs with:

docker compose logs -f phoenix

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

https://phoenix.yourdomain.com

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

Email: admin@localhost
Password: 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:

python3 --version
pip3 --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:

mkdir -p ~/phoenix-agent && cd ~/phoenix-agent
python3 -m venv venv
source venv/bin/activate

Now run the actual install, inside the activated venv:

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

Verify the installation with:

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:

nano ~/phoenix-agent/agent.py

Add:

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.documents import Document
from langchain_core.tools import tool
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk import trace as trace_sdk
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from openinference.instrumentation.langchain import LangChainInstrumentor
import 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()

@tool
def 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:

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:

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.

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.

FAQs

Can I use SQLite instead of Postgres for Arize Phoenix?

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

Is authentication enabled by default in Phoenix?

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

Do I need a GPU for the Phoenix setup with Docker?

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

Post Your Comment

PerLod delivers high-performance hosting with real-time support and unmatched reliability.

Contact us

Payment methods

payment gateway
Perlod Logo
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.