Como hospedar o Supabase em um VPS com Docker
Veja como executar a stack oficial do Supabase com Docker, trocar os segredos padrão, entender os 11 serviços, calcular a RAM e fazer backup e atualização.
O que você vai configurar
Hospedar o Supabase por conta própria significa executar a stack oficial do Docker Compose no seu servidor: Postgres, uma API REST à frente dele, um serviço de autenticação, armazenamento de ficheiros, WebSockets em tempo real e o dashboard Studio. Clone um repositório, edite um ficheiro .env e inicie onze contentores que, em conjunto, funcionam como um projeto Supabase sob o seu controlo.
A instalação é curta. O problema costuma estar no ficheiro .env. Ele inclui segredos de demonstração publicados no repositório, e uma stack iniciada com esses valores predefinidos fica acessível a qualquer pessoa que os encontre. Este guia explica quais os segredos que deve substituir, para que serve cada serviço, quanta memória a stack realmente precisa e como atualizá-la sem eliminar a base de dados.
Se o Compose ainda for novo para si, leia primeiro Noções básicas do Docker Compose num VPS. Tudo o que se segue pressupõe que docker compose version já apresenta uma versão.
O que a stack contém efetivamente
O Supabase não é um único programa. O ficheiro Compose inicia um conjunto de serviços separados na mesma rede. Saber qual é a função de cada um transforma uma parede de nomes de contentores num sistema que pode depurar.
dbé o PostgreSQL com as extensões do Supabase carregadas. Todos os outros serviços comunicam com ele. Se este contentor não estiver saudável, todos os restantes também falham.api-gwé o gateway da API. Desde a versão de agosto de 2026, executa o Envoy. Antes, era um serviço chamadokong, e o Kong continua disponível como substituição opcional. Escuta na porta 8000 e encaminha/rest/v1/,/auth/v1/e/storage/v1/para o backend correto. É o único contentor que deve expor.resté o PostgREST. Lê o esquema do Postgres e disponibiliza-o como uma API REST. Assim, uma tabela nova torna-se um novo endpoint sem escrever código.authé o GoTrue. Emite os tokens Web JSON (JWT) que identificam os utilizadores.storageeimgproxytratam dos carregamentos de ficheiros e do redimensionamento de imagens.realtimetransmite as alterações da base de dados através de websockets.studioemetasão o dashboard e a API de administração que lhe dá suporte.functionsé o runtime edge baseado em Deno, esupavisoré o pooler de ligações do Postgres.
Os logs e as análises (Logflare e vector) saíram do ficheiro Compose predefinido em junho de 2026. Agora estão num ficheiro de substituição opcional que ativa com sh run.sh config add logs. Isto usa a mesma sobreposição de ficheiros Compose descrita em executar uma stack a partir de vários ficheiros Compose.
É por isso que os valores de recursos abaixo são os que são. Não está a executar uma base de dados. Está a executar uma base de dados mais dez serviços de suporte.
Dimensionamento: planeie 8 GB de RAM
A stack utilizava aproximadamente 2.5 a 3 GB de memória residente num sistema acabado de instalar em julho de 2026, antes dos seus próprios dados ou tráfego. Nessa medição, o serviço de analytics e o processo Node.js do Studio eram os dois maiores consumidores individuais. O analytics já não faz parte da stack predefinida, por isso só consome memória se ativar a substituição da configuração de logs. Um servidor com 2 GB inicia os contentores, mas depois o kernel termina um deles através do out of memory killer. O sintoma é um contentor preso num ciclo de reinício, com o código de saída 137. A documentação de self-hosting do Supabase, consultada em outubro de 2026, indica 4 GB de RAM, 2 CPU cores e 40 GB de SSD como requisitos mínimos, e 8 GB, 4 cores e 80 GB como valores recomendados.
Atribua 8 GB de RAM e 4 vCPU a qualquer serviço de que dependa. 4 GB são suficientes para uma instância de desenvolvimento individual, se aceitar que uma consulta pesada e uma sessão do Studio em simultâneo serão lentas. O disco também é importante, porque o Postgres, o volume de armazenamento e, se ativar os logs, os dados de log ficam todos no diretório do projeto. Comece com 40 GB e monitorize o consumo. Contar os serviços antes de escolher um plano é um hábito útil para qualquer serviço que aloje por conta própria, porque PhotoPrism e Immich têm requisitos mínimos reais de RAM muito superiores aos sugeridos pelas respetivas páginas de início rápido. Se ainda não precisar de um servidor tão grande, compare-o com o que o plano gratuito do Supabase Cloud realmente inclui, porque uma aplicação pequena que se mantenha abaixo de 500 MB e nunca fique inativa durante uma semana pode funcionar aí sem custos.
Instalação: clone o repositório oficial
O procedimento suportado copia o diretório docker do repositório principal para um diretório de projeto seu. Essa separação é importante, porque impede que um git pull posterior substitua o seu .env.
git clone --depth 1 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/* supabase-project
cp supabase/docker/.env.example supabase-project/.env
cd supabase-project
docker compose pulldocker compose pull transfere vários gigabytes de imagens. Deve terminar com todos os serviços marcados como Pulled. Um erro manifest unknown neste ponto significa que a tag da imagem fixada foi removida no repositório upstream. A correção é obter uma cópia mais recente do repositório, em vez de editar as tags manualmente.
Os segredos que deve alterar antes do primeiro arranque
Faça isto antes de iniciar a stack, não depois. Vários destes valores são gravados nos dados no primeiro arranque. Alterá-los mais tarde implica repor a base de dados.
O repositório inclui um gerador que produz todos os valores corretamente, incluindo as duas chaves de API que têm de ser assinadas com o novo segredo JWT.
sh utils/generate-keys.sh --update-envEsse script grava novos valores para JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, SECRET_KEY_BASE, REALTIME_DB_ENC_KEY, VAULT_ENC_KEY, PG_META_CRYPTO_KEY, os tokens do Logflare, as chaves de protocolo S3 e MINIO_ROOT_PASSWORD em .env. Desde outubro de 2026, também grava valores aleatórios para POSTGRES_PASSWORD e DASHBOARD_PASSWORD. Precisa de openssl, que está presente em qualquer imagem Ubuntu normal. Como agora também reescreve a palavra-passe da base de dados, execute-o com --update-env apenas antes do primeiro arranque, nunca numa stack que já contenha dados.
Ainda vale a pena verificar manualmente dois valores em .env:
POSTGRES_PASSWORD. O script preenche-o com hexadecimal aleatório, o que é seguro. Se o substituir por um valor próprio, use apenas letras e dígitos. A pontuação neste valor quebra as strings de ligação que vários serviços criam juntando strings. A falha aparece como um erro de autenticação, não como um erro de análise, e isso leva a procurar o problema no local errado.DASHBOARD_USERNAMEeDASHBOARD_PASSWORD. Estas são as credenciais de autenticação básica do Studio. O script define uma palavra-passe aleatória, mas mantém o nome de utilizador predefinidosupabase. A palavra-passe predefinida fornecida é literalmentethis_password_is_insecure_and_should_be_updated. Se não executou o script, altere-a agora.
É importante perceber por que motivo ANON_KEY e SERVICE_ROLE_KEY não podem ser inventados. Ambos são JWTs assinados com JWT_SECRET. O gateway aceita apenas as chaves exatas em .env, e os serviços por trás dele verificam a assinatura com JWT_SECRET. Por isso, uma chave incompatível é rejeitada com HTTP 401. No gateway Envoy predefinido, o corpo da resposta é um Unauthorized simples. Em instalações mais antigas e na substituição por Kong, o Kong responde com {"message":"Invalid authentication credentials"}. Esta é a falha mais comum em instalações self-hosted: o operador alterou JWT_SECRET, mas manteve as chaves de demonstração. Gere os três valores em conjunto, sempre.
Trate SERVICE_ROLE_KEY como uma palavra-passe de root. Ignora completamente a segurança ao nível das linhas. Deve ser usada em código do lado do servidor e em mais nenhum local.
Defina SITE_URL como o endereço da sua aplicação e API_EXTERNAL_URL como o endereço público da API, com /auth/v1 no fim, por exemplo https://supabase.example.com/auth/v1. Defina SUPABASE_PUBLIC_URL como o mesmo endereço público, sem o caminho. O Auth cria os links de confirmação de email e de callback OAuth a partir desses valores. Se os deixar com os valores predefinidos de localhost, todos os seus utilizadores serão enviados para as próprias máquinas.
Depois, verifique os valores definidos:
sh run.sh secretsInicie-o e confirme que está saudável
sh run.sh start
docker compose psrun.sh start envolve docker compose up -d --wait, por isso não retorna até que as verificações de integridade sejam concluídas com êxito. Cada serviço deve apresentar running (healthy) ou running. A primeira inicialização demora de dois a quatro minutos porque o Postgres executa os respetivos scripts de inicialização antes de qualquer outro componente poder estabelecer uma ligação.
Se um contentor estiver a reiniciar, leia os respetivos logs pelo nome do serviço:
docker compose logs db
docker compose logs authO Studio fica então disponível na porta 8000 e solicita o nome de utilizador e a palavra-passe do dashboard que definiu.
Não exponha a porta 8000 à Internet pública
O gateway na porta 8000 usa HTTP simples. Todas as chaves de API e palavras-passe de utilizadores atravessam a rede em texto simples. As credenciais do Studio usam autenticação básica, que é codificação base64 e não encriptação.
Coloque um reverse proxy à frente do gateway e termine o TLS (transport layer security) nesse proxy. Faça o gateway escutar no endereço de loopback para impedir qualquer outro acesso. Em docker-compose.yml, a linha da porta sob o serviço api-gw passa a ser 127.0.0.1:${API_GW_HTTP_PORT:-${KONG_HTTP_PORT:-8000}}:8000/tcp, e o proxy encaminha os pedidos para essa porta. Numa instalação anterior à mudança para Envoy em agosto de 2026, a mesma linha da porta fica sob um serviço chamado kong. O artigo Traefik à frente de várias aplicações Compose explica a parte dos certificados. Esse mesmo proxy acaba por ficar à frente de tudo o resto no servidor, desde esta stack até algo tão pouco sério como uma biblioteca Jellyfin reconstruída como uma loja de vídeo dos anos 90. Cada serviço deve usar um nome de host, e não outra porta aberta. Se um dashboard for apenas para si, não use o proxy. Aceda à porta de loopback através de um túnel SSH, como faz self-hosted open-kritt para manter completamente a interface de análise fora da Internet pública.
Feche também o restante no firewall. O Docker publica portas ao criar as suas próprias regras do iptables, que uma configuração ingénua do ufw não consegue ver. Essa armadilha é explicada em por que os contentores Docker ignoram as regras do ufw.
Faça backup do banco de dados, não do diretório
Os dados do Postgres ficam em um bind mount em ./volumes/db/data. Copiar esse diretório enquanto o container está em execução produz uma cópia inconsistente, porque o Postgres armazena gravações em buffers e os arquivos no disco só ficam consistentes em um checkpoint. A restauração normalmente funcionará, mas às vezes perderá silenciosamente as últimas transações. Esse é o pior tipo de falha possível em um backup.
Em vez disso, faça um dump. pg_dumpall é executado dentro do container e produz um snapshot consistente:
docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sqlVerifique se o arquivo não está vazio antes de confiar nele. Depois, envie esses dumps para fora do servidor conforme uma programação definida. É para isso que servem os backups criptografados fora do servidor com restic. Um dump agendado que falha silenciosamente é indistinguível de nenhum backup. Por isso, configure o job do cron ou do systemd para enviar um alerta para o seu celular quando terminar com código diferente de zero. Faça backup do seu .env ao mesmo tempo. Perder JWT_SECRET invalida todos os tokens emitidos e torna ilegível todo segredo criptografado armazenado.
Os arquivos enviados ficam em ./volumes/storage. Eles são arquivos comuns, portanto uma cópia simples é suficiente.
Atualizar sem perder dados
O Supabase fixa as versões das imagens em docker-compose.yml, por isso nada muda até que faça essa alteração. Vale a pena usar fixação de versões em qualquer stack montada manualmente. É por isso que um relay RustDesk self-hosted fixa as duas imagens do servidor em vez de seguir uma tag que muda: uma atualização deve ser algo que escolhe fazer numa manhã em que tenha tempo para isso. Faça sempre um dump primeiro.
docker compose pull
sh run.sh recreaterecreate para a stack e inicia-a novamente com as novas imagens. Os seus dados permanecem porque estão nos bind mounts do host, não dentro dos containers. Leia CHANGELOG.md no repositório antes de avançar para uma versão principal, porque as atualizações principais do Postgres não são automáticas e exigem um dump e uma restauração.
Para aplicar alterações ao próprio ficheiro Compose, execute sh update.sh a partir do diretório do projeto ou sh update.sh --dry-run para ver primeiro o que mudaria. O comando combina os ficheiros upstream com as suas próprias alterações. Nunca substitui valores existentes em .env; apenas acrescenta chaves novas. Também arquiva a sua configuração em backups/ antes de a escrever, mas não arquiva a base de dados. Uma instalação sem um ficheiro .supabase-version precisa de --from e da release a partir da qual foi iniciada. Isto é particularmente importante para a release de August 2026, que substituiu o serviço kong por api-gw e exige uma atualização de docker-compose.yml. O método manual também funciona: clone novamente o repositório upstream e copie o diretório docker para o projeto, tendo cuidado para não substituir .env.
O reset completo, que destrói tudo, incluindo a base de dados, e repõe .env nos valores predefinidos (o valor antigo é mantido como .env.old), é feito por um script separado e pede confirmação:
sh reset.shFAQ
Por que as minhas chamadas à API retornam "Invalid authentication credentials"?
Essa mensagem exata vem do Kong, que é o gateway nas instalações anteriores a August 2026 ou nas instalações com a substituição do Kong. O gateway Envoy predefinido comunica o mesmo problema como um 401 simples Unauthorized. Em qualquer dos casos, a chave enviada não corresponde a ANON_KEY ou SERVICE_ROLE_KEY em .env, ou essas chaves não foram assinadas com o JWT_SECRET atualmente definido em .env. Numa stack que nunca teve dados, regenere os três valores em conjunto com sh utils/generate-keys.sh --update-env. Numa stack em execução, execute o script sem a flag e responda no, para que apenas mostre novos valores, copie JWT_SECRET, ANON_KEY e SERVICE_ROLE_KEY manualmente para .env e não altere POSTGRES_PASSWORD. Em seguida, execute sh run.sh recreate para que os serviços leiam os novos valores.
Posso executar o Supabase self-hosted numa VPS com 2 GB?
Não de forma fiável. A documentação oficial do Supabase para self-hosting indica 4 GB de RAM como requisito mínimo em October 2026, e a stack ficava próxima de 3 GB em July 2026 numa medição que incluía os serviços de logging, atualmente opcionais. Um servidor com 2 GB perde contentores para o out of memory killer, e verá o código de saída 137 em docker compose ps. Use 8 GB em produção e considere 4 GB o mínimo para desenvolvimento individual.
O Supabase self-hosted inclui edge functions?
Sim. O ficheiro Compose inclui o runtime de funções baseado em Deno e serve tudo o que colocar em ./volumes/functions. Não inclui a rede global de deployment da plataforma hosted, pelo que as suas funções são executadas no seu único servidor, numa única localização.
Como me ligo diretamente à base de dados Postgres?
Use docker exec -it supabase-db psql -U postgres para abrir uma shell interativa no próprio servidor. Para um cliente externo, ligue-se através do Supavisor na porta 5432 com o utilizador postgres.<POOLER_TENANT_ID> e o seu POSTGRES_PASSWORD. Não exponha essa porta à internet. Aceda a ela através de uma VPN ou de um túnel SSH.
Por que motivo os emails de confirmação da autenticação apontavam para localhost?
SITE_URL e API_EXTERNAL_URL em .env mantiveram os valores predefinidos. O serviço de autenticação constrói todos os links de confirmação e reposição de palavra-passe a partir desses dois valores, pelo que envia o endereço que lhe foi configurado. Defina ambos com o seu URL público real e recrie a stack.