Avançado 22 minDocker

Implantar um LLM em produção com Docker Compose

Executar um LLM localmente no seu computador leva dez minutos. Implantar um LLM em produção com Docker Compose para uma equipe — com uma URL em HTTPS, monitoramento, backups e segurança de verdade — é outro trabalho. Este guia reúne uma stack completa (Ollama, Open WebUI, Qdrant, n8n, Traefik) em um único arquivo docker-compose.yml comentado, pronto para ser instalado em um VPS ou em um servidor on-premise.

Por Mohamed Meguedmi·Atualização 2026-08-27·Testado no Windows, macOS e Linux

#Por que essa stack

O objetivo é atender às quatro necessidades concretas de uma PME ou de uma equipe técnica que adota IA local: um motor de inferência (Ollama), uma interface web para o usuário (Open WebUI), uma base vetorial para o RAG (Qdrant) e uma camada de automação no-code (n8n). O conjunto usa o Traefik como proxy reverso, responsável pelo HTTPS via Let's Encrypt e pelo roteamento.

Cada serviço roda em seu contêiner isolado, com seus volumes persistentes. Você pode desativar um sem afetar os outros, atualizar apenas um componente ou replicar a stack em um ambiente de staging com apenas um comando.

i
O que este guia não é
Isso não é um guia de Kubernetes. Para uma equipe de 10 a 50 pessoas em um único servidor (até cerca de 10 usuários simultâneos em uma GPU), o Docker Compose é mais que suficiente. Acima disso, ou para alta disponibilidade em múltiplos nós, veja k3s ou Nomad.

#Arquitetura-alvo

O kit Agentes Locais

Agentes que agem na sua máquina: Cline agêntico, MCP, n8n + Ollama, automações locais.

  • Espaço online vitalício
  • PDF + arquivos
  • Atualizações vitalícias
Traefik (porta 80/443)
Proxy reverso + terminação TLS automática via Let's Encrypt. Todo o tráfego externo passa por ele.
Open WebUI (interno)
Interface de chat no estilo do ChatGPT, disponível em chat.votre-domaine.fr. Autenticação local ou OIDC.
Ollama (interno)
Daemon de inferência. NÃO é exposto publicamente, acessível apenas via rede Docker.
Qdrant (interno)
Base vetorial para o RAG. Armazenamento dos embeddings dos seus documentos.
n8n
Automatização sem código, acessível em n8n.votre-domaine.fr.
Prometheus + Grafana
Opcionais, expostos internamente para monitoramento de GPU/CPU/RAM.

#Pré-requisitos

Um servidor Linux
Ubuntu 22.04 LTS ou Debian 12, acesso SSH como root, no mínimo 16 GB de RAM e 200 GB de espaço em disco.
Uma GPU NVIDIA é recomendada
RTX 3090 24 GB ou RTX 4090 para uso confortável com modelos de 14B a 32B, ou RTX 4070 12 GB para modelos de 7B. Sem GPU, mantenha-se com modelos de 3B em CPU.
Um domínio
Com acesso ao DNS para criar subdomínios. Essencial para Let's Encrypt.
Docker Engine + Compose v2
Instalação via o script oficial get.docker.com. O plugin compose está incluído desde 2022.
NVIDIA Container Toolkit
Se você tem uma GPU, é isso que permite ao Docker disponibilizá-la aos contêineres.

#1. Preparar o servidor

Partimos de uma instalação nova do Ubuntu 22.04. Três coisas para instalar: Docker, o NVIDIA Container Toolkit (se houver GPU) e um firewall adequado.

