Pular para o conteúdo
Docker

Evolution API: guardando os anexos num S3

Ilustração colorida de um unicórnio de crina luminosa ao lado de uma esteira por onde passam envelopes, fotos e pergaminhos que voam para dentro de um baú de pedra, com uma coruja carteiro e um castelo de velas flutuantes ao fundo

Olá meus Unicórnios! 🦄✨

A Evolution API funcionando é uma alegria: o WhatsApp virou uma API, você manda uma foto por curl e ela aparece na conversa. Aí passa uma semana, passa um mês, e chega aquela pergunta desconfortável: onde é que estão indo parar todos esses arquivos? 🤔

Foto de produto, áudio de cliente, PDF de nota, print que alguém mandou. Cada um deles é um arquivo, e por padrão eles ficam com a Evolution: no disco do contêiner e na base de dados dela. Funciona, mas é o tipo de coisa que só dá problema quando já é tarde: o disco da VPS enchendo, o backup do banco ficando gigante, e o dia em que você quiser trocar de servidor descobrindo que os arquivos estão amarrados naquela máquina.

A Evolution já tem a solução pronta, e é bem elegante: ela sabe mandar toda mídia para um S3. E como o S3 virou padrão de mercado, isso não significa obrigatoriamente pagar a Amazon: um MinIO na sua própria VPS fala exatamente a mesma língua. 📦

Este artigo é o passo a passo de ligar os dois. Não é longo, mas tem uma armadilha que engana quase todo mundo, e ela é do tipo pior: você configura tudo certinho, reinicia, e não acontece absolutamente nada. Sem erro, sem aviso, sem pista. Vamos por partes. 🙃

📋 O que você precisa ter antes de começar

Este tutorial começa no meio da história, então vale dizer de onde ele parte. Você precisa de duas coisas já funcionando:

  • A Evolution API rodando, com uma instância já conectada ao WhatsApp (aquela do QR Code). Aqui ela vive em /opt/evolution, com Docker Compose.
  • Um servidor S3. Se você ainda não tem, o caminho mais barato é subir um MinIO na mesma VPS: ele é um servidor de armazenamento de objetos que implementa a API do S3, roda num contêiner e não cobra por GB.

Se você não faz ideia do que é "armazenamento de objetos", a versão de bolso é esta: em vez de pastas dentro de pastas, existem buckets (baldes com nome) e objetos (os arquivos lá dentro). A Amazon popularizou isso com o S3, a API dela virou o padrão, e o MinIO implementa essa mesma API. Na prática: onde a documentação diz "S3", o MinIO serve.

Aqui eu usei um MinIO na mesma VPS da Evolution, com a API na porta 9000 e o painel web na 9001.

🪣 Primeiro o bucket, e ele não nasce sozinho

Antes de qualquer configuração, uma coisa precisa existir: o bucket. E esta é a primeira pegadinha, pequena mas certeira:

Criar é um clique no painel do MinIO (menu Buckets, botão Create Bucket) ou uma linha no terminal, se você já tem o mc, o programa de linha de comando do MinIO, configurado com um apelido:

mc mb meuminio/evolution
Bucket created successfully `meuminio/evolution`.

Eu chamei o meu de evolution, mas o nome é livre. Livre dentro das regras do S3, que pegam muita gente: só letras minúsculas, números, ponto e hífen. Nada de maiúscula, nada de underline. Um Anexos_WhatsApp é recusado duas vezes de uma só.

No painel, os buckets aparecem assim:

Object Browser do MinIO listando os buckets, entre eles o bucket evolution, com a coluna de objetos e de tamanho

🔑 Uma credencial só para a Evolution

Agora a credencial que a Evolution vai usar para falar com o S3. Você poderia usar o usuário administrador do MinIO, e vai funcionar. Mas não faça isso. 😅

O motivo é simples: a senha do administrador é a chave do servidor inteiro, e ela vai ficar escrita num arquivo de configuração. Se um dia você precisar trocá-la, tudo que a usava para de funcionar junto. O certo é criar uma chave de acesso separada, que serve para programas e pode ser revogada sozinha.

