What Is Self-Hosting AI Agents?
Self-hosting AI agents means running autonomous or semi-autonomous AI-powered software on infrastructure you controlβtypically a Virtual Private Server (VPS) or a dedicated machineβrather than relying on managed cloud services or third-party platforms. These agents can perform tasks like research, code generation, data analysis, email drafting, or even orchestrating complex workflows across multiple tools. When you self-host, you deploy the agent runtime, its dependencies, and often a local language model or an API proxy inside Docker containers on a Linux VPS. You own the data, control the environment, and pay only for the compute and bandwidth you actually consume.
A typical self-hosted AI agent stack looks like this:
- Orchestration framework β LangChain, CrewAI, AutoGPT, or a custom Python/Node.js loop that manages tool calls and memory
- LLM backend β a locally served model via Ollama/vLLM/LocalAI, or an API proxy that routes to OpenAI/Anthropic with rate limiting and caching
- Tool integrations β headless browser (Playwright), code executor (sandboxed Docker container), search APIs, database connectors
- Vector store & memory β ChromaDB, Qdrant, or PostgreSQL with pgvector for long-term memory and RAG pipelines
- Reverse proxy & auth β Nginx or Caddy with OAuth2/SSO to protect the agent's web dashboard or API endpoints
Why Self-Hosting Matters
1. Data Sovereignty and Privacy
When you use hosted AI agent platforms, your prompts, tool outputs, and intermediate reasoning steps often pass through third-party servers. For legal, healthcare, financial, or proprietary engineering work, this is a non-starter. Self-hosting keeps all sensitive context inside your own infrastructure. You can even run fully offline with local models like Llama 3, Mistral, or Qwen, ensuring zero data exfiltration.
2. Predictable Costs at Scale
Managed AI platforms charge per query, per agent-hour, or impose seat-based licensing that grows unpredictably. A VPS with a fixed monthly cost of $15β$60 can handle hundreds of thousands of agent iterations if architected correctly. Once you outgrow a single VPS, you can horizontally scale specific components (vector DB, message queue, GPU workers) rather than paying linearly for every conversation.
3. Full Control Over the Stack
You choose the model version, the timeout limits, the retry logic, the logging strategy, and the security boundaries. No vendor deprecates your workflow engine without notice. You can pin exact model versions and swap between providers or local models at will.
4. Customization Without Limits
Self-hosting lets you modify every layer: inject custom middleware for audit logging, add specialized tools that call internal APIs, implement fine-grained rate limiting per user, or integrate with legacy on-prem systems that a SaaS platform could never reach.
Architectural Overview
Before diving into Docker configurations and VPS setup, let's establish a reference architecture that balances simplicity with production readiness:
βββββββββββββββββββββββββββββββββββββββββββββββ
β Internet / Users β
β (Web dashboard, API clients, MCP clients) β
ββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β HTTPS :443
βΌ
ββββββββββββββββββββββββββββββββ
β Caddy / Nginx Reverse Proxy β
β - TLS termination (Let's β
β Encrypt auto-renewal) β
β - OAuth2-Proxy / Authelia β
β - Rate limiting β
β - WebSocket upgrades β
ββββββββββββββββ¬βββββββββββββββ
β
ββββββββββββΌβββββββββββ
βΌ βΌ βΌ
ββββββββββ ββββββββββ ββββββββββββ
β Agent β β LLM β β Vector β
β Runtimeβ β Proxy β β Store β
β (FastAPIβ (LiteLLMβ (ChromaDBβ
β or β or β or β
β Crew) β Ollama)β Qdrant) β
βββββ¬βββββ ββββββββββ ββββββ¬ββββββ
β β
βΌ βΌ
βββββββββββββββββββββββββββββββββββ
β Shared Persistent Storage β
β (Docker volumes / NFS mount) β
β - Agent logs & checkpoints β
β - Vector embeddings β
β - Model weights (if local) β
βββββββββββββββββββββββββββββββββββ
This architecture isolates concerns into separate containers that can be independently scaled, upgraded, or moved to different hardware. The reverse proxy is the only component exposed to the public internet.
Step-by-Step Implementation
Step 1: Provision Your VPS
Choose a VPS provider based on your workload. Here are solid options as of mid-2025:
- Hetzner Cloud β Best price-to-performance in Europe/US. Their CX42 (4 vCPU, 8GB RAM) at ~β¬12/month is ideal for CPU-heavy agent orchestration without a GPU. Their GPU instances (A100 or consumer-grade) start around β¬1.50/hour for model hosting.
- DigitalOcean β Excellent developer experience, predictable pricing. A basic Droplet (2 vCPU, 4GB RAM) at $24/month works for lightweight agents. Their App Platform can supplement container hosting.
- Linode/Akamai β Similar to DigitalOcean, with competitive GPU instances in select regions.
- Vultr β High-frequency CPU instances reduce agent iteration latency. Their 4 vCPU/8GB instance is ~$40/month.
- Lambda Labs β If you need dedicated GPU compute for local models, their A100 instances at ~$1.10/hour are among the cheapest.
For this tutorial, we'll target a Hetzner CX42 (4 vCPU Intel/AMD, 8GB RAM, 80GB NVMe SSD, ~β¬12/month) running Ubuntu 24.04 LTS. This gives enough headroom to run the agent runtime, a small local model via Ollama, ChromaDB, and the reverse proxy simultaneously.
Once your VPS is provisioned, SSH in and perform initial hardening:
# Update system
sudo apt update && sudo apt upgrade -y
# Install Docker and Docker Compose plugin
sudo apt install -y docker.io docker-compose-v2
# Add your user to the docker group (logout and back in after)
sudo usermod -aG docker $USER
# Enable unattended security updates
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
# Set up a basic firewall β only allow SSH (22), HTTP (80), HTTPS (443)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
# Verify Docker is running
docker --version
docker compose version
Step 2: Create the Project Structure
On your VPS, create a well-organized directory layout:
mkdir -p ~/ai-agent-stack/{agent-runtime,llm-proxy,vector-store,caddy,scripts,data}
cd ~/ai-agent-stack
This keeps each service isolated with its own configuration while sharing a single docker-compose.yml at the root level.
Step 3: The Docker Compose File β The Heart of the Stack
Create ~/ai-agent-stack/docker-compose.yml with the following content. This file defines every service, their networking, volumes, and environment variables:
# docker-compose.yml β Self-Hosted AI Agent Stack
# Place in ~/ai-agent-stack/
version: "3.9"
services:
# βββ Reverse Proxy with auto-TLS βββββββββββββββββββββββββββββββ
caddy:
image: caddy:2.8-alpine
container_name: caddy-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- agent-net
environment:
- ACME_AGREE=true
depends_on:
- agent-runtime
# βββ Agent Runtime (FastAPI-based) ββββββββββββββββββββββββββββ
agent-runtime:
build:
context: ./agent-runtime
dockerfile: Dockerfile
container_name: agent-runtime
restart: unless-stopped
expose:
- "8000"
volumes:
- ./data/agent-logs:/app/logs
- ./data/agent-checkpoints:/app/checkpoints
# Mount host Docker socket for sandboxed code execution
- /var/run/docker.sock:/var/run/docker.sock
environment:
- LLM_PROXY_URL=http://llm-proxy:4000
- VECTOR_STORE_URL=http://vector-store:8001
- AGENT_LOG_LEVEL=INFO
- AGENT_MAX_ITERATIONS=50
- AGENT_TIMEOUT_SECONDS=300
- OPENAI_API_KEY=${OPENAI_API_KEY:-} # Pass through if using cloud LLM
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
networks:
- agent-net
depends_on:
llm-proxy:
condition: service_healthy
vector-store:
condition: service_started
# βββ LLM Proxy (LiteLLM or Ollama) ββββββββββββββββββββββββββββ
llm-proxy:
image: ghcr.io/berriai/litellm:latest
container_name: llm-proxy
restart: unless-stopped
expose:
- "4000"
volumes:
- ./llm-proxy/config.yaml:/app/config.yaml:ro
- llm_cache:/app/cache
environment:
- LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY:-sk-proxy-master-key}
- LITELLM_SALT_KEY=${LITELLM_SALT_KEY:-salt-key-change-me}
networks:
- agent-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
interval: 30s
timeout: 10s
retries: 5
# βββ Vector Store (ChromaDB) ββββββββββββββββββββββββββββββββββ
vector-store:
image: chromadb/chroma:latest
container_name: vector-store
restart: unless-stopped
expose:
- "8001"
volumes:
- vector_data:/chroma/chroma
environment:
- IS_PERSISTENT=TRUE
- ANONYMIZED_TELEMETRY=FALSE
- ALLOW_RESET=TRUE
networks:
- agent-net
volumes:
caddy_data:
caddy_config:
llm_cache:
vector_data:
networks:
agent-net:
driver: bridge
Step 4: Configure the LLM Proxy
Create ~/ai-agent-stack/llm-proxy/config.yaml. LiteLLM acts as a unified gateway: it accepts OpenAI-compatible requests and routes them to multiple backendsβlocal Ollama instances, cloud APIs, or even Azure/Vertex AIβwith built-in rate limiting, cost tracking, and fallback logic:
# llm-proxy/config.yaml β LiteLLM Gateway Configuration
general_settings:
master_key: ${LITELLM_MASTER_KEY}
database_connection_pool_size: 5
allowed_routes: ["openai", "anthropic", "azure", "ollama"]
model_list:
# Cloud fallback model β used when local model is overloaded or down
- model_name: "gpt-4o-mini"
litellm_params:
model: "gpt-4o-mini"
api_key: ${OPENAI_API_KEY}
rpm: 500 # Requests per minute limit
tpm: 200000 # Tokens per minute limit
# Anthropic Claude for complex reasoning tasks
- model_name: "claude-3-5-sonnet"
litellm_params:
model: "claude-3-5-sonnet-20240620"
api_key: ${ANTHROPIC_API_KEY}
rpm: 100
tpm: 100000
# Local Ollama model β primary workhorse, zero egress cost
- model_name: "llama3-local"
litellm_params:
model: "ollama/llama3.1:8b"
api_base: "http://ollama-host:11434"
rpm: 200
tpm: 50000
# Higher timeout for local inference
timeout: 120
litellm_settings:
fallbacks:
- from_model: "llama3-local"
to_models: ["gpt-4o-mini"]
fallback_mode: "completion" # Auto-fallback on failure/timeout
cache: true
cache_type: "redis"
cache_host: "redis-host" # Optional: add Redis for caching LLM responses
cache_ttl: 3600
router_settings:
routing_strategy: "latency-based-routing"
allowed_fails: 3
num_retries: 2
retry_after: 5
If you prefer running a local model directly without the proxy complexity, you can replace the llm-proxy service with Ollama itself:
# Alternative: Add to docker-compose.yml if running Ollama directly
ollama:
image: ollama/ollama:latest
container_name: ollama
restart: unless-stopped
expose:
- "11434"
volumes:
- ollama_models:/root/.ollama
networks:
- agent-net
# Optional: enable GPU if your VPS has one
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: 1
# capabilities: [gpu]
# Then pull a model after first startup:
# docker exec -it ollama ollama pull llama3.1:8b
Step 5: Build the Agent Runtime
Create ~/ai-agent-stack/agent-runtime/Dockerfile:
# agent-runtime/Dockerfile
FROM python:3.12-slim
# Install system dependencies for Playwright and sandboxed execution
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
build-essential \
libglib2.0-0 \
libnss3 \
libnspr4 \
libdbus-1-3 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libgbm1 \
libpango-1.0-0 \
libcairo2 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Install Python packages
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Install Playwright browsers (headless Chromium for web scraping agents)
RUN playwright install --with-deps chromium
COPY . .
EXPOSE 8000
# Run the FastAPI agent server
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
Create ~/ai-agent-stack/agent-runtime/requirements.txt:
# agent-runtime/requirements.txt
fastapi==0.115.0
uvicorn[standard]==0.30.6
httpx==0.27.2
langchain==0.3.10
langchain-community==0.3.10
langgraph==0.2.35
openai==1.55.0
chromadb==0.5.20
playwright==1.48.0
pydantic==2.10.0
python-dotenv==1.0.1
sqlite3-api==1.0.0
Create the main agent server file at ~/ai-agent-stack/agent-runtime/main.py:
# agent-runtime/main.py
"""
Self-Hosted AI Agent Runtime
Exposes a REST API for creating, running, and monitoring AI agents.
"""
import os
import uuid
import json
import logging
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, BackgroundTasks, Query
from fastapi.responses import JSONResponse, StreamingResponse
from pydantic import BaseModel, Field
import httpx
# LangChain / LangGraph imports
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.sqlite import SqliteSaver
from langchain_openai import ChatOpenAI
from langchain_community.tools.playwright.navigate import NavigateTool
from langchain_community.tools.playwright.extract_text import ExtractTextTool
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import FakeEmbeddings
import sqlite3
# βββ Configuration ββββββββββββββββββββββββββββββββββββββββββββββ
LLM_PROXY_URL = os.getenv("LLM_PROXY_URL", "http://llm-proxy:4000")
VECTOR_STORE_URL = os.getenv("VECTOR_STORE_URL", "http://vector-store:8001")
LOG_DIR = os.getenv("AGENT_LOG_DIR", "/app/logs")
CHECKPOINT_DIR = os.getenv("AGENT_CHECKPOINT_DIR", "/app/checkpoints")
MAX_ITERATIONS = int(os.getenv("AGENT_MAX_ITERATIONS", "50"))
TIMEOUT_SECONDS = int(os.getenv("AGENT_TIMEOUT_SECONDS", "300"))
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("agent-runtime")
# βββ Lifespan: Initialize checkpoint store βββββββββββββββββββββ
@asynccontextmanager
async def lifespan(app: FastAPI):
os.makedirs(LOG_DIR, exist_ok=True)
os.makedirs(CHECKPOINT_DIR, exist_ok=True)
# SQLite checkpoint DB for LangGraph state persistence
checkpoint_db = os.path.join(CHECKPOINT_DIR, "checkpoints.db")
app.state.checkpoint_db = checkpoint_db
logger.info(f"Agent runtime started β checkpoints at {checkpoint_db}")
yield
logger.info("Agent runtime shutting down")
app = FastAPI(lifespan=lifespan, title="Self-Hosted AI Agent Runtime")
# βββ Pydantic Models ββββββββββββββββββββββββββββββββββββββββββββ
class AgentCreateRequest(BaseModel):
task: str = Field(..., description="Natural language task description")
model: str = Field(default="llama3-local", description="Model name (routed via LLM proxy)")
max_iterations: int = Field(default=MAX_ITERATIONS)
tools: list[str] = Field(default=["web_search", "code_executor"])
system_prompt: Optional[str] = None
class AgentStatus(BaseModel):
agent_id: str
status: str # "running", "completed", "failed", "timed_out"
task: str
created_at: str
completed_at: Optional[str] = None
iterations_used: int = 0
final_output: Optional[str] = None
error_message: Optional[str] = None
# βββ In-memory agent registry (backed by SQLite in production) ββ
agent_registry: Dict[str, AgentStatus] = {}
# βββ API Endpoints ββββββββββββββββββββββββββββββββββββββββββββββ
@app.get("/health")
async def health_check():
return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}
@app.post("/agents", response_model=AgentStatus, status_code=201)
async def create_agent(request: AgentCreateRequest, background_tasks: BackgroundTasks):
"""Create a new agent task and begin execution asynchronously."""
agent_id = str(uuid.uuid4())[:8]
status = AgentStatus(
agent_id=agent_id,
status="running",
task=request.task,
created_at=datetime.utcnow().isoformat(),
)
agent_registry[agent_id] = status
# Launch agent execution in background
background_tasks.add_task(
execute_agent,
agent_id=agent_id,
task=request.task,
model=request.model,
max_iterations=request.max_iterations,
tools=request.tools,
system_prompt=request.system_prompt,
)
logger.info(f"Agent {agent_id} created β task: {request.task[:80]}...")
return status
@app.get("/agents/{agent_id}", response_model=AgentStatus)
async def get_agent_status(agent_id: str):
"""Poll agent status by ID."""
if agent_id not in agent_registry:
raise HTTPException(status_code=404, detail="Agent not found")
return agent_registry[agent_id]
@app.get("/agents")
async def list_agents(
status_filter: Optional[str] = Query(None),
limit: int = Query(20, ge=1, le=100),
):
"""List all agents, optionally filtered by status."""
agents = list(agent_registry.values())
if status_filter:
agents = [a for a in agents if a.status == status_filter]
agents = sorted(agents, key=lambda a: a.created_at, reverse=True)[:limit]
return {"agents": agents, "total": len(agent_registry)}
@app.delete("/agents/{agent_id}")
async def delete_agent(agent_id: str):
"""Remove an agent record from the registry."""
if agent_id not in agent_registry:
raise HTTPException(status_code=404, detail="Agent not found")
del agent_registry[agent_id]
return {"deleted": agent_id}
# βββ Agent Execution Logic ββββββββββββββββββββββββββββββββββββββ
async def execute_agent(
agent_id: str,
task: str,
model: str,
max_iterations: int,
tools: list[str],
system_prompt: Optional[str] = None,
):
"""Core agent loop using LangGraph with tool calling."""
status = agent_registry[agent_id]
start_time = datetime.utcnow()
try:
# Initialize the LLM via LiteLLM proxy (OpenAI-compatible endpoint)
llm = ChatOpenAI(
model=model,
openai_api_key=os.getenv("OPENAI_API_KEY", "dummy"),
openai_api_base=f"{LLM_PROXY_URL}/v1",
temperature=0.7,
max_tokens=4096,
timeout=120,
)
# Build a simple StateGraph
builder = StateGraph(dict)
def agent_node(state: dict):
"""Core reasoning step β call LLM with tool definitions."""
messages = state.get("messages", [])
task_prompt = state.get("task", task)
full_messages = [
{"role": "system", "content": system_prompt or "You are a helpful AI assistant."},
{"role": "user", "content": task_prompt},
] + messages
# Bind tools (simplified β real implementation uses LangChain tools)
llm_with_tools = llm.bind_tools([]) # Add actual tool definitions here
response = llm_with_tools.invoke(full_messages)
return {"messages": messages + [response.dict()], "iteration": state.get("iteration", 0) + 1}
def should_continue(state: dict) -> str:
iteration = state.get("iteration", 0)
if iteration >= max_iterations:
return END
# Check for tool calls or final answer
last_msg = state.get("messages", [{}])[-1]
if last_msg.get("tool_calls"):
return "tool_executor"
return END
def tool_executor(state: dict):
"""Execute tool calls from the LLM response."""
messages = state.get("messages", [])
last_msg = messages[-1] if messages else {}
tool_results = []
for tool_call in last_msg.get("tool_calls", []):
# Simplified tool execution β real version dispatches to actual tools
result = {"id": tool_call.get("id"), "output": f"Tool {tool_call.get('name')} executed"}
tool_results.append(result)
return {"messages": messages + tool_results}
builder.add_node("agent", agent_node)
builder.add_node("tool_executor", tool_executor)
builder.set_entry_point("agent")
builder.add_conditional_edges("agent", should_continue, {
"tool_executor": "tool_executor",
END: END,
})
builder.add_edge("tool_executor", "agent")
# Compile with checkpoint saver for resilience
checkpoint_db = os.path.join(CHECKPOINT_DIR, "checkpoints.db")
with SqliteSaver.from_conn_string(checkpoint_db) as saver:
graph = builder.compile(checkpointer=saver)
config = {"configurable": {"thread_id": agent_id}}
initial_state = {"task": task, "messages": [], "iteration": 0}
result = graph.invoke(initial_state, config)
# Success
elapsed = (datetime.utcnow() - start_time).total_seconds()
status.status = "completed"
status.completed_at = datetime.utcnow().isoformat()
status.iterations_used = result.get("iteration", 0)
final_messages = result.get("messages", [])
status.final_output = final_messages[-1].get("content", "") if final_messages else ""
logger.info(f"Agent {agent_id} completed in {elapsed:.1f}s with {status.iterations_used} iterations")
except Exception as e:
elapsed = (datetime.utcnow() - start_time).total_seconds()
status.status = "failed" if elapsed < TIMEOUT_SECONDS else "timed_out"
status.completed_at = datetime.utcnow().isoformat()
status.error_message = str(e)
logger.error(f"Agent {agent_id} failed: {str(e)}")
# βββ Entrypoint βββββββββββββββββββββββββββββββββββββββββββββββββ
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Step 6: Configure the Reverse Proxy (Caddy)
Create ~/ai-agent-stack/caddy/Caddyfile:
# caddy/Caddyfile β Reverse Proxy with Auto-TLS
# Replace YOUR_DOMAIN.com with your actual domain
YOUR_DOMAIN.com {
# Let's Encrypt handles TLS certificate automatically
# Security headers
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
-Server
}
# Rate limit: 100 requests per second per IP
rate_limit {
zone agent_api {
key {remote_host}
events 100
window 1s
}
}
# Agent Runtime API
handle /api/* {
reverse_proxy agent-runtime:8000 {
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto "https"
}
}
# LLM Proxy (internal only β block external access)
# Uncomment if you need direct LLM access from external clients:
# handle /llm/* {
# reverse_proxy llm-proxy:4000
# }
# Default: serve a simple status page
handle {
respond / "Self-Hosted AI Agent Stack β Operational" 200
respond /health "OK" 200
}
# Logging
log {
output file /var/log/caddy/access.log
format json
}
}
Step 7: Deploy the Stack
With all files in place, deploy everything from the project root:
cd ~/ai-agent-stack
# Create a .env file with secrets
cat > .env << 'EOF'
OPENAI_API_KEY=sk-your-openai-key-here
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key-here
LITELLM_MASTER_KEY=sk-proxy-master-key-change-me
LITELLM_SALT_KEY=salt-key-change-me
EOF
# Secure the .env file
chmod 600 .env
# Build and start all services
docker compose up -d --build
# Check that all containers are healthy
docker compose ps
# View logs for debugging
docker compose logs -f agent-runtime
# Test the health endpoint
curl http://localhost:8000/health
# Create your first agent via the API
curl -X POST http://localhost:8000/agents \
-H "Content-Type: application/json" \
-d '{
"task": "Research the latest developments in quantum computing and summarize the top 3 breakthroughs",
"model": "llama3-local",
"max_iterations": 20
}'
# Poll for results (replace AGENT_ID with the returned ID)
curl http://localhost:8000/agents/AGENT_ID
Complete Cost Breakdown
Let's calculate the real monthly cost for a production-ready self-hosted AI agent stack serving a small team (up to 10 developers, ~50,000 agent iterations per month):
Base VPS Tier: CPU-Only Orchestration (No Local GPU)
| Component | Provider | Monthly Cost |
|---|---|---|
| VPS (4 vCPU, 8GB RAM, 80GB NVMe) | Hetzner CX42 | β¬12.00 |
| Block Storage (100GB for models/vectors) | Hetzner Volume | β¬4.40 |
| Static IPv4 Address | Included | β¬0.00 |
| Traffic (20TB included) | Included | β¬0.00 |
| Infrastructure Subtotal | β¬16.40 |
LLM API Costs (If Using Cloud Models for Fallback/Heavy Lifting)
| Model | Usage Estimate | Monthly Cost |
|---|---|---|
| GPT-4o-mini (fallback, 200K tokens/day) | 6M tokens/month @ $0.15/1M input | ~$1.20 |
| Claude 3.5 Sonnet (complex tasks, 50K tokens/day) | 1.5M tokens/month @ $3/1M input | ~$5.50 |
| Local Llama 3.1 8B (primary, 500K tokens/day) | 15M
β Ad β Google AdSense will appear here after approval |