Instalação Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
docker compose version
NVIDIA Container Toolkit
distribution=$(. /etc/os-release; echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
Testar se o Docker vê o GPU
docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi
Firewall UFW mínimo
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP - redir vers HTTPS'
sudo ufw allow 443/tcp comment 'HTTPS Traefik'
sudo ufw enable
!
Nunca exponha o Ollama diretamente
A porta 11434 do Ollama não possui autenticação. Se você a expuser na internet, qualquer pessoa poderá consumir os recursos da sua GPU e exfiltrar seus modelos pela API. Tudo passa pelo Traefik com autenticação.

#2. O docker-compose.yml comentado

Crie uma pasta de trabalho e o arquivo principal. Esse é o núcleo da implantação — cada seção está comentada para que você possa adaptá-la sem quebrar tudo.

Estrutura de pastas
mkdir -p /opt/llm-stack/{traefik,ollama,open-webui,qdrant,n8n}
cd /opt/llm-stack
touch docker-compose.yml .env
.env (a completar)
DOMAIN=votre-domaine.fr
ACME_EMAIL=admin@votre-domaine.fr
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=changez-moi-vraiment
TRAEFIK_DASHBOARD_AUTH=admin:$$apr1$$xxxxxxx  # generé avec htpasswd
docker-compose.yml
name: llm-stack

networks:
  proxy:      # réseau public exposé par Traefik
  internal:   # réseau privé Ollama <-> WebUI <-> Qdrant

volumes:
  ollama_data:
  open_webui_data:
  qdrant_data:
  n8n_data:
  traefik_certs:

services:

  # --- Traefik : reverse proxy + Let's Encrypt automatique ---
  traefik:
    image: traefik:v3.2
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/certs/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
      - --api.dashboard=true
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_certs:/certs
    networks: [proxy]
    labels:
      - traefik.enable=true
      - traefik.http.routers.dashboard.rule=Host(`traefik.${DOMAIN}`)
      - traefik.http.routers.dashboard.service=api@internal
      - traefik.http.routers.dashboard.entrypoints=websecure
      - traefik.http.routers.dashboard.tls.certresolver=le
      - traefik.http.routers.dashboard.middlewares=dashboard-auth
      - traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}

  # --- Ollama : moteur d'inférence, JAMAIS exposé en public ---
  ollama:
    image: ollama/ollama:latest
    restart: unless-stopped
    volumes:
      - ollama_data:/root/.ollama
    environment:
      - OLLAMA_HOST=0.0.0.0:11434
      - OLLAMA_KEEP_ALIVE=24h
      - OLLAMA_NUM_PARALLEL=2
    networks: [internal]
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

  # --- Open WebUI : interface chat, exposée en HTTPS ---
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    restart: unless-stopped
    depends_on: [ollama]
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
      - WEBUI_AUTH=true
      - ENABLE_SIGNUP=false
    volumes:
      - open_webui_data:/app/backend/data
    networks: [proxy, internal]
    labels:
      - traefik.enable=true
      - traefik.http.routers.chat.rule=Host(`chat.${DOMAIN}`)
      - traefik.http.routers.chat.entrypoints=websecure
      - traefik.http.routers.chat.tls.certresolver=le
      - traefik.http.services.chat.loadbalancer.server.port=8080

  # --- Qdrant : base vectorielle pour le RAG ---
  qdrant:
    image: qdrant/qdrant:latest
    restart: unless-stopped
    volumes:
      - qdrant_data:/qdrant/storage
    networks: [internal]

  # --- n8n : automatisation no-code ---
  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
      - N8N_HOST=n8n.${DOMAIN}
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.${DOMAIN}/
    volumes:
      - n8n_data:/home/node/.n8n
    networks: [proxy, internal]
    labels:
      - traefik.enable=true
      - traefik.http.routers.n8n.rule=Host(`n8n.${DOMAIN}`)
      - traefik.http.routers.n8n.entrypoints=websecure
      - traefik.http.routers.n8n.tls.certresolver=le
      - traefik.http.services.n8n.loadbalancer.server.port=5678
→
Por que duas redes
A rede internal não tem acesso ao proxy reverso. Ollama e Qdrant estão sozinhos nela — não há como um invasor que passe pelo Traefik acessar diretamente suas APIs. O Open WebUI está conectado às duas redes: recebe o tráfego público pela rede proxy e se comunica com o Ollama pela rede internal.

#3. Configurar o Traefik e o DNS

Antes do primeiro docker compose up, crie três registros DNS do tipo A apontando para o IP público do seu servidor:

chat.votre-domaine.fr
A interface Open WebUI.
n8n.votre-domaine.fr
O editor de fluxos n8n.
traefik.votre-domaine.fr
O painel de controle do Traefik (protegido por autenticação básica).

Gere o hash para a autenticação básica do dashboard do Traefik. Atenção: no .env, duplique os $ (escape no docker-compose).

Hash para autenticação básica
sudo apt install apache2-utils
htpasswd -nb admin VotreMotDePasse | sed -e 's/\$/\$\$/g'
!
Let's Encrypt e limites de taxa
Let's Encrypt limita a 50 certificados por domínio e por semana. Na fase de testes, use --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory para apontar para o servidor de staging. Você fará a troca quando tudo estiver funcionando.