No painel do MinIO isso fica em Access Keys, botão Create access key. Pelo terminal, é um comando:

mc admin user svcacct add meuminio SEU_USUARIO_ADMIN

Ele devolve o par que você vai usar daqui a pouco:

Access Key: SUA_CHAVE_AQUI
Secret Key: SEU_SEGREDO_AQUI
Expiration: no-expiry

🧭 Onde a Evolution enxerga o seu S3

Antes de escrever a configuração, uma pergunta que parece boba e é a causa da maioria dos problemas: qual endereço a Evolution deve usar para chegar no MinIO?

Parece óbvio, mas não é, porque quem faz a conexão não é você: é o programa de dentro do contêiner. E o mundo visto de lá dentro é diferente do seu.

Repare no que acontece quando eu tento, de dentro do contêiner da Evolution, alcançar o MinIO pelo nome do contêiner dele:

docker exec evolution-api wget -S -O /dev/null http://minio:9000/
wget: bad address 'minio:9000'

"Endereço ruim." E o motivo é bom de entender, porque explica metade dos perrengues com Docker: o nome do contêiner só funciona como endereço entre contêineres da mesma rede. Como a Evolution e o MinIO foram criados por dois arquivos docker-compose.yml diferentes, o Docker montou uma rede para cada, e um não enxerga o nome do outro.

Você pode conferir isso a qualquer momento:

docker network ls
NETWORK ID     NAME                DRIVER    SCOPE
6070f6a73066   bridge              bridge    local
c1d97334299f   evolution_default   bridge    local
def08c3fd90d   minio_default       bridge    local

Duas redes, evolution_default e minio_default, e o nome de uma não resolve na outra.

Como cada contêiner enxerga o host (a VPS em si), e o MinIO publicou a porta 9000 no host, o caminho que sempre funciona é passar por lá. O endereço do host, visto de dentro de um contêiner, é 172.17.0.1 na instalação padrão do Docker:

docker exec evolution-api wget -S -O /dev/null http://172.17.0.1:9000/
HTTP/1.1 403 Forbidden

Esse 403 parece ruim e é ótima notícia. 🎉 Ele quer dizer "tem alguém aqui, e essa pessoa está pedindo credencial" (é o AccessDenied do S3). O que a gente queria descobrir era se dá para chegar, e dá.

Então o endereço que vai na configuração depende de onde o seu S3 está. Os três casos:

  • MinIO na mesma VPS, em contêiner separado: use 172.17.0.1, o host visto de dentro.
  • MinIO em outra máquina: use o IP dela, normalmente.
  • MinIO com domínio e HTTPS: use o domínio, sem https:// na frente (isso é assunto da próxima seção).

⚙️ As variáveis, uma a uma

Agora sim, a configuração. Ela mora no arquivo .env, dentro da pasta da Evolution. Entre nela primeiro:

cd /opt/evolution

O cd é o comando de entrar numa pasta. Daqui em diante, tudo roda de dentro dela. Antes de mexer, faça uma cópia de segurança do arquivo, que é grátis e um dia salva:

cp .env .env.backup

Agora abra o arquivo para editar. No Ubuntu, o editor mais simples é o nano, que já vem instalado:

nano .env

A tela do terminal vira o editor, com o conteúdo atual do arquivo. Desça até o fim com as setas e acrescente estas oito linhas:

S3_ENABLED=true
S3_ACCESS_KEY=SUA_CHAVE_AQUI
S3_SECRET_KEY=SEU_SEGREDO_AQUI
S3_BUCKET=evolution
S3_PORT=9000
S3_ENDPOINT=172.17.0.1
S3_USE_SSL=false
S3_SAVE_VIDEO=true

Para colar no terminal, use Ctrl + Shift + V ou clique com o botão direito. O Ctrl + V de sempre não funciona aqui, e essa é a primeira coisa que trava quem está começando. 🙃

Para salvar e sair, são três teclas em sequência:

Ctrl + O     grava o arquivo (a letra O, de "output")
Enter        confirma o nome
Ctrl + X     sai do editor

O rodapé do nano mostra esses atalhos o tempo todo, com o ^ significando Ctrl: o ^O é Ctrl + O e o ^X é Ctrl + X.

Para conferir que ficou tudo lá, o cat mostra o conteúdo de um arquivo na tela:

cat .env

E o que cada linha faz:

VariávelO que ela diz
S3_ENABLEDLiga o recurso. Sem true aqui, o resto é ignorado.
S3_ACCESS_KEYO usuário da credencial (a access key que você criou).
S3_SECRET_KEYA senha dessa credencial (o secret).
S3_BUCKETO nome do bucket, que precisa existir antes.
S3_PORTA porta do servidor S3.
S3_ENDPOINTO endereço, sem http:// e sem a porta.
S3_USE_SSLSe a conexão é por HTTPS (true) ou HTTP puro (false).
S3_SAVE_VIDEOSe vídeo também vai para o S3, e não só foto, áudio e documento.

Dois detalhes que custam tempo se passarem batido:

O S3_ENDPOINT quer só o endereço. Não escreva http://172.17.0.1:9000 ali: o protocolo sai do S3_USE_SSL e a porta sai do S3_PORT. São três informações separadas de propósito, e juntá-las numa só quebra a conexão.

O S3_SAVE_VIDEO existe porque vídeo é o tipo que mais pesa. Deixe true se quiser tudo no S3, que é o normal.

🔒 Porta 9000 sem SSL, ou porta 443 com domínio

As duas variáveis que mais geram dúvida são o S3_PORT e o S3_USE_SSL, porque a resposta certa depende de como o seu S3 está publicado. E o erro clássico é misturar as duas situações. 😬

Situação 1: o MinIO cru, na rede ou na própria VPS. É o que eu fiz aqui. A porta 9000 do MinIO responde em HTTP puro, sem certificado:

S3_PORT=9000
S3_ENDPOINT=172.17.0.1
S3_USE_SSL=false

Situação 2: o MinIO atrás de um domínio com HTTPS. Se você pôs um proxy reverso na frente, com certificado, aí o endereço público é o domínio e a porta é a 443, que é a porta padrão do HTTPS:

S3_PORT=443
S3_ENDPOINT=s3.seudominio.com.br
S3_USE_SSL=true

Repare que o domínio vai sem o https://. Quem informa o protocolo é o S3_USE_SSL=true, e escrever o esquema no endereço faz a Evolution montar uma URL torta.

A regra para não errar é ler as três linhas como uma frase só: "fale com este endereço, na esta porta, usando este protocolo". Combinações como porta 9000 com S3_USE_SSL=true falham porque a Evolution tenta uma conversa criptografada com quem está respondendo em texto puro.

💀 A armadilha: o .env que não chega no contêiner

Se você só for ler um pedaço deste artigo, leia este. 🙏

Com o .env salvo, o instinto é reiniciar e testar. Foi o que eu fiz:

docker compose up -d
Container evolution-postgres  Running
Container evolution-redis     Running
Container evolution-api       Running

E aí, nada. Nenhuma mídia no bucket, nenhum erro no log, nenhuma mudança na resposta da API. Como se eu não tivesse escrito nada. 😳

Tem duas coisas erradas nessa saída, e as duas são silenciosas.

A primeira está na palavra Running. Repare que o Docker não disse Recreated, disse Running: ele olhou e concluiu que não havia nada para fazer. Mudar o .env, sozinho, não é motivo suficiente para ele recriar o contêiner.

E a segunda é a causa raiz. Este comando pergunta ao contêiner quais variáveis S3_ ele realmente recebeu:

docker exec evolution-api env | grep S3_

A resposta foi vazia. Nenhuma linha. As oito variáveis estavam no arquivo, o arquivo estava na pasta certa, e mesmo assim elas não existiam lá dentro.

O motivo está no docker-compose.yml. Este é o formato que a instalação padrão da Evolution usa:

services:
  evolution-api:
    image: evoapicloud/evolution-api:v2.3.7
    environment:
      SERVER_URL: http://SEU_IP_AQUI:8080
      AUTHENTICATION_API_KEY: ${AUTHENTICATION_API_KEY}
      DATABASE_ENABLED: "true"
      # ... e assim por diante, uma linha para cada variavel

Está tudo listado uma a uma no bloco environment:. E é isso que muda tudo: quando o compose escreve as variáveis nominalmente assim, ele passa para o contêiner exatamente as que estão nessa lista, e mais nenhuma. O .env continua sendo lido, mas só serve para preencher os ${...} que aparecem no próprio compose. O que não foi declarado ali fica de fora, em silêncio.

É por isso que o erro é tão cruel: o arquivo certo, no lugar certo, com o conteúdo certo, e nada acontece. Não há erro possível, porque, do ponto de vista do Docker, você não pediu nada.

🔧 Declarando as variáveis no compose

A correção é acrescentar as oito linhas no environment:, encadeando cada uma com o valor que vem do .env. Abra o arquivo:

nano docker-compose.yml

E acrescente estas linhas dentro do bloco environment: do serviço evolution-api, logo abaixo das que já estão lá:

      S3_ENABLED: ${S3_ENABLED}
      S3_ACCESS_KEY: ${S3_ACCESS_KEY}
      S3_SECRET_KEY: ${S3_SECRET_KEY}
      S3_BUCKET: ${S3_BUCKET}
      S3_PORT: ${S3_PORT}
      S3_ENDPOINT: ${S3_ENDPOINT}
      S3_USE_SSL: ${S3_USE_SSL}
      S3_SAVE_VIDEO: ${S3_SAVE_VIDEO}

Aquele ${S3_BUCKET} quer dizer "pegue o valor da variável S3_BUCKET do .env". É o mesmo padrão que o arquivo já usa para a senha do banco, então você está seguindo o estilo da casa, não inventando um. E é bom que seja assim: a credencial fica só no .env, e o docker-compose.yml pode até ser mostrado para alguém sem revelar segredo nenhum.

Antes de subir, peça ao próprio Docker para conferir o arquivo. São dez segundos que evitam meia hora:

docker compose config

Se ele imprimir o conteúdo de volta, o arquivo está válido. Se a indentação estiver errada, ele reclama apontando a linha exata. Agora sim:

docker compose up -d
Container evolution-api  Recreate
Container evolution-api  Recreated
Container evolution-api  Starting
Container evolution-api  Started

Recreated. Essa é a palavra que a gente queria ver. 🎉 Diferente do Running de antes, ela diz que o contêiner foi refeito com a configuração nova.

E a conferência que fecha o assunto:

docker exec evolution-api env | grep S3_
S3_ENABLED=true
S3_SAVE_VIDEO=true
S3_ACCESS_KEY=SUA_CHAVE_AQUI
S3_ENDPOINT=172.17.0.1
S3_BUCKET=evolution
S3_USE_SSL=false
S3_PORT=9000

Agora elas existem lá dentro. A ordem sai embaralhada e isso é normal, não significa nada.

✅ O log que diz que deu certo

A aplicação demora uns 30 segundos para subir de verdade depois do up -d, então dê um tempo antes de olhar. O log é quem conta a verdade:

docker logs evolution-api | tail -20

A linha que interessa é esta, e ela é bem explícita:

INFO  [S3 Service]  S3 Bucket evolution - ON
LOG   [SERVER]      HTTP - ON: 8080

S3 Bucket evolution - ON. A Evolution conseguiu falar com o S3, encontrou o bucket e ligou o recurso. Se você quiser ir direto ao ponto sem ler o log inteiro:

docker logs evolution-api | grep "S3"

🩺 E quando dá errado: o erro que não explica nada

Vale ver como é a falha, porque ela é bem menos informativa do que se esperaria. Para provocá-la de propósito, troquei o S3_ENDPOINT por minio, aquele nome de contêiner que não resolve entre redes diferentes.

