Um site estático não tem backend. Não tem banco de dados, não tem servidor de aplicação processando requisições — e é exatamente isso que o torna rápido, barato e resiliente. Mas essa simplicidade cobra um preço quando você quer algo que dependa de estado persistente, e comentários são o caso mais óbvio. No WordPress ou Ghost, o sistema de comentários faz parte da aplicação. Num site gerado por Hugo, Jekyll ou Eleventy, essa camada simplesmente não existe.

A solução que dominou por mais de uma década foi o Disqus: um snippet de JavaScript, um iframe com a caixa de comentários, e pronto. O preço real dessa conveniência aparece nas entrelinhas — scripts de rastreamento, cookies de terceiros, publicidade injetada no seu site e todos os dados armazenados em servidores que você não controla. Existem alternativas baseadas em GitHub Issues (Utterances, Giscus), que funcionam bem para blogs técnicos mas exigem que o leitor tenha conta no GitHub. E existe uma categoria que resolve o problema da forma mais direta: um servidor de comentários leve que você mesmo hospeda, com seus próprios dados, no seu próprio domínio. O Isso se encaixa nessa categoria.

O que é o Isso#

O Isso é um servidor de comentários open-source escrito em Python, criado em 2012 como alternativa self-hosted ao Disqus. O nome vem do alemão Ich schrei sonst — algo como “senão eu grito”. A arquitetura é propositalmente simples: o servidor é uma aplicação Python que expõe uma API REST e armazena tudo em um único arquivo SQLite. O cliente é um script JavaScript de aproximadamente 20 KB (gzipado) que você embarca nas suas páginas. Não há painel administrativo elaborado, não há sistema de contas, não há integração com redes sociais. Os visitantes escrevem comentários informando nome e email — opcionalmente anônimos — e podem editar ou apagar o que escreveram dentro de uma janela de tempo configurável.

Os comentários suportam Markdown, threading funciona nativamente com respostas aninhadas, e a moderação pode ser habilitada para que comentários só apareçam após aprovação. O Isso também inclui uma ferramenta de importação que lê dumps XML do Disqus e do WordPress.

A escolha do SQLite como backend é uma decisão de design, não uma limitação. Comentários em um blog são dados pequenos, com escrita esporádica e proporção de leitura gigantesca. O SQLite lida com esse perfil sem esforço, e o backup do histórico inteiro se resume a copiar um arquivo.

Arquitetura#

O setup que vamos montar funciona assim: os blogs continuam hospedados no Cloudflare Pages (ou qualquer outro serviço de sites estáticos), e o Isso roda num VPS separado, acessível via um subdomínio como isso.seudominio.org. Quando um visitante abre um post, o JavaScript do Isso carrega a partir do VPS, busca os comentários daquela página e renderiza a seção de comentários. Se o VPS sair do ar por qualquer motivo, o blog continua funcionando normalmente — o visitante só não vê os comentários até o serviço voltar. A falha é graceful.

O Isso escuta numa porta local e não implementa TLS. Quem cuida do HTTPS é um proxy reverso — Nginx, Caddy, ou o que você já tiver no servidor. Sem HTTPS no endpoint do Isso, os comentários não funcionam em nenhum site servido via HTTPS, porque o navegador recusa conexões mixed-content silenciosamente.

Preparando o servidor#

Este tutorial usa um VPS com Ubuntu 24.04 e Nginx gerenciado pelo WordOps. Se você usa outro setup para o Nginx, adapte a parte do proxy reverso. O restante é idêntico.

Instale as dependências:

apt update
apt install python3-dev python3-venv build-essential

Crie a estrutura de diretórios. Neste exemplo, tudo fica em /var/www/isso.seudominio.org — configuração, banco de dados e virtualenv no mesmo lugar, o que simplifica backups:

mkdir -p /var/www/isso.seudominio.org/{config,db}

Crie o virtualenv e instale o Isso e o gunicorn:

python3 -m venv /var/www/isso.seudominio.org/venv
/var/www/isso.seudominio.org/venv/bin/pip install isso gunicorn

Ajuste as permissões para o usuário do servidor web (no WordOps e em muitos setups Nginx, é o www-data):

chown -R www-data:www-data /var/www/isso.seudominio.org

