Docker/Postgres/pgvector/Redis
Uma aplicação de IA precisa manter dados, executar consultas e recuperar contexto mesmo depois de reiniciar. Docker ajuda a reproduzir o ambiente, PostgreSQL fornece persistência transacional, pgvector oferece representação e busca vetorial e Redis atende funções de cache e mensageria. A arquitetura não ganha essas propriedades automaticamente ao colocar os nomes em um compose: volumes, migrações, redes e credenciais precisam de decisões explícitas.
Nesta semana você montará API, PostgreSQL com pgvector e Redis em uma stack local. O laboratório usa documentos e credenciais sintéticas e inclui testes reais dos serviços, persistência e migração. Separaremos o que uma demonstração local comprova do que uma operação de produção ainda exigiria, como backup, recuperação, autorização, atualização de imagens e monitoramento.
BashDockerPostgreSQLRedisAo terminar esta aula
- Volumes e migrações têm contratos próprios.
- Serviços precisam de testes além de status running.
- Embeddings exigem dimensão, versão e autorização.
Antes de continuar: Laboratório: AI Gateway
Container, imagem e volume: três ciclos de vida
FundamentosUma imagem descreve o filesystem e a configuração base usados para iniciar um container. O container é uma instância em execução com estado próprio; recriá-lo não preserva automaticamente as alterações gravadas em sua camada gravável. Um volume tem um ciclo de vida separado e pode manter dados quando o container é removido. Para um banco, essa diferença é decisiva: persistir no diretório errado pode gerar uma aparência de durabilidade que desaparece no próximo deploy.
Uma rede Compose permite que serviços se encontrem pelo nome do serviço, como db ou redis. Dentro do container da API, localhost significa a própria API, não seu banco. Publicar uma porta no host é diferente de disponibilizá-la na rede interna. No laboratório, somente a API precisa de uma porta no host; comandos de inspeção entram via docker compose exec. Um container não representa uma barreira absoluta: privilégios, montagens e rede definem o alcance do isolamento.
PostgreSQL e migrações que preservam o contrato
PostgreSQLPostgreSQL oferece transações para agrupar mudanças que devem ocorrer juntas. Em uma operação, registrar documento e seu estado de indexação pode exigir atomicidade para evitar referências sem conteúdo. Uma migração transforma o esquema de uma versão para outra e deve ser rastreada, revisada e aplicada de forma controlada. CREATE TABLE IF NOT EXISTS ajuda a repetir um exemplo inicial, mas não detecta que uma tabela existente possui tipo errado ou índice ausente.
Uma estratégia de migração madura mantém histórico e valida a versão esperada. Evite incluir destruição de dados no caminho automático de inicialização sem plano de reversão. Para testar persistência, insira um registro, reinicie serviços sem remover volumes e confira o registro. Isso prova retenção no cenário, não capacidade de recuperação após corrupção ou perda do disco. Backup só vira evidência de recuperação quando uma restauração é executada e os dados resultantes são verificados.
pgvector: dimensão, distância e busca aproximada
PostgreSQLpgvector adiciona tipos e operações para vetores no PostgreSQL. A dimensão do vetor precisa corresponder ao modelo de embedding adotado; misturar espaços produz comparações sem significado mesmo quando o banco aceita números. Versione embeddingModel, dimensão e pipeline de normalização. A distância cosseno, produto interno e distância euclidiana têm interpretações diferentes, e a escolha precisa ser coerente com o embedding e o índice.
O exemplo usa vetores de três dimensões escritos à mão para testar inserção e ordenação, não qualidade semântica. Busca exata calcula candidatos conforme a consulta; índices aproximados trocam parte da precisão de recuperação por desempenho em determinados cenários. Meça recall e latência no seu conjunto antes de escolher um índice. Filtrar por tenant é parte da autorização e deve acontecer no acesso ao dado; a similaridade vetorial não decide quem pode ler um documento. Um resultado próximo também não prova que a resposta gerada é verdadeira.
Redis como cache ou estado operacional
RedisRedis pode atender necessidades diferentes, incluindo cache e estruturas para streaming. Para cache, defina TTL, chave e invalidação como estudado na semana 40. Para estado operacional, defina durabilidade e recuperação compatíveis com o risco: configurações de persistência e replicação alteram o comportamento diante de falhas. Usar Redis para cache descartável é uma decisão diferente de usá-lo como única fonte de um job crítico.
A stack local habilita AOF para exercitar persistência, mas isso não garante ausência de perda em toda falha. Não trate cache como fonte normativa de políticas nem como substituto automático de uma transação no banco. Teste misses, expiração e indisponibilidade. Se Redis cair, a API deve ter comportamento definido: consultar a fonte, limitar carga ou retornar indisponibilidade. Um sistema que inicia sem cache e depois trava em toda requisição não demonstrou resiliência apenas porque o container está healthy.
Secrets, readiness e fronteiras de acesso
FundamentosCompose secrets disponibiliza um arquivo ao serviço autorizado, evitando copiar a credencial para a imagem. A aplicação precisa ler esse arquivo ou usar o mecanismo compatível da imagem, como uma variável com sufixo _FILE quando suportada. Isso não criptografa automaticamente o arquivo local nem impede administradores do host de acessá-lo. Proteja a origem do segredo, permissões e logs; mantenha arquivos reais fora do repositório.
↗ Manage secrets securely in Docker Compose↗ What is a container?
Liveness indica se o processo está vivo; readiness indica se ele está apto a atender o trabalho definido. Uma API que responde /health sem consultar banco demonstra liveness, não disponibilidade de todas as dependências. depends_on com healthcheck ajuda na ordem inicial, mas não substitui retry e tratamento de falhas durante a execução. Declare quais endpoints dependem de quais serviços. A verificação desta semana combina healthchecks com comandos SQL e Redis, porque só o status dos containers não comprova os contratos de persistência e consulta.
↗ Manage secrets securely in Docker Compose↗ What is a container?
Stack local com testes dos serviços
AvaliaçãoCrie uma pasta vazia e rode o script abaixo em Bash; no Windows pode ser WSL ou Git Bash com acesso ao Docker. O exemplo publica uma API mínima de liveness, mantém banco e Redis na rede interna e executa consultas diretamente nos containers. Não há cliente de embedding nem chamada de modelo. Registre versões das imagens realmente baixadas e fixe seus digests ao entregar a reprodução.
mkdir -p secrets
printf '%s' 'senha-sintetica-do-lab' > secrets/db_password.txt
cat > compose.yaml <<'YAML'
services:
api:
image: node:22-alpine
working_dir: /app
volumes: ["./api.cjs:/app/api.cjs:ro"]
command: ["node", "api.cjs"]
ports: ["127.0.0.1:3000:3000"]
depends_on:
db: {condition: service_healthy}
redis: {condition: service_healthy}
db:
image: pgvector/pgvector:pg17
environment:
POSTGRES_USER: lab
POSTGRES_DB: lab
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets: [db_password]
volumes: ["pgdata:/var/lib/postgresql/data"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U lab -d lab"]
interval: 2s
timeout: 2s
retries: 20
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
volumes: ["redisdata:/data"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 2s
timeout: 2s
retries: 20
secrets:
db_password:
file: ./secrets/db_password.txt
volumes:
pgdata:
redisdata:
YAML
cat > api.cjs <<'JS'
require('node:http').createServer((req,res)=>{
res.setHeader('Content-Type','application/json');
res.end(JSON.stringify({alive:true,path:req.url}));
}).listen(3000,'0.0.0.0');
JS
docker compose up -d --wait
docker compose exec -T db psql -U lab -d lab -v ON_ERROR_STOP=1 <<'SQL'
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS docs(id text PRIMARY KEY, tenant text NOT NULL, embedding vector(3));
INSERT INTO docs VALUES ('d1','t1','[1,0,0]'),('d2','t2','[0,1,0]') ON CONFLICT DO NOTHING;
SELECT id FROM docs WHERE tenant='t1' ORDER BY embedding <=> '[1,0,0]' LIMIT 1;
SQL
docker compose exec -T redis redis-cli SET lab:marker presente
docker compose exec -T redis redis-cli GET lab:marker
docker compose exec -T api node -e "fetch('http://localhost:3000/health').then(r=>r.json()).then(console.log)"
docker compose restart db redis
docker compose exec -T db pg_isready -U lab -d lab
docker compose exec -T db psql -U lab -d lab -c "SELECT count(*) FROM docs;"
docker compose exec -T redis redis-cli GET lab:markerA consulta vetorial retorna d1 no tenant t1, Redis devolve presente e a API retorna alive:true. Depois do restart, espere readiness antes de consultar se pg_isready ainda reportar indisponibilidade. O count esperado é dois. Esses testes não provam que a API consulta o banco: ela é apenas um processo mínimo no ambiente. A integração de negócio é uma etapa adicional, e a entrega deve distinguir os dois contratos.
Exercício aplicado
Após recriar o container do banco, todos os documentos somem, mas /health continua respondendo. Explique a falha e mostre como verificar a correção.
- Confira o volume e o caminho de dados da imagem.
- Diferencie health do processo de readiness dos dados.
- Insira marcador, recrie sem remover volume e consulte.
- Registre migração, extensão e quantidade de documentos.
Abrir resolução comentada
A API mínima mede liveness e não consulta dados. O desaparecimento indica que o armazenamento não foi preservado no caminho correto ou que o volume foi removido. Corrija a montagem e execute a prova de retenção com marcador.
Persistência após recriação é uma evidência específica. Uma restauração de backup ainda é necessária para testar recuperação quando o volume ou host é perdido. Não transforme um resultado local de health em uma garantia de durabilidade.
mkdir -p secrets
printf '%s' 'senha-sintetica-do-lab' > secrets/db_password.txt
cat > compose.yaml <<'YAML'
services:
api:
image: node:22-alpine
working_dir: /app
volumes: ["./api.cjs:/app/api.cjs:ro"]
command: ["node", "api.cjs"]
ports: ["127.0.0.1:3000:3000"]
depends_on:
db: {condition: service_healthy}
redis: {condition: service_healthy}
db:
image: pgvector/pgvector:pg17
environment:
POSTGRES_USER: lab
POSTGRES_DB: lab
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets: [db_password]
volumes: ["pgdata:/var/lib/postgresql/data"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U lab -d lab"]
interval: 2s
timeout: 2s
retries: 20
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
volumes: ["redisdata:/data"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 2s
timeout: 2s
retries: 20
secrets:
db_password:
file: ./secrets/db_password.txt
volumes:
pgdata:
redisdata:
YAML
cat > api.cjs <<'JS'
require('node:http').createServer((req,res)=>{
res.setHeader('Content-Type','application/json');
res.end(JSON.stringify({alive:true,path:req.url}));
}).listen(3000,'0.0.0.0');
JS
docker compose up -d --wait
docker compose exec -T db psql -U lab -d lab -v ON_ERROR_STOP=1 <<'SQL'
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS docs(id text PRIMARY KEY, tenant text NOT NULL, embedding vector(3));
INSERT INTO docs VALUES ('d1','t1','[1,0,0]'),('d2','t2','[0,1,0]') ON CONFLICT DO NOTHING;
SELECT id FROM docs WHERE tenant='t1' ORDER BY embedding <=> '[1,0,0]' LIMIT 1;
SQL
docker compose exec -T redis redis-cli SET lab:marker presente
docker compose exec -T redis redis-cli GET lab:marker
docker compose exec -T api node -e "fetch('http://localhost:3000/health').then(r=>r.json()).then(console.log)"
docker compose restart db redis
docker compose exec -T db pg_isready -U lab -d lab
docker compose exec -T db psql -U lab -d lab -c "SELECT count(*) FROM docs;"
docker compose exec -T redis redis-cli GET lab:markerComo conferir seu resultado
- API, SQL e Redis passam em testes separados.
- Vector(3) rejeita dimensão incompatível.
- Dados permanecem após down/up sem remover volumes.
- Segredo é montado apenas no serviço autorizado.
Aplique em um problema novo
Primeiro resolva sem consultar a resposta. Explique suas decisões e guarde a evidência. A conclusão de leitura é independente desta autoavaliação.
Confira seus pré-requisitos
- Distinguir imagem/container/volume.
- Interpretar readiness e persistência.
Container recriado perdeu documentos, mas /health responde 200. Banco usa filesystem efêmero. Corrija diagnóstico.
Conferir raciocínio e critérios de domínio
Dados duráveis devem ficar em volume/armazenamento configurado; imagem não é dataset.
Liveness do HTTP não prova banco/migração/consulta; readiness verifica dependências necessárias.
Secrets e portas restritas protegem ambiente; não expor banco só para teste.
Evidências para autoavaliação ou revisão por pares
- Persistência: Dados duráveis devem ficar em volume/armazenamento configurado; imagem não é dataset.
- Saúde: Liveness do HTTP não prova banco/migração/consulta; readiness verifica dependências necessárias.
- Acesso: Secrets e portas restritas protegem ambiente; não expor banco só para teste.
Um erro frequente
Health 200 prova stack pronta.
Liveness não prova readiness de banco e migração.
Teste sua compreensão
Responda com suas palavras antes de abrir o comentário. Saber explicar uma decisão é parte do domínio.
1. localhost da API aponta para o banco?
2. Vetor próximo demonstra verdade da resposta?
Não.
Similaridade de representação não comprova suporte factual.
3. Compose secrets criptografa o arquivo local?
Não automaticamente.
Ele controla disponibilização por arquivo ao serviço; proteja a origem no host.
Seu progresso fica salvo neste navegador. Concluir a leitura não substitui demonstrar o domínio nos exercícios.
Referências e aprofundamento
Documentação oficial e trabalhos originais. As referências registram o escopo e as limitações para você conferir o que sustentam.
- What is a container?
Docker • consulta: 2026-10-06
DockerContêineres, isolamento de processos, portabilidade e comparação com máquinas virtuais.
Limites: Isolamento de contêiner não implica sandbox infalível; validar rede, mounts, identidade e permissões.
- Manage secrets securely in Docker Compose
Docker • consulta: 2026-10-06
DockerSecrets montados em arquivos e concessão explícita por serviço.
Limites: O arquivo de origem também precisa de proteção; suporte apresentado para Linux containers.
- Transactions
PostgreSQL • consulta: 2026-10-06
PostgreSQLTransações, atomicidade, persistência, COMMIT, ROLLBACK e savepoints.
Limites: Transação local não cria atomicidade entre banco, fila e serviços externos.
- pgvector: vector similarity search for Postgres
pgvector • consulta: 2026-10-06
PostgreSQLTipos vetoriais, busca exata/aproximada e índices HNSW/IVFFlat.
Limites: Busca aproximada troca recall por desempenho; medir plano de execução, filtros e recall.
- Redis streaming
Redis • consulta: 2026-10-06
RedisAPIsStreams, logs append-only, consumer groups, acknowledgments e distribuição entre workers.
Limites: Entrega at-least-once requer tratamento de duplicações; Redis Streams e uma fila de jobs têm semânticas diferentes.
- Cache-Aside Pattern
Microsoft • consulta: 2026-10-06
FundamentosCarregamento sob demanda, invalidação e expiração de cache.
Limites: Cache pode ficar desatualizado; não garante consistência entre origem e cópia.