O log ficou assim:

ERROR  [S3 Service]  S3 ERROR:
ERROR  [S3 Service]

É isso. "S3 ERROR:" e uma segunda linha vazia, onde deveria estar o detalhe do que aconteceu. 🫠 Nada sobre endereço, nada sobre credencial, nada sobre bucket.

Pior: a aplicação sobe normalmente e continua atendendo. Mandei uma imagem com o endereço errado e ela chegou no WhatsApp igualzinho, sem reclamação nenhuma. A única diferença é que a resposta da API voltou sem o campo do S3, e o arquivo não apareceu no bucket.

Como o erro não diz o motivo, o jeito é eliminar candidatos na mão. Na ordem em que mais dão problema:

  • O endereço não é alcançável. Teste de dentro do contêiner, como fizemos lá em cima, com o wget.
  • O bucket não existe, ou o nome está escrito diferente do que está no S3_BUCKET.
  • A credencial está errada. Copiar o segredo com um espaço no fim é clássico.
  • A porta e o SSL não combinam (9000 com true, ou 443 com false).

📤 A prova: mandando uma mídia

Com o ON no log, hora de provar. Mande uma imagem pela API, como você faria normalmente:

curl -X POST "http://SEU_IP_AQUI:8080/message/sendMedia/tutorial" \
  -H "apikey: SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "SEU_DESTINO_AQUI",
    "mediatype": "image",
    "mimetype": "image/png",
    "fileName": "prova.png",
    "caption": "testando o S3",
    "media": "https://exemplo.com.br/uma-imagem.png"
  }'

Nada nesse comando mudou por causa do S3, e isso é de propósito: a configuração é invisível para quem usa a API. O que muda é a resposta, que agora traz um campo novo:

{
    "key": {
        "remoteJid": "SEU_DESTINO_AQUI",
        "fromMe": true,
        "id": "3EB0317A6C3BE67159EAB4"
    },
    "status": "PENDING",
    "message": {
        "mediaUrl": "http://172.17.0.1:9000/evolution/evolution-api/ID-DA-SUA-INSTANCIA/SEU_DESTINO_AQUI/3EB0317A6C3BE67159EAB4/imageMessage/3EB0317A6C3BE67159EAB4.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=SUA_CHAVE_AQUI&X-Amz-Date=20260809T232411Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=7ae20560f11cdbb00ad584a6a1ae57ce"
    },
    "messageType": "imageMessage"
}

Aquele mediaUrl é o troféu. 🏆 Ele não existia antes, e é a Evolution dizendo "guardei o arquivo, e ele está aqui".

Vale reparar em três coisas dentro dessa URL:

A primeira é o caminho, que não é um monte de arquivo jogado numa pasta só. A Evolution organiza por instância, depois por conversa, depois por mensagem, e no fim pelo tipo (imageMessage, documentMessage, audioMessage). Achar um arquivo específico depois vira uma questão de seguir o caminho.

A segunda é o X-Amz-Signature: é um link assinado. O bucket continua privado, e o que dá acesso é a assinatura embutida na URL. Quem pedir o mesmo arquivo sem ela leva um erro de permissão.

A terceira é o X-Amz-Expires=604800, que são sete dias em segundos. Esse link expira. Ele serve para você usar agora, não para gravar no seu banco de dados como se fosse um endereço permanente. Guarde o caminho do arquivo, e gere um link novo quando precisar.

🎯 O arquivo dentro do bucket

A prova final é olhar o bucket. Pelo terminal:

mc ls -r meuminio/evolution
[2026-08-09 23:24:11]  1.6KiB  .../imageMessage/3EB0317A6C3BE67159EAB4.png
[2026-08-09 23:24:23]   13KiB  .../documentMessage/manual.pdf

E no painel do MinIO, navegando pelas pastas até o fim:

Object Browser do MinIO dentro do bucket evolution, mostrando o arquivo manual.pdf de 13 KiB na pasta documentMessage