Configuração#

O Isso usa arquivos de configuração no formato INI. Se você vai servir comentários para um único site, basta um arquivo. Para servir múltiplos sites a partir da mesma instância — que é o nosso caso, com dois blogs — a abordagem muda: cada site precisa do seu próprio arquivo de configuração, com um campo name que identifica o site, e o Isso roda via gunicorn com o módulo isso.dispatch em vez do comando isso run direto.

A documentação oficial é explícita sobre esse ponto: o parâmetro host numa única config aceita múltiplas URLs, mas apenas variantes do mesmo site (por exemplo, versões com e sem www, ou HTTP e HTTPS). Sites diferentes exigem configs separadas.

Crie o arquivo para o primeiro site, /var/www/isso.seudominio.org/config/blog1.cfg:

[general]
name = blog1
dbpath = /var/www/isso.seudominio.org/db/blog1.db
host = https://blog1.seudominio.org
max-age = 15m
notify = stdout

[moderation]
enabled = true

[server]
listen = http://127.0.0.1:8000

[guard]
enabled = true
ratelimit = 2
direct-reply = 3
reply-to-self = false
require-author = true
require-email = true

[admin]
enabled = true
password = TROQUE_POR_UMA_SENHA_FORTE

E o arquivo para o segundo site, /var/www/isso.seudominio.org/config/blog2.cfg:

[general]
name = blog2
dbpath = /var/www/isso.seudominio.org/db/blog2.db
host = https://blog2.seudominio.org
max-age = 15m
notify = stdout

[moderation]
enabled = true

[server]
listen = http://127.0.0.1:8000

[guard]
enabled = true
ratelimit = 2
direct-reply = 3
reply-to-self = false
require-author = true
require-email = true

[admin]
enabled = true
password = TROQUE_POR_UMA_SENHA_FORTE

Os pontos que merecem atenção:

  • name — identificador único do site. É esse valor que define o subpath na URL da API (/blog1/, /blog2/).
  • dbpath — caminho absoluto. Cada site tem seu próprio banco de dados SQLite.
  • host — a URL do seu blog, não do Isso. É usada para validação de CORS.
  • moderation enabled = true — todo comentário passa por aprovação antes de aparecer. Desative depois de ganhar confiança no guard e nos seus leitores.
  • require-author e require-email = true — reduz spam anônimo significativamente.
  • admin enabled = true — habilita o painel de moderação, acessível em https://isso.seudominio.org/blog1/admin.

Se você serve apenas um site, pode usar um único arquivo e rodar com isso -c /caminho/config.cfg run diretamente. Nesse caso, o campo name não é necessário e os endpoints da API ficam na raiz (sem subpath).

Executando com gunicorn e systemd#

Para multi-site, o Isso usa o módulo isso.dispatch junto com o gunicorn. O gunicorn precisa saber quais arquivos de configuração carregar através da variável de ambiente ISSO_SETTINGS, com os caminhos separados por ponto-e-vírgula.

Teste manualmente antes de criar o serviço:

sudo -u www-data \
  ISSO_SETTINGS="/var/www/isso.seudominio.org/config/blog1.cfg;/var/www/isso.seudominio.org/config/blog2.cfg" \
  /var/www/isso.seudominio.org/venv/bin/gunicorn isso.dispatch -b 127.0.0.1:8000

Você deve ver nos logs que o gunicorn iniciou e que ambos os sites foram conectados. Em outro terminal, confirme:

curl http://127.0.0.1:8000/blog1/info
curl http://127.0.0.1:8000/blog2/info

Cada um deve retornar um JSON com a versão do Isso, a origem configurada e o status de moderação. Se os dois respondem, Ctrl+C no gunicorn e crie o serviço systemd.

Crie /etc/systemd/system/isso.service:

[Unit]
Description=Isso Commenting Server
After=network.target

[Service]
Type=simple
User=www-data
Group=www-data
Environment=ISSO_SETTINGS=/var/www/isso.seudominio.org/config/blog1.cfg;/var/www/isso.seudominio.org/config/blog2.cfg
ExecStart=/var/www/isso.seudominio.org/venv/bin/gunicorn isso.dispatch -b 127.0.0.1:8000
WorkingDirectory=/var/www/isso.seudominio.org
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Ative e inicie:

systemctl daemon-reload
systemctl enable --now isso
systemctl status isso

A partir daqui o Isso sobe automaticamente com o servidor.

Proxy reverso com Nginx#

O Isso precisa estar acessível via HTTPS para que os navegadores dos visitantes consigam se comunicar com a API. O processo escuta em 127.0.0.1:8000 e o Nginx faz o proxy.

Se você usa WordOps, o comando é direto:

wo site create isso.seudominio.org --proxy=127.0.0.1:8000 -le

Isso cria o vhost, configura o proxy reverso e provisiona o certificado Let’s Encrypt automaticamente.

Se não usa WordOps, crie o vhost manualmente. Um exemplo mínimo para /etc/nginx/sites-available/isso.seudominio.org:

server {
    listen 80;
    listen [::]:80;
    server_name isso.seudominio.org;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Ative o site e obtenha o certificado SSL:

ln -s /etc/nginx/sites-available/isso.seudominio.org /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
certbot --nginx -d isso.seudominio.org

Teste externamente:

curl https://isso.seudominio.org/blog1/info
curl https://isso.seudominio.org/blog2/info

Se retornar os JSONs com a versão e a origem de cada site, o servidor está pronto.

Por que não Docker#

A imagem oficial do Isso (ghcr.io/isso-comments/isso:release) traz o gunicorn embutido com a porta 8080 hardcoded no entrypoint. A diretiva listen do arquivo de configuração e a variável de ambiente GUNICORN_CMD_ARGS são ignoradas pela imagem — o gunicorn sempre tenta fazer bind na porta 8080, independente do que você configurar.

Com network_mode: host, se a porta 8080 já estiver ocupada no host por outro serviço (um painel de controle, por exemplo), o container entra em loop de erro e nunca inicia. Com bridge networking e port mapping (-p 127.0.0.1:OUTRA_PORTA:8080), o Docker proxy deveria traduzir as portas, mas em alguns ambientes o NAT não funciona corretamente — a conexão estabelece e é imediatamente resetada, mesmo com o Isso respondendo normalmente de dentro do container.

A instalação nativa com virtualenv, gunicorn e systemd elimina todas essas camadas intermediárias. Você controla a porta diretamente, sem depender do entrypoint da imagem, do Docker proxy ou do iptables/nftables do Docker. Para uma aplicação Python leve com um banco SQLite, a complexidade do Docker não se justifica.

Integração com Hugo#

Com o servidor rodando e acessível via HTTPS, o lado do Hugo se resume a duas coisas: incluir o script do Isso no template de posts e — se você usa um CMS como o Pages CMS — adicionar o campo correspondente na configuração do CMS para poder habilitar ou desabilitar comentários por post.

O partial de comentários#

Crie o arquivo layouts/partials/comments.html no repositório do seu blog:

<section id="isso-comments">
  <script
    data-isso="https://isso.seudominio.org/blog1/"
    data-isso-css="true"
    data-isso-lang="pt_BR"
    data-isso-reply-to-self="false"
    data-isso-require-author="true"
    data-isso-require-email="true"
    data-isso-avatar="true"
    data-isso-avatar-bg="#f0f0f0"
    data-isso-vote="true"
    src="https://isso.seudominio.org/blog1/js/embed.min.js"
  ></script>
  <noscript>Ative o JavaScript para ver os comentários.</noscript>
  <section id="isso-thread"></section>
</section>

O valor de data-isso é a URL base da instância do Isso, incluindo o subpath do site (/blog1/). O src aponta para o script servido pela própria instância. Os atributos data-isso-* controlam o comportamento do client — a referência completa está na documentação oficial.

Incluindo no template#

A forma de incluir o partial depende do tema. Alguns temas Hugo já têm suporte nativo ao Isso e expõem um campo no front matter para ativá-lo — consulte a documentação do seu tema para saber se esse é o caso e qual é o nome do campo esperado (pode ser comments, isso, disableComments, entre outros). Nesses casos, basta configurar os parâmetros no hugo.toml e o tema cuida do resto.

Se o tema não tem suporte nativo, edite o template de posts (layouts/_default/single.html ou o equivalente no seu tema) e adicione a chamada ao partial no ponto onde os comentários devem aparecer — normalmente depois do conteúdo e antes do footer:

{{ if ne .Params.comments false }}
    {{ partial "comments.html" . }}
{{ end }}

Com essa lógica, os comentários aparecem em todos os posts por padrão. Para desabilitar em um post específico, adicione comments: false no front matter.

Configuração do Pages CMS#

Se você gerencia o conteúdo pelo Pages CMS, adicione o campo de comentários no .pages.yml para que ele apareça na interface de edição. O nome do campo precisa corresponder ao que o template espera — no exemplo acima, é comments:

      - name: comments
        label: Comentários
        type: boolean
        default: true

Isso permite habilitar ou desabilitar comentários por post diretamente pela interface do CMS, sem editar o Markdown manualmente.

Moderação e notificações#

Com moderation = true na configuração, todo comentário novo entra em fila e fica invisível até que você aprove. A aprovação pode ser feita pelo painel de administração (acessível em https://isso.seudominio.org/blog1/admin) ou por links assinados enviados por email.

Para receber notificações por email, troque notify = stdout por notify = smtp e configure a seção [smtp]:

[general]
notify = smtp

[smtp]
username = seu@email.com
password = sua-senha-ou-app-password
host = smtp.seuprovedor.com
port = 587
security = starttls
to = seu@email.com
from = isso@seudominio.org
timeout = 10

Um detalhe que merece atenção: o Isso envia os emails via SMTP a partir do servidor onde está rodando. Se o domínio do remetente não tiver registros SPF, DKIM e rDNS configurados corretamente, esses emails têm grande chance de cair em spam. Se você não quer lidar com reputação de IP e configuração de email, aponte o SMTP para um serviço de relay como Mailgun, Brevo ou o próprio Gmail com uma app password.

Backup#

O banco de dados é um único arquivo SQLite por site. Se esse arquivo corromper ou for apagado, você perde todo o histórico de comentários. A solução é um cron job que copie os arquivos periodicamente:

sqlite3 /var/www/isso.seudominio.org/db/blog1.db ".backup /backups/isso-blog1-$(date +%Y%m%d).db"
sqlite3 /var/www/isso.seudominio.org/db/blog2.db ".backup /backups/isso-blog2-$(date +%Y%m%d).db"

O comando .backup do SQLite faz cópia a quente, sem inconsistências mesmo que o Isso esteja gravando no momento. O arquivo raramente passa de alguns megabytes, então copiar para um storage externo com rsync ou rclone é trivial.

Se você organizou tudo dentro de /var/www/isso.seudominio.org/ como fizemos aqui, e seu servidor já tem um script de backup que varre /var/www/, os bancos de dados já estão cobertos sem configuração adicional.

Migração do Disqus ou WordPress#

Se você já tem comentários em outro sistema, o Isso inclui uma ferramenta de importação:

/var/www/isso.seudominio.org/venv/bin/isso -c /var/www/isso.seudominio.org/config/blog1.cfg import -t disqus disqus-export.xml
/var/www/isso.seudominio.org/venv/bin/isso -c /var/www/isso.seudominio.org/config/blog1.cfg import -t wordpress wordpress-export.xml

Os comentários ficam associados aos URIs das páginas originais. Se a estrutura de URLs mudou na migração para Hugo, os comentários importados vão apontar para os caminhos antigos. A correção é direta no SQLite:

sqlite3 /var/www/isso.seudominio.org/db/blog1.db \
    "UPDATE threads SET uri = '/novo/caminho/' WHERE uri = '/caminho/antigo/';"

Conclusão#

O Isso faz exatamente o que promete — comentários self-hosted, leves e sem rastreamento — sem tentar fazer mais do que deveria. A instalação nativa com virtualenv e gunicorn é simples, previsível e consome recursos mínimos. O banco de dados é um arquivo que você pode abrir com sqlite3, os comentários são texto puro com Markdown, e se um dia o projeto for abandonado, seus dados continuam legíveis sem ferramentas especiais.

Para quem já administra um servidor e quer manter controle total sobre os dados dos seus visitantes, é a escolha que faz mais sentido.