Customer Support AI Agent: A Self-Hosted Docker Deployment Guide
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.
Every SaaS vendor wants to sell you a customer support AI agent at $0.50 per resolved ticket plus a platform fee. If you run infrastructure for a living, that math stops making sense the moment your support volume grows. This guide walks through building and deploying a customer support AI agent yourself, using Docker, an open-source LLM backend, and a retrieval layer over your own documentation — no per-seat licensing required.
We’ll cover the architecture, the Docker Compose stack, retrieval-augmented generation (RAG) for grounding answers in your actual docs, reverse proxy security, and monitoring — everything you need to run this in production, not just a demo.
Why Self-Host a Customer Support AI Agent
Most teams reach for a hosted customer support AI agent because it’s fast to set up. But once you’re past the pilot stage, three problems show up: cost scales linearly with ticket volume, your support data (often full of customer PII) lives on a third party’s servers, and you’re locked into whatever model and prompt behavior the vendor ships that quarter.
Self-hosting flips all three. You control the model, the data stays on infrastructure you own, and the cost is a fixed VPS bill instead of a per-resolution fee.
The Case for Self-Hosting
The tradeoff is operational: you own uptime, security patching, and monitoring. If you’re already comfortable managing Docker in production, this is a small lift.
Architecture Overview
A production-grade customer support AI agent has four components:
1. A frontend/widget or API endpoint that receives customer messages.
2. An LLM inference layer — either a local model served via Ollama or a hosted API.
3. A retrieval layer (RAG) that pulls relevant chunks from your knowledge base so answers are grounded in your actual docs rather than the model’s training data.
4. A reverse proxy and monitoring layer to handle TLS termination and keep the whole thing observable.
All four run as containers behind a single Docker Compose file, which keeps the deployment reproducible across environments.
Building the Stack
We’ll use Ollama for local inference, a lightweight vector store (Chroma) for retrieval, and a small FastAPI service to tie them together. This mirrors patterns we’ve covered in our Docker Compose production guide, so if Compose syntax is new to you, start there first.
Docker Compose Setup
Here’s a minimal but production-usable docker-compose.yml:
version: "3.9"
services:
ollama:
image: ollama/ollama:latest
volumes:
- ollama_data:/root/.ollama
restart: unless-stopped
networks:
- support_net
chroma:
image: chromadb/chroma:latest
volumes:
- chroma_data:/chroma/chroma
restart: unless-stopped
networks:
- support_net
support-agent:
build: ./agent
environment:
- OLLAMA_HOST=http://ollama:11434
- CHROMA_HOST=http://chroma:8000
- MODEL_NAME=llama3
depends_on:
- ollama
- chroma
restart: unless-stopped
networks:
- support_net
nginx:
image: nginx:alpine
ports:
- "443:443"
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- support-agent
restart: unless-stopped
networks:
- support_net
volumes:
ollama_data:
chroma_data:
networks:
support_net:
driver: bridge
Bring it up with:
docker compose up -d
docker compose exec ollama ollama pull llama3
That second command pulls the model weights into the Ollama container. For a support agent, llama3:8b is usually the right balance of speed and quality on a mid-tier VPS; jump to a larger model only if you have GPU access.
Connecting the LLM Backend
The support-agent service is a small FastAPI app that does three things: embed the incoming question, retrieve relevant chunks from Chroma, and pass both to the LLM with a grounding prompt.
from fastapi import FastAPI
from pydantic import BaseModel
import httpx
import chromadb
app = FastAPI()
client = chromadb.HttpClient(host="chroma", port=8000)
collection = client.get_or_create_collection("support_docs")
class Query(BaseModel):
message: str
@app.post("/chat")
async def chat(query: Query):
results = collection.query(query_texts=[query.message], n_results=4)
context = "nn".join(results["documents"][0])
prompt = (
f"Answer the customer's question using only the context below. "
f"If the context doesn't cover it, say you'll escalate to a human.nn"
f"Context:n{context}nnQuestion: {query.message}"
)
async with httpx.AsyncClient(timeout=60) as http_client:
response = await http_client.post(
"http://ollama:11434/api/generate",
json={"model": "llama3", "prompt": prompt, "stream": False},
)
return {"answer": response.json()["response"]}
This is the whole trick behind why a self-hosted customer support AI agent doesn’t hallucinate wildly: the retrieval step forces the model to answer from your actual documentation rather than guessing. Load your help center articles, README files, and FAQ pages into Chroma ahead of time using a simple ingestion script, and re-run it whenever your docs change.
Securing Your Deployment
A customer support AI agent handles real customer data, which means it’s a security-sensitive service, not a side project. Treat it accordingly.
Reverse Proxy and TLS
Never expose the FastAPI service or Ollama’s API directly to the internet. Put Nginx in front, terminate TLS there, and only proxy the /chat endpoint:
server {
listen 443 ssl;
server_name support.yourdomain.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
location /chat {
proxy_pass http://support-agent:8000/chat;
proxy_set_header Host $host;
limit_req zone=support_limit burst=10 nodelay;
}
}
Add a rate-limiting zone (limit_req_zone) in the main Nginx config to prevent abuse — an unauthenticated chat endpoint is a common target for scraping or prompt-injection probing. If you’re new to reverse proxy configuration, our Nginx vs. Traefik comparison covers which tool fits which use case.
For certificate management, Let’s Encrypt via Certbot handles renewal automatically and costs nothing.
mem_limit, cpus) so a runaway inference request can’t take down the host.Monitoring and Uptime
Once this is customer-facing, you need to know immediately if it goes down. A dropped support widget is a worse customer experience than no widget at all. Pair container-level health checks in Compose with an external uptime monitor so you’re alerted from outside your own network — if your VPS loses connectivity entirely, an internal check won’t catch it.
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
retries: 3
We’ve written more on building out this kind of setup in our self-hosted monitoring stack guide, which pairs well with an external service like BetterStack for uptime alerts and incident status pages — worth it for anything customer-facing.
Scaling Considerations
A single VPS handles a surprising amount of traffic for a text-based agent, since inference is the bottleneck, not I/O. When you outgrow one box:
For the underlying compute, DigitalOcean droplets are a solid starting point for the whole stack, and Hetzner is worth comparing if you want more RAM per dollar for running larger local models. Both support the Docker-based deployment described here without any changes to the Compose file.
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 a customer support AI agent locally?
No. Models in the 7B–8B parameter range (like Llama 3 8B) run acceptably on CPU for low-to-moderate traffic. A GPU becomes worthwhile once you need sub-second response times or want to run larger, more capable models.
How is this different from just using ChatGPT with a custom prompt?
A hosted API call to ChatGPT is simpler to start with, but you lose data control and pay per token. This self-hosted setup grounds answers in your own documents via RAG and keeps all customer data on infrastructure you control — important if you handle regulated data.
Can this fully replace human support agents?
For tier-1 questions answerable from documentation, yes. Build in an explicit escalation path — the prompt in the example above already instructs the model to say it will escalate rather than guess, which you should route to a human queue or ticketing system.
How do I keep the knowledge base up to date?
Re-run your ingestion script into Chroma whenever your docs change. Many teams hook this into a CI/CD pipeline so a merge to the docs repo automatically re-indexes the content.
Is a self-hosted customer support AI agent FTC or compliance safe?
Self-hosting doesn’t automatically grant compliance, but it does give you full control over data retention, logging, and access — which makes meeting requirements like GDPR or SOC 2 controls significantly easier than depending on a third-party vendor’s data handling policies.
What happens if the LLM gives a wrong answer to a customer?
Build a feedback loop: log every conversation, flag low-confidence or escalated responses, and review them weekly. Treat wrong answers as gaps in your retrieval documents, not just model failures, and update the source docs accordingly.
Leave a Reply