Ali está o manual.pdf que eu mandei, com 13 KiB, dentro da pasta documentMessage. 🎉

E olha um detalhe simpático: o documento manteve o nome original. Foto e áudio ganham o identificador da mensagem como nome, porque não têm nome de verdade, mas documento preserva o que veio. Isso ajuda muito na hora de procurar alguma coisa no meio de milhares de arquivos.

Se você quiser conferir que o link funciona de verdade, e não só que a URL existe, é só baixar:

curl -o baixado.png -w "HTTP %{http_code}  %{size_download} bytes\n" "COLE_A_MEDIAURL_AQUI"
HTTP 200  1657 bytes

Baixou, com o tamanho certo. O arquivo está lá e está inteiro. ✅

📥 Recebido também vai, e é o que mais importa

Até aqui eu falei de envio, porque é o mais fácil de testar. Mas o recurso vale para os dois lados, e o lado de receber costuma ser o que enche o disco de verdade: você controla quantas fotos manda, não controla quantas te mandam. 😅

Quando alguém te envia uma mídia, a Evolution baixa o arquivo do WhatsApp, guarda no S3, e o evento que chega no seu webhook traz o mesmo mediaUrl apontando para o seu bucket. Isso muda uma coisa prática no seu sistema:

Sem S3, para pegar uma mídia recebida você precisa chamar a Evolution pedindo o conteúdo do arquivo, que vem codificado em base64 (aquele texto gigante). Com S3, você já recebe uma URL de onde baixar. É mais leve para todo mundo, e o arquivo binário para de trafegar pela sua API.

🗄️ O que fica na base de dados

Uma pergunta natural: se o arquivo foi para o S3, o que sobra no banco? Dá para espiar, porque a Evolution cria uma tabela só para isso:

docker exec evolution-postgres \
  psql -U evolution -d evolution \
  -c 'select "fileName", type, mimetype from "Media" order by id desc limit 3;'
              fileName                    |      type       |    mimetype
------------------------------------------+-----------------+-----------------
 .../documentMessage/manual.pdf           | documentMessage | application/pdf
 .../imageMessage/3EB0317A6C...EAB4.png   | imageMessage    | image/png

Repare que o que o banco guarda é o caminho e o tipo, não o arquivo. É exatamente o que a gente queria: o banco fica com o catálogo, leve e rápido de fazer backup, e o S3 fica com o peso.

E aqui mora uma última pegadinha, que eu descobri sem querer. Lembra do teste com o endereço errado, que falhou? Aquela mensagem criou linha na tabela mesmo assim. Comparando os dois lados:

# quantas midias a base acha que existem
docker exec evolution-postgres psql -U evolution -d evolution -t \
  -c 'select count(*) from "Media";'

# quantos arquivos existem de fato no bucket
mc ls -r meuminio/evolution | wc -l
4
3

Quatro na base, três no bucket. 😳 O registro do envio que falhou ficou lá, apontando para um arquivo que nunca chegou. Pedindo aquele arquivo específico, o MinIO é direto:

mc: <ERROR> Unable to stat ... Object does not exist.

A lição prática: a existência do registro no banco não garante a existência do arquivo. Se o seu sistema for baixar mídia a partir dessa tabela, trate o "arquivo não existe" como um caso normal, e não como algo impossível. É o mesmo cuidado que se toma com qualquer referência a arquivo externo, só que aqui a falha vem de um endereço mal configurado que não gritou.

📼 As mídias que já estavam lá

A pergunta que sempre vem depois que tudo funciona: e o que já existia?

A resposta é curta: fica onde está. A configuração vale para o que chegar dali em diante, e não existe migração automática. A Evolution não sai varrendo o histórico para reenviar arquivo antigo, e nem seria bom que saísse: imagine ela decidindo processar meses de conversa sozinha, numa VPS pequena. 😬

Então o certo é encarar isso como uma linha divisória: antes de hoje, as mídias estão no volume do Docker e na base; a partir de hoje, elas vão para o bucket. Se o passado for grande e realmente importar, é um trabalho manual e à parte, com muito cuidado.