#4. Primeira execução e verificações

  1. 01
    Iniciar a stack
    A partir de /opt/llm-stack, execute docker compose up -d. O Traefik negociará os certificados Let's Encrypt em poucos segundos — monitore os logs.
  2. 02
    Verificar o estado dos serviços
    docker compose ps doit afficher tous les services en running. Aucun ne doit redémarrer en boucle.
  3. 03
    Baixar um primeiro modelo
    docker compose exec ollama ollama pull qwen3.5:9b (6,6 GB, 256k de contexto, o modelo versátil de 8 GB de referência em 2026). Para um modelo mais potente em uma GPU de 24 GB: qwen3.8:27b (≈18 GB de VRAM, 262k de contexto, licença Apache 2.0).
  4. 04
    Testar a interface
    Abra https://chat.votre-domaine.fr. A primeira conta criada é a de administrador. ENABLE_SIGNUP=false bloqueia qualquer cadastro público depois disso.
  5. 05
    Verificar se a GPU está sendo usada
    docker compose exec ollama ollama ps : la colonne PROCESSOR doit afficher 100% GPU.
Logs de Traefik em tempo real
docker compose logs -f traefik | grep -i acme

#5. Fortalecer a segurança

Uma stack pública mal configurada é escaneada em até uma hora. Aqui estão as seis regras não negociáveis para uma implantação de LLM em produção com Docker Compose que não acabará no shodan.io de alguém.

Senhas fortes e únicas
Gere seus segredos com openssl rand -base64 32. Nunca use a senha de exemplo do .env.
Desativar o cadastro público
ENABLE_SIGNUP=false em Open WebUI. Sem isso, qualquer pessoa pode criar uma conta e usar sua GPU.
Cabeçalhos de segurança via middleware Traefik
Adicione um middleware secureHeaders com HSTS, frame-deny, content-type-nosniff. Três linhas, muito impacto.
Limitar recursos por contêiner
Em deploy.resources.limits, defina memory e cpus. Isso impede que um serviço com falha paralise todo o servidor.
Contêineres em modo somente leitura quando possível
Adicione read_only: true no Traefik e no Qdrant. Use tmpfs para pastas de escrita temporárias.
Atualizações mensais
O watchtower automatiza os pulls, mas prefira um cron + tag git explícita para manter o controle das versões em produção.
Middleware de cabeçalhos de segurança
# À ajouter sur le service open-webui en labels:
- traefik.http.routers.chat.middlewares=secure-headers
- traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000
- traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true
- traefik.http.middlewares.secure-headers.headers.frameDeny=true
- traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true
- traefik.http.middlewares.secure-headers.headers.browserXssFilter=true
→
Fail2ban à frente do Traefik
Para bloquear ataques de força bruta à autenticação básica (painel do Traefik, n8n), instale o fail2ban com um filtro que analise os logs do Traefik. 5 tentativas → 1 hora de banimento do IP. Configuração em 10 linhas.

#6. Monitoramento Prometheus + Grafana

Três métricas que você deve obrigatoriamente monitorar em produção: a VRAM usada pelo Ollama, a latência das requisições do Open WebUI e o estado dos certificados Let's Encrypt (o Traefik expõe tudo em /metrics).

Bloco para adicionar ao docker-compose.yml
  prometheus:
    image: prom/prometheus:latest
    restart: unless-stopped
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    networks: [internal]

  nvidia-exporter:
    image: utkuozdemir/nvidia_gpu_exporter:latest
    restart: unless-stopped
    devices: [/dev/nvidiactl, /dev/nvidia0]
    volumes:
      - /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1:/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1:ro
      - /usr/bin/nvidia-smi:/usr/bin/nvidia-smi:ro
    networks: [internal]

  grafana:
    image: grafana/grafana:latest
    restart: unless-stopped
    volumes:
      - grafana_data:/var/lib/grafana
    networks: [proxy, internal]
    labels:
      - traefik.enable=true
      - traefik.http.routers.grafana.rule=Host(`grafana.${DOMAIN}`)
      - traefik.http.routers.grafana.entrypoints=websecure
      - traefik.http.routers.grafana.tls.certresolver=le
      - traefik.http.services.grafana.loadbalancer.server.port=3000
