🛠️ Guia Prático: Implementar Stack Vendor-Agnostic¶
Objetivo Final¶
Uma stack 100% portável que roda em:
- Seu laptop agora
- VPS em 2 minutos
- Qualquer cloud em 5 minutos
- Kubernetes em 30 minutos
🎯 Passo 1: Preparação (1 hora)¶
1.1 Instalar Docker (se não tiver)¶
# macOS
brew install docker
# Ou baixar Docker Desktop
# https://www.docker.com/products/docker-desktop
# Verificar
docker --version
docker-compose --version
1.2 Estrutura do Projeto¶
cd /Users/glauberportella/Projects/ifriend/ifriend-agents
# Criar diretorios
mkdir -p infra/docker infra/k8s data/postgres data/ollama
# Estrutura resultante:
proyecto/
├─ docker-compose.yml # Stack orquestrado
├─ .env # Configurações secretas
├─ Dockerfile # Imagem personalizada
├─ requirements.txt # Dependências Python
│
├─ infra/
│ ├─ docker/
│ │ ├─ init_db.sql # Schema PostgreSQL
│ │ ├─ compose.prod.yml # Para produção
│ │ └─ compose.local.yml # Local com volumes
│ │
│ └─ k8s/
│ ├─ namespace.yaml # Namespace k8s
│ ├─ postgres.yaml # StatefulSet
│ ├─ ollama.yaml # Deployment
│ └─ api.yaml # Deployment
│
├─ data/
│ ├─ postgres/ # Dados de DB (gitignored)
│ └─ ollama/ # Modelos Ollama (gitignored)
│
├─ busca_productos/ # Seu código (já existe)
│ ├─ agent.py
│ ├─ slack_bot.py
│ ├─ memory_service_agnostic.py
│ └─ requirements.txt
│
└─ docs/
├─ VENDOR_AGNOSTIC_STACK.md
└─ SETUP_GUIDE.md (este arquivo)
🐳 Passo 2: Docker Compose Local (2 horas)¶
2.1 Criar docker-compose.yml¶
version: '3.8'
services:
# PostgreSQL com pgvector
postgres:
image: pgvector/pgvector:pg16-latest
container_name: ifriend-postgres
environment:
POSTGRES_DB: ${DB_NAME:-ifriend}
POSTGRES_USER: ${DB_USER:-ifriend}
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
POSTGRES_INITDB_ARGS: "-c shared_preload_libraries=vector"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./infra/docker/init_db.sql:/docker-entrypoint-initdb.d/01-init.sql
- ./infra/docker/seed_data.sql:/docker-entrypoint-initdb.d/02-seed.sql
ports:
- "${DB_PORT:-5432}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-ifriend}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ifriend_net
restart: unless-stopped
environment:
TZ: America/Sao_Paulo
# Ollama para embeddings
ollama:
image: ollama/ollama:latest
container_name: ifriend-ollama
volumes:
- ollama_data:/root/.ollama
ports:
- "${OLLAMA_PORT:-11434}:11434"
environment:
- OLLAMA_HOST=0.0.0.0:11434
- OLLAMA_NUM_GPU=0 # Mude para 1 se tiver GPU
- OLLAMA_NUM_THREAD=2 # Ajuste para seu CPU
- TZ=America/Sao_Paulo
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 10s
timeout: 5s
retries: 3
networks:
- ifriend_net
restart: unless-stopped
# API Backend (seu código)
api:
build:
context: .
dockerfile: Dockerfile
args:
PYTHON_VERSION: 3.11
container_name: ifriend-api
environment:
# Database
DATABASE_URL: postgresql://${DB_USER:-ifriend}:${DB_PASSWORD:-changeme}@postgres:5432/${DB_NAME:-ifriend}
# Ollama
OLLAMA_URL: http://ollama:11434
OLLAMA_MODEL: nomic-embed-text
# Slack
SLACK_BOT_TOKEN: ${SLACK_BOT_TOKEN}
SLACK_APP_TOKEN: ${SLACK_APP_TOKEN}
# Logging
LOG_LEVEL: ${LOG_LEVEL:-INFO}
# Environment
ENVIRONMENT: ${ENVIRONMENT:-development}
TZ: America/Sao_Paulo
depends_on:
postgres:
condition: service_healthy
ollama:
condition: service_healthy
ports:
- "${API_PORT:-8000}:8000"
volumes:
- ./busca_productos:/app/busca_productos
- ./ifriend_agent:/app/ifriend_agent
networks:
- ifriend_net
restart: unless-stopped
# Liiveness probe para k8s depois
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 10s
timeout: 5s
retries: 3
# (Optional) pgAdmin para gerenciar DB
pgadmin:
image: dpage/pgadmin4:latest
container_name: ifriend-pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-admin}
ports:
- "${PGADMIN_PORT:-5050}:80"
networks:
- ifriend_net
restart: unless-stopped
profiles:
- dev # Só roda se especificar: docker-compose --profile dev up
volumes:
postgres_data:
driver: local
ollama_data:
driver: local
networks:
ifriend_net:
driver: bridge
2.2 Criar .env¶
# Database
DB_NAME=ifriend
DB_USER=ifriend
DB_PASSWORD=your_secure_password_here_change_this
DB_PORT=5432
# Ollama
OLLAMA_PORT=11434
OLLAMA_MODEL=nomic-embed-text
# API
API_PORT=8000
ENVIRONMENT=development
LOG_LEVEL=INFO
# Slack (copie seus tokens)
SLACK_BOT_TOKEN=xoxb-your-token-here
SLACK_APP_TOKEN=xapp-your-token-here
# PgAdmin (apenas dev)
PGADMIN_PASSWORD=admin
PGADMIN_PORT=5050
2.3 Criar Dockerfile¶
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
curl \
postgresql-client \
git \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements
COPY requirements.txt .
# Install Python packages
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Create health check endpoint in Slack bot
RUN mkdir -p /app/health && echo "OK" > /app/health/status.txt
# Start application
CMD ["python", "-m", "busca_productos.slack_bot"]
2.4 Criar infra/docker/init_db.sql¶
-- Extensions
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS uuid-ossp;
-- Schema
CREATE SCHEMA IF NOT EXISTS memories;
-- Memories table
CREATE TABLE memories.memories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
app_name TEXT NOT NULL,
user_id TEXT NOT NULL,
session_id TEXT,
sector TEXT NOT NULL DEFAULT 'general',
title TEXT NOT NULL,
content TEXT NOT NULL,
embedding vector(384),
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
metadata JSONB DEFAULT '{}',
CONSTRAINT memories_nonempty_content CHECK (length(trim(content)) > 0)
);
-- Índices para performance
CREATE INDEX idx_memories_app_user ON memories.memories(app_name, user_id);
CREATE INDEX idx_memories_embedding ON memories.memories USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
CREATE INDEX idx_memories_sector ON memories.memories(sector);
CREATE INDEX idx_memories_created ON memories.memories(created_at DESC);
CREATE INDEX idx_memories_metadata ON memories.memories USING GIN(metadata);
-- Sessions table
CREATE TABLE memories.sessions (
id TEXT PRIMARY KEY,
app_name TEXT NOT NULL,
user_id TEXT NOT NULL,
data JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX idx_sessions_app_user ON memories.sessions(app_name, user_id);
CREATE INDEX idx_sessions_created ON memories.sessions(created_at DESC);
-- Vector search function
CREATE OR REPLACE FUNCTION memories.match_memories(
query_embedding vector,
p_user_id TEXT,
p_app_name TEXT,
p_limit INT DEFAULT 5,
p_min_similarity FLOAT DEFAULT 0.5
)
RETURNS TABLE (
id UUID,
title TEXT,
content TEXT,
sector TEXT,
similarity FLOAT8
) AS $$
BEGIN
RETURN QUERY
SELECT
m.id,
m.title,
m.content,
m.sector,
1 - (m.embedding <=> query_embedding)::FLOAT8 as similarity
FROM memories.memories m
WHERE
m.user_id = p_user_id
AND m.app_name = p_app_name
AND (m.embedding <=> query_embedding) <= (1 - p_min_similarity)
ORDER BY m.embedding <=> query_embedding
LIMIT p_limit;
END;
$$ LANGUAGE plpgsql STABLE PARALLEL SAFE;
-- Grants
GRANT USAGE ON SCHEMA memories TO ifriend;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA memories TO ifriend;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA memories TO ifriend;
GRANT USAGE ON TYPE vector TO ifriend;
-- Seed data (optional)
COMMENT ON SCHEMA memories IS 'Vector memory and session storage for ifriend agents';
2.5 Testar Localmente¶
# 1. Build imagem
docker-compose build
# 2. Iniciar todos os serviços
docker-compose up -d
# 3. Verificar status
docker-compose ps
# OUTPUT esperado:
# NAME COMMAND SERVICE STATUS
# ifriend-postgres "docker-entrypoint..." postgres Up (healthy)
# ifriend-ollama "ollama serve" ollama Up (healthy)
# ifriend-api "python -m busca..." api Up
# 4. Puxar modelo Ollama
docker exec ifriend-ollama ollama pull nomic-embed-text
# 5. Testar Database
docker exec ifriend-postgres psql -U ifriend -d ifriend -c "SELECT version();"
# 6. Testar Ollama
curl http://localhost:11434/api/tags
# 7. Ver logs
docker-compose logs -f api
# 8. Parar tudo
docker-compose down
🌐 Passo 3: Deploy em VPS (3 horas)¶
3.1 Criar VM no Digital Ocean¶
# Via console em https://cloud.digitalocean.com
# Ou via CLI:
doctl compute droplet create ifriend-api \
--region sfo3 \
--image ubuntu-22-04-x64 \
--size s-1vcpu-2gb \
--ssh-keys your-ssh-key-id \
--enable-backups
# Output:
# ID Name Memory vCPUs Disk Status Price
# 456789 ifriend-api 2048 1 50 new $0.0149/hr ($10/mth)
# Pegar IP
VPS_IP=$(doctl compute droplet get ifriend-api -o json | jq -r '.droplets[0].networks.v4[0].ip_address')
echo $VPS_IP # 167.99.xxx.xxx
3.2 SSH e Setup Inicial¶
# SSH na VM
ssh -i ~/.ssh/id_rsa root@$VPS_IP
# 1. Update sistema
apt update && apt upgrade -y
# 2. Instalar Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
# 3. Instalar Docker Compose
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
# 4. Criar user não-root (melhor prática)
useradd -m -s /bin/bash deploy
usermod -aG docker deploy
# 5. Setup SSH para deploy
su - deploy
mkdir -p ~/.ssh
# Adicionar sua chave pública
3.3 Clonar e Configurar¶
# Como user deploy
cd /home/deploy
# Clone repo
git clone seu-repo.git ifriend
cd ifriend
# Setup .env (alterar valores!)
cp .env.example .env
nano .env
# Valores importantes para VPS:
# - DB_PASSWORD: Use algo seguro
# - SLACK_BOT_TOKEN: Seu token real
# - ENVIRONMENT: production
# - OLLAMA_NUM_THREAD: Ajuste para 1 (VPS pequeno)
3.4 Iniciar Stack em Produção¶
# Docker Compose para produção (com restart automático)
docker-compose -f docker-compose.prod.yml up -d
# Verificar
docker-compose ps
# Puxar modelo (demora um pouco primeira vez)
docker exec ifriend-ollama ollama pull nomic-embed-text
3.5 Setup Reverse Proxy (Nginx)¶
# Instalar Nginx
sudo apt install -y nginx
# Criar config
sudo nano /etc/nginx/sites-available/ifriend
# Conteúdo:
upstream api {
server localhost:8000;
}
server {
listen 80;
server_name your-domain.com;
# Redirecionar HTTP → HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
location / {
proxy_pass http://api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Enable site
sudo ln -s /etc/nginx/sites-available/ifriend /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
# SSL com Let's Encrypt
sudo apt install -y certbot python3-certbot-nginx
sudo certbot certonly -d your-domain.com
☸️ Passo 4: Kubernetes (4 horas)¶
4.1 Setup k3s (lightweight Kubernetes)¶
# Na VM ou seu laptop
curl -sfL https://get.k3s.io | sh -
# Verificar
sudo kubectl get nodes
# Get kubeconfig para usar localmente
cat /etc/rancher/k3s/k3s.yaml
# Copie para ~/.kube/config
4.2 Criar Kubernetes Manifests¶
infra/k8s/namespace.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: ifriend
infra/k8s/postgres.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: ifriend
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: ifriend
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: pgvector/pgvector:pg16-latest
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: db-config
key: db-name
- name: POSTGRES_USER
valueFrom:
configMapKeyRef:
name: db-config
key: db-user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: ifriend
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
infra/k8s/ollama.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ollama
namespace: ifriend
spec:
replicas: 1
selector:
matchLabels:
app: ollama
template:
metadata:
labels:
app: ollama
spec:
containers:
- name: ollama
image: ollama/ollama:latest
ports:
- containerPort: 11434
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
volumeMounts:
- name: data
mountPath: /root/.ollama
volumes:
- name: data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: ollama
namespace: ifriend
spec:
selector:
app: ollama
ports:
- port: 11434
targetPort: 11434
infra/k8s/api.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: ifriend
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: ifriend-api:latest
imagePullPolicy: Never # Use local image
ports:
- containerPort: 8000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: api-secret
key: database-url
- name: OLLAMA_URL
value: "http://ollama:11434"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: ifriend
spec:
type: LoadBalancer
selector:
app: api
ports:
- port: 8000
targetPort: 8000
4.3 Deploy em Kubernetes¶
# Create namespace
kubectl apply -f infra/k8s/namespace.yaml
# Create secrets e configs
kubectl create secret generic db-secret \
-n ifriend \
--from-literal=password='your-secure-password'
kubectl create secret generic api-secret \
-n ifriend \
--from-literal=database-url='postgresql://ifriend:password@postgres:5432/ifriend'
# Create config
kubectl create configmap db-config \
-n ifriend \
--from-literal=db-name=ifriend \
--from-literal=db-user=ifriend
# Deploy
kubectl apply -f infra/k8s/
# Verificar
kubectl get all -n ifriend
kubectl logs -f deployment/api -n ifriend
✅ Checklist de Validação¶
Local (docker-compose)¶
# ✅ Database
docker exec ifriend-postgres psql -U ifriend -d ifriend -c "SELECT 1;"
# ✅ Ollama
curl http://localhost:11434/api/tags
# ✅ API health
curl http://localhost:8000/health
# ✅ Memory service
docker logs ifriend-api | grep -i memory
VPS (docker-compose)¶
# SSH na VM
ssh deploy@$VPS_IP
# ✅ Services rodando
docker-compose ps
# ✅ Database acessível
docker exec ifriend-postgres psql -U ifriend -d ifriend -c "SELECT 1;"
# ✅ Ollama pronto
curl http://localhost:11434/api/tags
# ✅ Nginx rodando
curl https://your-domain.com/health
Kubernetes¶
# ✅ Pods rodando
kubectl get pods -n ifriend
# ✅ Services
kubectl get svc -n ifriend
# ✅ Logs
kubectl logs deployment/api -n ifriend
# ✅ Port forward para testar
kubectl port-forward svc/api 8000:8000 -n ifriend
curl http://localhost:8000/health
🎯 Próximos Passos¶
- ✅ Agora: Teste docker-compose localmente
- ⏭️ Amanhã: Deploy em VPS
- ⏭️ Próxima semana: Setup Kubernetes
- ⏭️ Depois: Backup + monitoring + CI/CD