A boa notícia é que o custo de ligar isso é baixo. Com os dois de pé e o S3 funcionando, o consumo:

docker stats --no-stream
NAME            CPU %     MEM USAGE / LIMIT
evolution-api   0.00%     133.8MiB / 7.755GiB
minio           1.19%     270.6MiB / 7.755GiB

O MinIO em pouco mais de 270 MB de RAM e a Evolution em 133 MB. Numa VPS de 7,8 GB, os dois juntos não chegam a 6% da memória.

🧾 O resumo do que quebra

Se você chegou aqui e alguma coisa não funcionou, é quase certo que seja um destes quatro, na ordem em que eles aparecem:

SintomaCausa
Nada acontece, nenhum erro, nenhuma linha de S3 no logAs variáveis não foram declaradas no environment: do compose
docker compose up -d diz Running em vez de RecreatedO Docker não viu motivo para recriar; mexer só no .env não basta
S3 ERROR: sem detalhe nenhumEndereço inalcançável, bucket inexistente, credencial errada, ou porta e SSL trocados
Mensagem chega no WhatsApp, mas não vem mediaUrlO S3 está quebrado e a Evolution seguiu em frente sem ele

E o comando que resolve metade das dúvidas, o que eu mais usei escrevendo isto aqui:

docker exec evolution-api env | grep S3_

Ele pergunta ao contêiner, não ao arquivo. E é a diferença entre o que você acha que configurou e o que a aplicação realmente recebeu. 🔍

No fim, o que essa configuração entrega é uma separação que faz sentido: a Evolution cuida do WhatsApp, o S3 cuida dos arquivos, e cada um faz o que sabe. O disco da VPS para de crescer sozinho, o backup do banco volta a ser um backup de banco, e no dia em que você quiser trocar de servidor os arquivos não vão junto na mudança, porque nunca estiveram amarrados a ela. 🦄

Por hoje é só, meus unicórnios! 🦄✨

Que a magia do arco-íris continue brilhando em suas vidas! Até mais! 🌈🌟

Perguntas frequentes

Por que as variáveis S3 não fazem efeito mesmo estando no .env?
Porque o docker-compose.yml da Evolution lista as variáveis uma a uma no bloco environment:. O que está no .env e não foi declarado ali fica de fora e nunca chega ao contêiner. Confira com docker exec evolution-api env | grep S3_: se não sair nada, é isso.
Como saber se o S3 da Evolution está funcionando?
Dois sinais. No log, logo depois de subir, tem de aparecer S3 Bucket seu-bucket - ON. E na resposta de um envio de mídia aparece um campo novo, o mediaUrl, com o endereço do arquivo no seu S3. Sem S3 ligado esse campo simplesmente não existe.
Qual valor usar em S3_PORT e S3_USE_SSL?
Depende de como o seu S3 está publicado. MinIO cru na rede, sem HTTPS, é S3_PORT=9000 e S3_USE_SSL=false. Se você pôs um domínio com certificado na frente, é S3_PORT=443 e S3_USE_SSL=true. Misturar os dois (porta 9000 com SSL ligado, por exemplo) faz a conexão falhar.
A Evolution cria o bucket sozinha?
Não. O bucket precisa existir antes de a Evolution subir, com o nome exato que está em S3_BUCKET. E vale a regra de nome do S3: só letras minúsculas, números, ponto e hífen.
O que acontece com as mídias antigas, de antes de ligar o S3?
Ficam onde estavam. A configuração vale só para o que chegar dali em diante, e não existe migração automática: a Evolution não sai varrendo o histórico para reenviar arquivo. Ligue o S3 e considere o passado um capítulo à parte.
O link mediaUrl é público?
Não. Ele é um link assinado, com validade: os parâmetros X-Amz-Signature e X-Amz-Expires na URL são o que dá acesso. O bucket continua privado, e quem pedir o mesmo arquivo sem a assinatura leva um erro de permissão.

Leia também