prometheus.yml
global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'traefik'
    static_configs:
      - targets: ['traefik:8080']
  - job_name: 'nvidia'
    static_configs:
      - targets: ['nvidia-exporter:9835']
  - job_name: 'qdrant'
    static_configs:
      - targets: ['qdrant:6333']

No Grafana, importe o dashboard de ID 14574 (NVIDIA GPU Exporter) e o de ID 17346 (Traefik v3). Você terá uma visão completa em 5 minutos: VRAM, temperaturas da GPU, taxa de requisições HTTP por subdomínio, latências P95.

#7. Backup e atualizações

Cinco volumes contêm todas as suas informações críticas: ollama_data (modelos), open_webui_data (contas + conversas + RAG), qdrant_data (vetores), n8n_data (fluxos), traefik_certs (certificados).

/usr/local/bin/backup-llm-stack.sh
#!/bin/bash
set -e
BACKUP_DIR=/var/backups/llm-stack
TS=$(date +%Y%m%d-%H%M)
mkdir -p "$BACKUP_DIR"

for vol in open_webui_data qdrant_data n8n_data traefik_certs; do
  docker run --rm \
    -v llm-stack_${vol}:/data \
    -v "$BACKUP_DIR":/backup \
    alpine tar czf "/backup/${vol}-${TS}.tar.gz" -C /data .
done

find "$BACKUP_DIR" -name '*.tar.gz' -mtime +30 -delete
Cron diário às 3h da manhã
sudo chmod +x /usr/local/bin/backup-llm-stack.sh
echo "0 3 * * * root /usr/local/bin/backup-llm-stack.sh" | sudo tee /etc/cron.d/llm-stack-backup
i
Por que não é feito backup de ollama_data
Os modelos facilmente ocupam dezenas de GB e podem ser baixados novamente com ollama pull. Não é necessário incluí-los nos backups diários — salve a lista em vez disso: docker compose exec ollama ollama list > models.txt.

Para atualizações, priorize a segurança: fixe as versões (image: ollama/ollama:0.5.4 em vez de :latest), faça um snapshot do VPS antes de cada atualização e teste primeiro no ambiente de staging.

Atualização limpa
cd /opt/llm-stack
docker compose pull
docker compose up -d
docker image prune -f

#Solução de problemas

Traefik não gera certificado
Verifique se as portas 80 e 443 estão abertas E se o DNS aponta corretamente para o servidor. docker compose logs traefik | grep -i error revela 90% dos casos (DNS não propagado, limite de taxa, falha no desafio).
Open WebUI exibe 'No models found'
Você ainda não baixou nenhum modelo no Ollama. docker compose exec ollama ollama pull qwen3.5:9b. Verifique também OLLAMA_BASE_URL=http://ollama:11434 (nome do serviço, não localhost).
GPU não detectada no contêiner
nvidia-container-toolkit não instalado ou Docker não reiniciado após a configuração. Teste com docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi.
n8n redireciona em loop para HTTP
WEBHOOK_URL e N8N_PROTOCOL=https devem ser definidos. Sem isso, o n8n acredita que está em HTTP e o navegador se confunde.
Grafana inacessível por trás do Traefik
Adicione GF_SERVER_ROOT_URL=https://grafana.${DOMAIN} e GF_SERVER_SERVE_FROM_SUB_PATH=false à seção environment dele.
Latência que explode com vários usuários
OLLAMA_NUM_PARALLEL muito alto para a VRAM. Reduza para 1 ou 2 de acordo com sua placa. Monitore ollama ps enquanto o sistema estiver sob carga.

#Para se aprofundar

Sua stack está rodando, monitorada e com backup. Três caminhos naturais para profissionalizá-la ainda mais:

Conectar o RAG aos seus documentos
O guia RAG com ChromaDB e Mistral adapta-se sem dificuldades ao Qdrant — mesma lógica de embeddings e chunking, base vetorial diferente.
Automatizar fluxos de trabalho empresariais
O guia Automatizar com n8n e Ollama descreve dez receitas práticas (tradução de e-mails, classificação de leads, resumo de RSS) que aproveitam diretamente sua stack.
Tratar da conformidade
O guia sobre LLM local e RGPD detalha as obrigações legais (registro, prazo de retenção, direitos dos usuários) a serem documentadas para uma implantação em uma empresa.
Este guia ajudou você?

Um comentário, um erro ou uma observação? Avise-nos; isso ajuda a melhorar o guia para todos.