Building a Self-Hosted AI Agent for Customer Support with Docker
Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.
Customer support teams are drowning in repetitive tickets — password resets, shipping status, refund policy questions. An ai agent for customer support can triage and resolve a large chunk of that volume automatically, without sending customer data to a third-party SaaS platform. This guide walks through a self-hosted stack you can run on your own VPS, using Docker, an open-source LLM runtime, and a vector database for retrieval-augmented answers.
We’ll cover architecture, a full docker-compose.yml, retrieval-augmented generation (RAG) for grounding answers in your actual help docs, monitoring, and hardening. This is written for developers and sysadmins who are comfortable with the command line and want infrastructure they actually control.
Why Self-Host an AI Agent for Customer Support
Most “AI customer support” products are hosted SaaS wrappers around a third-party LLM API. That’s fine for a quick prototype, but it comes with real tradeoffs:
A self-hosted stack fixes all four. You pick the model, you control the data, and you pay for compute instead of per-message fees. The tradeoff is operational: you now own uptime, scaling, and model quality. That’s a fair trade if you already run Docker-based infrastructure — and if you’re used to managing Docker Compose stacks in production, the learning curve here is small.
Core Architecture
The stack has four moving parts:
1. LLM runtime — Ollama serving an open-weight model (Llama 3, Mistral, or similar) over a local HTTP API.
2. Vector database — stores embeddings of your help docs, past tickets, and FAQs so the agent can retrieve relevant context before answering (this is the RAG pattern).
3. Agent orchestrator — a lightweight app (Python/Node) that receives incoming messages, queries the vector DB, builds a prompt, and calls the LLM.
4. Reverse proxy + TLS — nginx or Caddy in front of everything, terminating HTTPS.
Here’s the layout:
client (widget / helpdesk webhook)
|
v
nginx (TLS termination)
|
v
agent-api (FastAPI) <--> vector-db (Qdrant)
|
v
ollama (LLM runtime)
Docker Compose Setup
Here’s a working docker-compose.yml for the full stack. Each service is isolated in its own container, which keeps upgrades and rollbacks clean.
version: "3.9"
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
volumes:
- ollama_data:/root/.ollama
restart: unless-stopped
networks:
- agent_net
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
volumes:
- qdrant_data:/qdrant/storage
restart: unless-stopped
networks:
- agent_net
agent-api:
build: ./agent-api
container_name: agent-api
environment:
- OLLAMA_HOST=http://ollama:11434
- QDRANT_HOST=http://qdrant:6333
- MODEL_NAME=llama3
depends_on:
- ollama
- qdrant
restart: unless-stopped
networks:
- agent_net
nginx:
image: nginx:alpine
container_name: agent-proxy
ports:
- "443:443"
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- agent-api
restart: unless-stopped
networks:
- agent_net
volumes:
ollama_data:
qdrant_data:
networks:
agent_net:
driver: bridge
Pull the model once the containers are up:
docker compose up -d
docker exec -it ollama ollama pull llama3
That’s a full local inference stack — no external API key required. If you’d rather use a hosted model API instead of running Ollama, you can swap the ollama service for a call to an external provider, but then you’re back to sending data off-box, which defeats part of the point.
The Agent API (RAG Logic)
The orchestrator is the piece that actually makes this useful. A bare LLM will hallucinate refund policies and shipping windows. Grounding it in your real documentation via retrieval fixes that. A minimal FastAPI implementation looks like this:
# agent-api/main.py
from fastapi import FastAPI
from pydantic import BaseModel
import httpx
app = FastAPI()
class Query(BaseModel):
message: str
@app.post("/chat")
async def chat(query: Query):
async with httpx.AsyncClient() as client:
# 1. Embed the incoming message and search Qdrant for relevant docs
search = await client.post(
"http://qdrant:6333/collections/support_docs/points/search",
json={"vector": embed(query.message), "limit": 3}
)
context = "n".join(p["payload"]["text"] for p in search.json()["result"])
# 2. Build a grounded prompt and call the local LLM
prompt = f"Answer using only this context:n{context}nnQuestion: {query.message}"
response = await client.post(
"http://ollama:11434/api/generate",
json={"model": "llama3", "prompt": prompt, "stream": False}
)
return {"reply": response.json()["response"]}
(The embed() helper is left out for brevity — use a small embedding model like nomic-embed-text, also servable through Ollama, to keep everything on the same runtime.)
Before this works, you need to load your help center content into Qdrant as embeddings — a one-time ingestion script that chunks your docs, embeds each chunk, and upserts them into the support_docs collection.
Escalation and Human Handoff
An AI agent for customer support should never trap a frustrated customer in an infinite bot loop. Build in explicit escalation logic:
This handoff logic is usually a small state machine in the agent-api service, not a separate system.
Monitoring and Uptime
Once this is customer-facing, downtime means angry tickets about the ticket-answering bot. Put real monitoring in front of it:
/chat endpoint and the Ollama API separately — an LLM OOM crash looks different from a proxy failure.docker compose logs -f agent-api is fine for debugging, but not for production incident response.If you haven’t set up centralized log shipping for Docker before, our guide on monitoring Docker containers in production covers the basics with Prometheus and Grafana, which pairs well with this stack.
Hosting and Hardening
Inference workloads are CPU/GPU-hungry, so pick your VPS accordingly:
11434, 6333) directly to the internet — they have no built-in auth. Keep them on the internal Docker network only, as shown in the compose file above.If you’re new to reverse proxy configuration for Docker stacks, see our nginx reverse proxy guide for Docker for a full TLS setup with Let’s Encrypt.
Testing Before You Launch
Don’t point this at live customers until you’ve stress-tested it:
/chat endpoint with something like hey or k6 to see how many concurrent conversations your VPS can actually handle before latency degrades.Recommended: Ready to put this into practice? DigitalOcean is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.
FAQ
Do I need a GPU to run an AI agent for customer support?
No. Small models (7-8B parameters, quantized) run acceptably on CPU for low-to-moderate ticket volume. A GPU becomes worthwhile once you need sub-second responses at higher concurrency.
Is a self-hosted AI agent for customer support GDPR-friendly?
Self-hosting gives you more control since customer data never leaves your infrastructure, but you still need proper data retention policies, encryption at rest, and a documented processing basis — self-hosting reduces third-party exposure, it doesn’t automatically make you compliant.
Can this replace human support agents entirely?
No, and it shouldn’t try to. Use it to deflect repetitive, well-documented questions and route anything ambiguous, emotional, or account-specific to a human.
What LLM should I start with?
Llama 3 8B or Mistral 7B are solid starting points — both run through Ollama with minimal configuration and produce reasonable results when grounded with RAG.
How do I keep the agent’s answers up to date as my docs change?
Re-run the embedding ingestion script whenever your help center content changes, or schedule it as a nightly cron job so the vector database stays in sync.
What happens if the LLM container runs out of memory?
Set memory limits in your compose file (mem_limit) and configure restart: unless-stopped as shown above, plus an external health check, so a crash triggers an alert instead of silent downtime.
Wrapping Up
A self-hosted AI agent for customer support gives you control over data, cost, and model choice that hosted SaaS tools can’t match — at the price of owning the operational burden yourself. Start small: one model, one vector collection, a handful of FAQ documents, and a hard escalation rule. Expand from there once you trust the answers it’s giving.
If you’re already comfortable with Docker Compose stacks, this is a weekend project, not a months-long platform build.
Leave a Reply