Como hospedar o OpenAnalytics em um VPS
Veja os requisitos reais do OpenAnalytics: ClickHouse, Postgres, Valkey, 4 GB de RAM, 25 GB livres e quatro DNS, além do que ocupa o disco.
A infraestrutura necessária antes do primeiro passo
Para alojar o OpenAnalytics por conta própria, precisa de um VPS Linux com cerca de 4 GB de RAM, 25 GB de espaço livre em disco, Docker com o plugin Compose e quatro registos DNS já apontados para o servidor. Esse é o requisito real e deve ser apresentado antes do primeiro comando, não depois.
A stack tem seis serviços de aplicação e três armazenamentos de dados. O Postgres guarda o plano de controlo: contas, sites, chaves de API e links de partilha. O ClickHouse guarda os eventos brutos e os rollups que o dashboard consulta. O Valkey é executado duas vezes: uma como fila de eventos durável e outra como cache que pode ser perdido, porque essas duas funções precisam de políticas de expulsão opostas. Apenas um processo, o gateway de consultas, pode ler o ClickHouse. Antes de executar cada consulta, esse processo verifica uma assinatura Ed25519 no envelope da consulta.
Se pretendia um único binário e um único ficheiro de configuração, esta não é a opção certa. O GoatCounter é a alternativa de binário único nesta categoria: um executável Go, SQLite por predefinição e nenhuma base de dados externa. A stack mais pesada oferece funis, métricas Web Vitals, atribuição de receitas da sua própria conta Stripe e um servidor MCP (model context protocol). Escolher entre ferramentas de análise alojadas por conta própria é o artigo que analisa esse compromisso. Este guia parte do princípio de que já tomou essa decisão.
Aponte primeiro os quatro registos DNS para o servidor
Os quatro subdomínios têm de resolver para o IP público do servidor antes de iniciar qualquer componente, porque o Caddy solicita certificados Let's Encrypt no primeiro arranque e o desafio falha se o nome ainda não resolver.
app.example.comdisponibiliza o dashboard.api.example.comdisponibiliza a API e os callbacks OAuth.c.example.comdisponibiliza o collector e o script do tracker.rt.example.comdisponibiliza o fluxo realtime.
Use quatro registos A ou um registo A e três registos CNAME que apontem para ele. Confirme com dig +short app.example.com antes de continuar. Um nome adicionado há um minuto ainda pode estar em cache como NXDOMAIN pelo resolver que o Let's Encrypt utilizar, por isso vale a pena aguardar e consultar os logs do Caddy quando a primeira tentativa de emissão do certificado falhar. Executar novamente a instalação não acelera a propagação do DNS.
Como alojar o OpenAnalytics por conta própria com Docker Compose
Confira uma versão marcada. O branch padrão é usado para o desenvolvimento, enquanto uma tag de versão corresponde às imagens publicadas. Os comandos abaixo pressupõem que o Docker e o plugin Compose já estão instalados. O guia executar serviços Docker Compose numa VPS explica esse processo.
git clone https://github.com/OpenLabs-so/openanalytics
cd openanalytics
git checkout "$(git tag -l 'v*' --sort=-v:refname | sed '/-/d' | head -1)"
cd infra/selfhost
./generate-secrets.sh --domain example.com --email you@example.com --with-geoip
docker compose pull && docker compose up -dO sed '/-/d' na linha de checkout exclui tags de pré-lançamento. Assim, você obtém a versão estável mais recente em vez de uma release candidate. O --with-geoip baixa o banco de dados de cidades DB-IP durante a geração. Se você ignorar essa etapa, cada evento terá um país nulo e a visualização geográfica não mostrará nada. Você pode adicioná-lo depois executando infra/selfhost/geoip/fetch-dbip.sh, definindo GEOIP_DB_PATH=/geoip/dbip-city-lite.mmdb em env/collector.env e recriando o coletor com docker compose up -d --force-recreate collector. Esse banco de dados é atualizado mensalmente. Repita o download todos os meses para evitar que os dados das cidades fiquem desatualizados.
Faça uma cópia de segurança dos segredos gerados antes de continuar
O gerador grava três elementos. .env contém os nomes de domínio e as referências das imagens. env/*.env contém um ficheiro de segredos por serviço. docker-compose.override.yml contém três pares de chaves Ed25519 como escalares de bloco YAML, porque um PEM com várias linhas não pode ser guardado num ficheiro env. Todos estes ficheiros são excluídos pelo Git, e nenhum pode ser regenerado com os mesmos valores.
Copie já esses ficheiros para fora da máquina. Cada perda tem uma consequência específica:
- Se perder as palavras-passe dos armazenamentos, fica sem acesso ao Postgres e ao ClickHouse. Só é possível redefini-las a partir do interior dos contentores.
- Se perder
OA_CREDENTIAL_KEYRING, todas as credenciais de terceiros armazenadas ficam irrecuperáveis. Qualquer pessoa que tenha ligado uma conta Stripe terá de a ligar novamente. - Se perder
ANONYMOUS_IDENTITY_SECRET, a identificação dos visitantes é recalibrada: os visitantes de ontem passam a ser contabilizados como novos, e a interrupção fica visível nos gráficos. - Se perder
AUTH_SECRET, todas as sessões são invalidadas e todas as pessoas têm de iniciar sessão novamente. - Se perder uma chave privada de assinatura, rode o par de chaves. Não se perde qualquer dado.
Dois segredos têm de ser idênticos byte a byte em dois ficheiros cada. ANONYMOUS_IDENTITY_SECRET aparece em collector.env e worker.env, porque o coletor calcula o hash do visitante e o worker grava-o. OA_CREDENTIAL_KEYRING aparece em api.env e worker.env. Todos os restantes segredos estão associados exatamente a um único serviço de propósito. Se um serviço receber um segredo que não deve possuir, termina em vez de iniciar.
Suba a stack e verifique-a
grep OA_IMAGE .env
docker compose pull
docker compose up -d
docker compose logs -f migrate
docker compose psmigrate aplica os esquemas do Postgres e do ClickHouse e depois termina, por isso um contentor migrate parado é o estado final correto. tracker-build compila oa.js num volume servido pelo Caddy e também termina. Todos os restantes serviços devem apresentar healthy em docker compose ps. Um serviço que reinicia continuamente quase sempre está a falhar na validação do ambiente. O log apresenta todos os problemas numa única lista, em vez de mostrar um problema por reinício. As duas causas habituais são uma variável deixada em branco, que é rejeitada em vez de ser tratada como não definida, e um segredo colocado no ficheiro do serviço errado.
Em arm64, ou a partir de uma branch, não existem imagens publicadas e é necessário compilá-las localmente com docker compose up -d --build. Um host com 4 GB fica sem memória durante essa compilação. Adicione swap primeiro. Ela só é necessária durante a compilação:
fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstabA compilação demora aproximadamente dez minutos. O pull demora alguns minutos, razão pela qual existem as imagens da release.
Reivindique imediatamente a primeira conta
Abra https://app.example.com. Uma implantação em que ninguém iniciou sessão ainda não mostra um formulário de início de sessão: oferece a criação da primeira conta. Essa conta mantém permanentemente os privilégios elevados e é a única que vê o ecrã de definições da implantação. Depois de criada, a rota responde com 409, para que ninguém possa entrar depois de si. Faça isto assim que a stack estiver saudável, não na semana seguinte.
Instale o tracker
Adicione um site no dashboard para receber a tag. O formato é fixo:
<script
async
src="https://c.example.com/oa.js"
data-key="YOUR_TRACKING_KEY"
data-collector="https://c.example.com"
></script>Coloque-a no cabeçalho da página. A chave de tracking é pública por definição, por isso deve ficar no HTML, onde qualquer pessoa pode lê-la. O script instala window.oa, e chamadas como oa("track", ...) são colocadas numa fila por um stub e processadas assim que o ficheiro é carregado. Dessa forma, um evento personalizado emitido antecipadamente não é perdido. Se outro componente da página já utilizar window.oa, o tracker é instalado como window.openanalytics. Se o mesmo site também responder como serviço onion, não inclua a tag nessa versão, porque um script obtido de c.example.com faz o visitante do Tor Browser regressar à clearnet e associa os dois endereços no mesmo carregamento da página.
Depois, verifique todo o percurso de ponta a ponta:
curl -s https://c.example.com/oa.js -o /dev/null -w '%{http_code} %{size_download}\n'
curl -s https://api.example.com/health | head -c 200
docker compose logs --tail=50 worker | grep -i batchO primeiro comando deve imprimir 200 e alguns quilobytes. Carregue uma página do site e procure uma linha de lote no log do worker dentro de alguns segundos. O collector responde com 202 assim que aceita um evento, e 202 significa colocado na fila, não armazenado. O worker move os eventos para o ClickHouse. Se os eventos forem aceites, mas nada aparecer no dashboard, o worker está bloqueado. Um crescimento contínuo da profundidade da fila do Valkey confirma isso. As causas habituais são credenciais incorretas do ClickHouse em worker.env ou uma permissão em falta numa tabela adicionada por uma migração recente.
Mantenha o coletor público e o dashboard protegido por autenticação
O Caddy é incluído no ficheiro compose e obtém os certificados para os quatro nomes automaticamente, portanto o caminho predefinido não exige trabalho de proxy da sua parte. Se o servidor já executar um reverse proxy Nginx, coloque a stack atrás do infra/selfhost/nginx.conf.example fornecido e mantenha o processamento dos cabeçalhos intacto:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header CF-Connecting-IP "";
proxy_set_header True-Client-IP "";
proxy_set_header Fly-Client-IP "";O coletor calcula o hash diário dos visitantes a partir do IP do cliente, portanto deve obter esse endereço da ligação e nunca de um cabeçalho. Encaminhar CF-Connecting-IP a partir de um salto não fiável permite que qualquer chamador reivindique qualquer endereço. Isso corrompe a geolocalização e infla as contagens de visitantes ao mesmo tempo.
O acesso é separado de forma clara por nome de host. c. e rt. têm de estar acessíveis a todos os visitantes de todos os sites que mede, portanto nunca coloque autenticação básica nem uma lista de permissões de IP à frente desses dois. app. e api. só precisam de estar acessíveis às pessoas que iniciam sessão. A autenticação da própria aplicação protege o dashboard: o início de sessão por palavra-passe está ativo por predefinição através de AUTH_PASSWORD_SIGNIN=enabled em env/api.env, e os botões do Google ou do GitHub só aparecem quando o ID de cliente e o segredo de cliente existem para o respetivo fornecedor. As ligações mágicas precisam de um transporte de correio. Sem esse transporte, a API apenas regista o envio numa outbox, portanto nada é entregue e nenhum erro é gerado. Se as suas outras aplicações self-hosted já estiverem atrás de um único login do Authentik, decida desde cedo se este dashboard fará parte desse sistema ou manterá as suas próprias contas, porque a primeira conta criada aqui fica permanentemente com privilégios.
Uma definição determina se o dashboard funciona. AUTH_TRUSTED_ORIGINS em env/api.env tem de corresponder exatamente à origem do dashboard. Se estiver errada ou ausente, a API não emite cabeçalhos CORS (partilha de recursos entre origens), o browser recusa todas as chamadas e obtém um dashboard que apresenta o layout, mas não mostra dados, enquanto docker compose ps indica que está tudo saudável.
Enquanto estiver na configuração do proxy, trate também do tráfego automatizado. Os crawlers acedem ao coletor como qualquer outro cliente, e as visualizações das páginas ficam registadas no ClickHouse e nas suas métricas. Bloquear crawlers de IA no servidor mantém parte desse tráfego fora da base de dados antes de afetar a precisão e ocupar espaço em disco.
O que significa não usar cookies aqui e qual é o custo
Não existe cookie. A identidade do visitante é um hash com salt. O salt é alterado todos os dias, e os endereços IP brutos nunca são armazenados. A geolocalização é resolvida localmente com base no ficheiro DB-IP no seu próprio disco. Assim, nenhuma consulta sobre um visitante sai do host. Manter as consultas localmente elimina o fornecedor, mas não elimina os dados. É a mesma limitação que existe quando executa a sua própria instância SearXNG e o IP do seu servidor passa a ser o endereço que os motores de pesquisa veem.
A vantagem é não existir um identificador persistido no dispositivo do visitante. É precisamente esse identificador que pode sujeitar um tracker às regras de consentimento da diretiva ePrivacy da UE. Por esse motivo, configurações como esta, baseadas apenas em dados agregados, são normalmente executadas sem um banner de consentimento. O GDPR continua a aplicar-se aos dados que armazena e ao período durante o qual os mantém. A sua assessoria jurídica é que decide o seu caso, não um README.
O custo é perder a identidade entre dias. Como o salt é alterado, uma pessoa que visite o site na segunda-feira e novamente na quarta-feira será contada como duas pessoas, por decisão de design e sem solução alternativa. As contagens de visitantes únicos por dia são fiáveis. As contagens semanais e mensais de visitantes únicos são calculadas a partir das contagens diárias e irão sobrestimar o alcance. Por isso, qualquer métrica de "visitante recorrente" para períodos longos não mede aquilo que o nome indica. As sessões e os percursos são fiáveis dentro de um único dia. Alterar ANONYMOUS_IDENTITY_SECRET tem o mesmo efeito que atingir o limite de um dia. Trate essa alteração como uma mudança de dados, e não como uma tarefa de manutenção de rotina.
O coletor respeita Do Not Track e Global Privacy Control, o sinal do navegador que informa um site de que não deve vender nem partilhar dados pessoais. A tag de script inclui as suas próprias opções para o mesmo objetivo: data-respect-gpc, data-respect-dnt e data-require-consent. Esta última impede toda a recolha até que o consentimento seja concedido e guarda a resposta em localStorage, usando a chave oa.consent. Definir data-storage="none" desativa totalmente o armazenamento no navegador.
Por que o disco fica cheio ao fim de seis meses
É isto que derruba um servidor de analytics self-hosted, e os eventos normalmente não são a causa.
Comece pelas imagens. Uma release publica dez imagens, que ocupam cerca de 13 GB em disco. Durante uma atualização, a nova geração é obtida antes de a antiga ser removida, por isso, durante algum tempo, ficam duas gerações armazenadas. Isto representa a maior parte dos 25 GB necessários, antes de chegar sequer uma única visualização de página.
Depois, os snapshots. snapshot.sh para a stack, arquiva os dois volumes de dados juntamente com todos os secrets e reinicia a stack. Neste caso, apenas as cópias a frio são seguras, porque o ClickHouse faz merge das parts em segundo plano e uma cópia obtida durante um merge fica inconsistente. upgrade.sh cria automaticamente um snapshot antes de cada atualização, por isso os arquivos acumulam-se no mesmo disco até definir um limite.
./snapshot.sh create --label before-something-risky
./snapshot.sh list
./snapshot.sh --keep 3Num host próximo do limite, liberte a geração anterior antes de atualizar. Isto é seguro enquanto a stack está em execução, porque as imagens usadas pelos containers em execução continuam referenciadas:
docker image prune -a -fDepois, há os próprios eventos. O ClickHouse comprime bastante os dados colunares, por isso o volume de eventos em bruto cresce mais lentamente do que a maioria das pessoas espera, e as tabelas de rollup lidas pelo dashboard são pequenas em comparação com a tabela em bruto. Meça em vez de fazer estimativas:
docker system df -v
docker compose exec clickhouse df -h /var/lib/clickhousePara obter o valor por tabela, execute isto com as credenciais do ClickHouse que o generator gravou em infra/selfhost/env/:
SELECT table, formatReadableSize(sum(bytes_on_disk)) AS size, sum(rows) AS row_count
FROM system.parts
WHERE active
GROUP BY table
ORDER BY sum(bytes_on_disk) DESC;Faça essa medição na primeira semana e novamente na quarta semana. Dois pontos permitem calcular uma taxa de crescimento, e essa taxa indica quando é necessário aumentar o volume. O guia de self-hosting não documenta nenhuma opção de retenção ou time-to-live para eventos em bruto em agosto de 2026. Por isso, dimensione o disco com base na taxa medida, em vez de assumir que as linhas antigas expiram automaticamente.
Há uma armadilha relacionada com eliminações que convém conhecer antes de ser afetado por ela. A eliminação de um site ou de uma conta coloca o trabalho numa fila para o worker, e esse worker precisa de CLICKHOUSE_MAINTENANCE_USER e CLICKHOUSE_MAINTENANCE_PASSWORD definidos, com um utilizador oa_maintenance correspondente existente no ClickHouse. Sem esses valores, a eliminação fica permanentemente na fila. O site desaparece do dashboard, mas todas as linhas permanecem em disco. O resultado é a aparência de uma limpeza sem recuperar espaço.
Atualizações e os três custos
git fetch --tags
git checkout "$(git tag -l 'v*' --sort=-v:refname | sed '/-/d' | head -1)"
cd infra/selfhost
./upgrade.shupgrade.sh imprime três custos antes de agir. O tempo de indisponibilidade é real: os eventos tentados enquanto o coletor está parado são perdidos, porque o tracker não tenta enviá-los novamente. O rollback causa perda de dados, porque rollback.sh --to backups/<snapshot> substitui os dois armazenamentos integralmente e descarta todas as linhas gravadas depois da criação desse snapshot. O disco é o terceiro custo: corresponde ao conjunto de snapshots descrito acima.
É fácil configurar incorretamente duas regras de reinício. Inicie o gateway de consultas antes da API, porque uma API mais recente envia campos de consulta que um gateway mais antigo rejeita. O ClickHouse precisa de uma recriação, e não de um reinício, porque docker compose restart reutiliza o ambiente original do container e ignora silenciosamente a sua alteração:
docker compose up -d --force-recreate clickhouseO dashboard tem o mesmo tipo de armadilha. As três origens NEXT_PUBLIC_* em env/web.env são compiladas no bundle do navegador e substituídas quando o container inicia. Portanto, um dashboard que chama o hostname errado é corrigido com docker compose up -d --force-recreate web, nunca com restart. O log do container web imprime as origens usadas no arranque. Essa é a forma mais rápida de confirmar que a correção foi aplicada.
Se o ClickHouse se recusar a iniciar depois de uma alteração de configuração, leia a primeira linha do log. Uma linha que começa com oa-entrypoint: indica que o entrypoint rejeitou um valor definido por si. Qualquer outra mensagem normalmente significa que o ficheiro de configuração contém XML inválido. A causa mais comum é um hífen duplo dentro de um comentário XML, o que é ilegal nesse contexto.
AGPL-3.0 e o nome
O código é licenciado ao abrigo da AGPL-3.0. Executá-lo sem alterações nos seus próprios sites não cria qualquer obrigação de publicação. A obrigação começa quando modifica o código e executa essa versão modificada como um serviço de rede: a licença passa então a exigir que disponibilize o código-fonte modificado aos utilizadores desse serviço. Isto inclui fornecer dashboards aos clientes na sua instância e inclui integrar o software num produto que venda. Manter as alterações num fork público cumpre esta obrigação sem qualquer procedimento adicional.
A marca é independente do código. O nome "OpenAnalytics" e o domínio alojado do projeto identificam a instância operada pelos respetivos autores e não fazem parte da concessão da licença. A sua implementação executa o software sem utilizar a marca, por isso atribua ao serviço um nome próprio antes de o disponibilizar a clientes pagantes.
FAQ
Posso executar o OpenAnalytics numa VPS com 1 GB?
Não. O projeto requer cerca de 4 GB de RAM e 25 GB de espaço livre em disco, porque uma implementação executa seis serviços de aplicação juntamente com o Postgres, o ClickHouse e duas instâncias do Valkey. O ClickHouse, por si só, não é um processo pequeno. Numa máquina com 1 GB, os contentores arrancam e o kernel termina depois um deles por falta de memória, normalmente o ClickHouse. Se um plano de 1 GB for uma limitação obrigatória, use uma ferramenta de binário único, como o GoatCounter, que utiliza SQLite sem uma base de dados externa.
Preciso de um banner de cookies com o OpenAnalytics?
Essa é uma questão para o seu advogado, e os factos técnicos são favoráveis. Não existe cookie, a identidade do visitante é um hash com salt que roda diariamente e os endereços IP brutos nunca são armazenados. Assim, não é gravado nada persistente que permita identificar o visitante. O GDPR continua a regular o que armazena e durante quanto tempo o conserva. Se quiser condicionar explicitamente a recolha ao consentimento, defina data-require-consent na tag de script: o tracker não recolhe nada até o consentimento ser concedido e mantém a resposta em localStorage sob oa.consent.
Por que motivo os eventos devolvem 202, mas nunca aparecem no dashboard?
202 significa que o collector aceitou e colocou o evento numa fila, não que o armazenou. O worker esvazia essa fila para o ClickHouse. Por isso, um dashboard vazio com pedidos concluídos com sucesso aponta para o worker. Leia docker compose logs --tail=50 worker e monitorize a profundidade da fila do Valkey. Uma fila que continua a crescer significa que o worker está bloqueado. As causas habituais são credenciais incorretas do ClickHouse em worker.env ou uma permissão em falta numa tabela criada por uma migração recente.
Por que motivo o dashboard está vazio quando todos os contentores estão saudáveis?
Verifique primeiro AUTH_TRUSTED_ORIGINS em env/api.env. Esse valor tem de corresponder exatamente à origem do dashboard. Quando não corresponde, a API não envia cabeçalhos CORS. O browser recusa então todas as chamadas, e é apresentada uma interface funcional sem dados. A segunda verificação deve incidir nos três valores NEXT_PUBLIC_* em env/web.env, que são substituídos quando o contentor web arranca. Para os corrigir, é necessário docker compose up -d --force-recreate web, porque um simples reinício mantém os valores antigos.
O AGPL-3.0 impede-me de oferecer isto a clientes?
Não, mas impõe uma condição. Se executar o código sem alterações, não deve nada a ninguém. Se o modificar e executar essa versão modificada como um serviço utilizado por outras pessoas, tem de disponibilizar o código-fonte modificado a esses utilizadores. Um fork público cumpre esse requisito. Separadamente, o nome "OpenAnalytics" não é licenciado juntamente com o código. Por isso, qualquer produto que venda precisa do seu próprio nome.