Nibleaf: documentação self-hosted com Docker
Olá meus Unicórnios! 🦄✨
Documentação é aquele assunto que todo mundo adia. Você escreve o
sistema, ele fica lindo, e a explicação de como usá-lo mora num arquivo
.txt que só você entende. 😅 Quando finalmente resolvi tratar isso
com carinho, esbarrei numa escolha chata: as plataformas bonitas de
documentação são todas serviço pago por assento, e as gratuitas pedem que
você escreva Markdown puro no editor de código, sem ver nada até publicar.
O Nibleaf resolve os dois lados. Ele é uma plataforma de documentação que você instala no seu próprio servidor, com um editor visual no estilo Notion por cima de arquivos Markdown de verdade. Você escreve clicando, e o que fica gravado é Markdown comum, que você pode levar embora quando quiser. Tem busca própria, versionamento a cada publicação e suporte a idiomas da direita para a esquerda (árabe, hebraico) desde o primeiro dia, o que é raro.
Instalei numa VPS pequena para ver quanto doía. Foi mais simples do que eu esperava, e mesmo assim tropecei duas vezes. Os dois tropeços estão aqui, porque são exatamente os que vão acontecer com você. 🙈
🧰 O que você precisa antes de começar
Este tutorial pressupõe que você tem um servidor Linux (usei Ubuntu 24.04) e sabe entrar nele por SSH. Nada além disso. Se você nunca usou Docker, tudo bem: eu explico cada comando na linha em que ele aparece.
Sobre o tamanho da máquina, um aviso que vale ouro: o repositório do
Nibleaf tem dois arquivos de instalação. Um constrói o programa
a partir do código-fonte, e a documentação avisa que isso precisa de 5
a 6 GB de RAM livres, derrubando servidores pequenos no meio do
processo. O outro baixa a imagem pronta. Use sempre o segundo.
É o docker-compose.prod.yml, e é o único que aparece neste artigo.
Na minha máquina de testes eu tinha 6,8 GB de RAM livres e 86 GB de disco, e o Nibleaf usou uma fração disso. Já já mostro os números medidos.
🐳 Instalando o Docker (se ainda não tiver)
O Docker é o programa que roda cada peça do Nibleaf dentro da sua caixinha isolada. Se o seu servidor ainda não tem, o jeito oficial é este:
curl -fsSL https://get.docker.com | sh
Traduzindo: o curl baixa o script oficial de instalação e o
| sh manda executá-lo. Para conferir se deu certo, peça a versão
das duas ferramentas que vamos usar:
docker --version
docker compose version
Na minha VPS as respostas foram Docker version 29.7.2 e
Docker Compose version v5.5.0. Se o segundo comando reclamar que
compose não existe, você tem uma versão antiga demais do Docker,
de quando o comando ainda se escrevia docker-compose com hífen.
📁 Criando a pasta e baixando o arquivo de instalação
Toda a instalação vai morar numa pasta só. Eu usei
/opt/nibleaf, que é o lugar tradicional para programas
instalados à mão no Linux.
mkdir -p /opt/nibleaf
cd /opt/nibleaf
curl -sSLO https://raw.githubusercontent.com/lord007tn/nibleaf/main/docker-compose.prod.yml
O que cada linha faz: o mkdir cria a pasta (o -p
manda criar sem reclamar se ela já existir), o cd entra nela, e
o curl -O baixa o arquivo mantendo o nome original.
Confira que ele chegou:
ls -la
Tem de aparecer o docker-compose.prod.yml com uns 12 KB.
Esse arquivo é a receita: ele descreve as seis peças do Nibleaf e como elas
conversam entre si. A boa notícia é que você não vai editar esse
arquivo. Toda a configuração vai num segundo arquivo, separado.
🔑 O arquivo .env, que é onde tudo acontece
O Nibleaf lê as suas configurações de um arquivo chamado
.env, na mesma pasta. Aqui vem o primeiro detalhe que confunde
quem está começando: arquivos cujo nome começa com ponto são
invisíveis no Linux. Depois de criá-lo, um ls comum não
mostra nada, e você acha que perdeu o arquivo. Para vê-lo, use
ls -la, onde o -a quer dizer "mostre tudo,
inclusive os escondidos".
Antes de escrever o arquivo, precisamos gerar os segredos. São valores aleatórios que o Nibleaf usa para assinar as sessões de login e para proteger o banco. Não invente esses valores de cabeça, e não copie os meus: gere os seus com o comando abaixo, que imprime quatro linhas prontas para copiar.
echo "BETTER_AUTH_SECRET=$(openssl rand -hex 32)"
echo "POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=')"
echo "STORAGE_ACCESS_KEY_ID=$(openssl rand -hex 10)"
echo "STORAGE_SECRET_ACCESS_KEY=$(openssl rand -base64 24 | tr -d '/+=')"
O openssl rand -hex 32 sorteia 32 bytes aleatórios e os
escreve em hexadecimal, dando uma cadeia de 64 caracteres. O
tr -d '/+=' na segunda linha apaga as barras e sinais de mais
que o formato base64 produz, porque esses caracteres atrapalham quando a
senha entra numa URL de conexão.
Guarde as quatro linhas que saíram. Agora vamos criar o arquivo com o
editor nano, que já vem instalado:
nano .env
A tela fica preta e vazia, com uma barra de comandos embaixo. Cole o
conteúdo abaixo, trocando as quatro linhas de segredo pelas
suas e o SEU_IP_AQUI pelo endereço do seu servidor.
NIBLEAF_VERSION=v0.1.1
APP_URL=http://SEU_IP_AQUI:4310
NIBLEAF_BIND=0.0.0.0
APP_PORT=4310
SITE_BASE_DOMAIN=
BETTER_AUTH_SECRET=cole_aqui_a_sua_primeira_linha
POSTGRES_PASSWORD=cole_aqui_a_sua_segunda_linha
STORAGE_PUBLIC_ENDPOINT=http://SEU_IP_AQUI:9300
STORAGE_ACCESS_KEY_ID=cole_aqui_a_sua_terceira_linha
STORAGE_SECRET_ACCESS_KEY=cole_aqui_a_sua_quarta_linha
STORAGE_BUCKET=nibleaf
STORAGE_FORCE_PATH_STYLE=true
MAXIO_SECURE_COOKIES=false
REQUIRE_EMAIL_VERIFICATION=false
DISABLE_SIGNUP=false
Para salvar e sair do nano: Ctrl+O
(a letra O, de "output"), depois Enter para
confirmar o nome, e Ctrl+X para fechar. Aquele
símbolo ^ que aparece na barra de baixo do editor significa
justamente a tecla Ctrl: onde se lê ^O, entenda
Ctrl+O.
Como o arquivo guarda senhas, feche-o para os outros usuários da máquina e confira que ele existe:
chmod 600 .env
ls -la
📝 O que cada configuração significa
Vale entender o que você acabou de escrever, porque três dessas linhas são a diferença entre o painel abrir e você passar a tarde procurando o erro.
| Configuração | Para que serve |
|---|---|
APP_URL | O endereço pelo qual você vai abrir o painel no navegador. É a linha mais importante do arquivo. |
NIBLEAF_BIND | Quem pode alcançar a porta. O padrão de fábrica é 127.0.0.1, que aceita só conexões de dentro do próprio servidor. Como eu queria abrir do meu computador, coloquei 0.0.0.0, que aceita de fora. |
APP_PORT | A porta do painel. O padrão é 4310. |
BETTER_AUTH_SECRET | A chave que assina as sessões de login. Sem ela, nada sobe. |
POSTGRES_PASSWORD | A senha do banco de dados que guarda as suas páginas. |
STORAGE_* | O armazenamento das imagens que você subir para a documentação. |
REQUIRE_EMAIL_VERIFICATION | Se o Nibleaf exige confirmar o e-mail antes do primeiro login. Deixei false porque não configurei envio de e-mail; com true e sem servidor de e-mail, você cria a conta e nunca consegue entrar. |
Sobre o armazenamento, uma boa surpresa: você não precisa
contratar um bucket na Amazon. A instalação já traz junto um servidor
de arquivos compatível com S3, chamado maxio, que cria o bucket
sozinho na primeira subida. Aquelas duas chaves que você gerou não são de
nenhum serviço externo: é com elas que o maxio nasce. Você as
está inventando, não copiando de lugar nenhum.
💥 O primeiro erro: segredo vazio não passa
Antes de acertar, eu errei de propósito para ver o que acontecia. Deixei
o BETTER_AUTH_SECRET vazio e mandei subir. O resultado não foi
um contêiner com defeito: foi uma parede de erros antes de qualquer
coisa iniciar.
error while interpolating services.app.environment.BETTER_AUTH_SECRET: required
variable BETTER_AUTH_SECRET is missing a value: Set BETTER_AUTH_SECRET in .env
— generate with `openssl rand -hex 32`
error while interpolating services.postgres.environment.POSTGRES_PASSWORD: required
variable POSTGRES_PASSWORD is missing a value: Set POSTGRES_PASSWORD in .env
error while interpolating services.maxio.environment.MAXIO_ACCESS_KEY: required
variable STORAGE_ACCESS_KEY_ID is missing a value: Set STORAGE_ACCESS_KEY_ID in .env
Repare no detalhe bonito: quem barrou não foi o Nibleaf, foi o
próprio Docker Compose. O arquivo de instalação usa uma sintaxe
chamada ${VARIAVEL:?mensagem}, que quer dizer "se esta variável
estiver vazia, pare tudo e mostre esta mensagem". É por isso que o erro
chega com a solução escrita dentro dele, dizendo qual comando gera o valor.
Isso é uma escolha de projeto que eu respeito muito. A alternativa comum, e péssima, seria o programa subir com um segredo padrão qualquer, funcionar lindamente, e deixar você com um servidor onde qualquer pessoa que conheça o valor padrão consegue forjar uma sessão de login. Falhar cedo e falhar barulhento é o comportamento certo aqui.
📥 Baixando as imagens (e a hora em que a internet me traiu)
Com o .env preenchido de verdade, o primeiro passo é baixar
as imagens. Elas são grandes: a do Nibleaf sozinha tem 2 GB.
docker compose -f docker-compose.prod.yml pull
E foi aqui que eu tomei um susto. Depois de baixar 320 MB, o servidor do GitHub simplesmente cortou a conexão:
82133255ea51 Downloading 320.9MB
82133255ea51 Downloading 321.5MB
failed to copy: read tcp ...:57224->...:443: read: connection reset by peer
Meu primeiro instinto foi achar que eu tinha errado alguma coisa. Não tinha: é só a rede. Download de 2 GB tem chance de cair, e quando cai no meio o Docker devolve essa mensagem meio críptica. A solução é a mais boba possível: rode o mesmo comando de novo. O Docker guarda as camadas que já baixaram e continua de onde parou, em vez de recomeçar do zero.
Se a sua conexão for instável, vale mandar o comando tentar sozinho algumas vezes:
for i in 1 2 3 4 5; do
docker compose -f docker-compose.prod.yml pull && break
echo "tentativa $i falhou, repetindo em 5 segundos"
sleep 5
done
O && break é a parte que importa: ele sai do laço
assim que o download termina bem, para não repetir à toa. Na segunda
tentativa o meu passou inteiro.
🚀 Subindo tudo
Agora sim, o comando que liga o Nibleaf:
docker compose -f docker-compose.prod.yml up -d
O up cria e inicia os contêineres; o -d os
deixa rodando em segundo plano, para você poder fechar o terminal e ir
tomar um café. ☕ A saída conta uma historinha bem organizada:
Container nibleaf-postgres-1 Started
Container nibleaf-dragonfly-1 Started
Container nibleaf-maxio-1 Started
Container nibleaf-postgres-1 Healthy
Container nibleaf-migrate-1 Started
Container nibleaf-migrate-1 Exited
Container nibleaf-server-1 Started
Container nibleaf-worker-1 Started
Container nibleaf-server-1 Healthy
Container nibleaf-app-1 Started
Leia essa saída com carinho, porque ela mostra a ordem certa das coisas.
Primeiro sobem o banco, a fila e o armazenamento. Quando o banco fica
Healthy, entra o migrate, que cria as tabelas e
termina (por isso ele aparece como Exited: não é
erro, é o esperado, ele é uma tarefa de uma vez só). Só depois disso sobem o
server e o worker, e por último o
app, que é o painel que você vê.
Para conferir o estado de todos:
docker compose -f docker-compose.prod.yml ps
SERVICE STATUS
app Up About a minute (healthy)
dragonfly Up 14 minutes (healthy)
maxio Up 14 minutes (healthy)
postgres Up 14 minutes (healthy)
server Up About a minute (healthy)
worker Up About a minute (healthy)
A palavra que interessa é (healthy). Um contêiner pode
aparecer como Up e ainda assim não estar pronto para atender:
Up quer dizer só que o processo está vivo. O
healthy é o próprio programa respondendo "estou funcionando".
Se você abrir o painel cedo demais e vir erro, espere esses seis
healthy aparecerem antes de se preocupar.
🔓 Liberando a porta no firewall
O Ubuntu costuma vir com um firewall chamado ufw, que
bloqueia tudo que você não liberou explicitamente. Se ele estiver ligado, o
painel não vai abrir do seu computador, e não há mensagem nenhuma explicando
por quê: a página simplesmente fica carregando para sempre. Veja o estado
dele:
ufw status
Se a resposta for Status: active e a porta 4310 não estiver
na lista, libere-a:
ufw allow 4310/tcp
Agora teste, do seu computador, se o painel responde. O
%{http_code} pede ao curl que mostre só o código
da resposta:
curl -s -o /dev/null -w "%{http_code}\n" http://SEU_IP_AQUI:4310/sign-in
Um 200 significa que está tudo de pé. 🎉
🧙 Criando a primeira conta
Abra http://SEU_IP_AQUI:4310 no navegador. A primeira tela
é a de login, e como ainda não existe ninguém, o caminho é o link
Create one, embaixo do botão.
Repare no lado esquerdo da tela: Arabic-ready, RTL-first authoring. Esse é o diferencial que me fez querer testar o projeto. A maioria das plataformas de documentação trata escrita da direita para a esquerda como um remendo adicionado depois; esta nasceu bilíngue, com árvore de páginas por idioma.
Preencha nome, e-mail e senha (mínimo de oito caracteres), marque o aceite dos termos e clique em Create account. A primeira conta criada vira a dona da instalação.
😤 O segundo erro: Invalid origin
Foi exatamente aqui que eu levei o tapa na cara. Preenchi tudo, cliquei no botão, e apareceu uma tarja rosa com duas palavras e nenhuma explicação:
Nada no formulário estava errado. A senha era válida, o e-mail também, e
os seis contêineres estavam healthy. Fui ler o registro do
servidor, que é sempre o lugar certo para perguntar:
docker compose -f docker-compose.prod.yml logs server --tail 40
E lá estava, cristalino:
ERROR [Better Auth]: Invalid origin: http://localhost:4310
Entendi na hora, e a culpa era minha. 😳 Eu tinha escrito
o endereço do servidor no APP_URL, mas naquele momento estava
abrindo o painel por localhost, através de um túnel. Para o
Nibleaf, aquilo era um endereço diferente do que ele conhecia, e
ele recusou.
E a recusa é uma proteção de verdade, não uma implicância. Ela existe para impedir que um site malicioso qualquer faça requisições de login para o seu painel usando a sessão do seu navegador. O Nibleaf só aceita pedidos vindos dos endereços que você declarou confiar. Dá para ver isso com clareza chamando a interface de login direto:
curl -s -X POST http://127.0.0.1:4310/api/auth/sign-in/email \
-H 'Content-Type: application/json' \
-H 'Origin: http://localhost:4310' \
-d '{"email":"[email protected]","password":"sua_senha"}'
{
"message": "Invalid origin",
"code": "INVALID_ORIGIN"
}
Um 403 limpinho. A lista de endereços confiáveis chama-se
TRUSTED_ORIGINS e, quando você não a escreve,
ela vale exatamente o que estiver no APP_URL, e mais
nada. Como eu usava dois endereços, precisei declarar os dois. Abra
o .env de novo:
nano .env
E acrescente estas duas linhas no fim, separando os endereços por vírgula, sem espaço depois dela:
TRUSTED_ORIGINS=http://SEU_IP_AQUI:4310,http://localhost:4310
CORS_ALLOWED_ORIGINS=http://SEU_IP_AQUI:4310,http://localhost:4310
Salve com Ctrl+O, Enter, Ctrl+X.
Mudança em .env só vale depois de recriar os contêineres, e o
comando é o mesmo de antes:
docker compose -f docker-compose.prod.yml up -d
O Docker percebe sozinho o que mudou e recria só o necessário. Repeti o
mesmo curl de antes, e a resposta virou 200.
Voltei ao navegador, criei a conta, e entrou. 🥳
🏠 O painel por dentro
Passado o susto, a recompensa. O Nibleaf já entra com um site de documentação de exemplo criado e publicado, com sete páginas prontas:
Gostei muito dessa decisão. Plataforma que te larga numa tela vazia dizendo "crie o seu primeiro projeto" te obriga a adivinhar como as coisas funcionam; aqui você já tem um site inteiro para abrir, mexer e quebrar sem medo. Os números de visitas estão zerados porque a análise de acessos é própria do Nibleaf, sem serviço externo espionando os seus leitores.
✍️ O editor visual, que é o motivo de tudo
Clicando no site e em Open editor, chega-se ao que interessa:
À esquerda, a árvore de páginas, com pastas e o seletor de idioma no topo. No centro, o texto do jeito que o leitor vai ver, editável clicando em cima. À direita, comentários, para revisar documentação em equipe sem sair da ferramenta. E no alto, aquelas abas: Visual, WYSIWYG, Markdown e Preview.
É a aba Markdown que prova a promessa do projeto. A mesma página, um clique depois:
Markdown puro, com ## nos títulos e **negrito**
como sempre foi. Aquele bloco de destaque bonito que aparece na visão visual
é só uma etiqueta <Note> no texto. Ou seja: você escreve
clicando, sem decorar sintaxe, e o que fica guardado continua sendo um
arquivo de texto comum que qualquer editor abre. Se um dia você quiser sair
do Nibleaf, leva a sua documentação embora inteira. 🎒
Repare também no rodapé do editor: Words: 192 · Characters: 1103. Detalhe pequeno, mas é o tipo de coisa que só existe em ferramenta feita por quem realmente escreve.
🚪 Fechando o cadastro para estranhos
Repare numa coisa que passa batido: a instalação nasce com o cadastro aberto. Qualquer pessoa que descobrir o endereço da sua VPS pode criar uma conta e entrar. Numa documentação interna de empresa, isso é exatamente o que você não quer. 😬
Felizmente tem uma chave para isso, e ela já está no seu .env.
Abra o arquivo:
cd /opt/nibleaf
nano .env
Ache a linha DISABLE_SIGNUP e troque o false por
true:
DISABLE_SIGNUP=true
Salve com Ctrl+O, Enter, e saia com Ctrl+X. Agora mande o Docker aplicar a mudança:
docker compose -f docker-compose.prod.yml up -d
Espere uns 20 segundos e abra a página de cadastro. O formulário sumiu, e no lugar dele apareceu um aviso:
Mas eu não confio em formulário que some. 🕵️♀️ Sumir da tela é uma coisa; recusar de verdade é outra. Fui bater direto na API de cadastro, que é por onde alguém mal-intencionado tentaria passar:
curl -X POST http://SEU_IP_AQUI:4310/api/auth/sign-up/email \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"SenhaQualquer123","name":"Invasor"}'
A resposta:
{
"message": "Email and password sign up is not enabled",
"code": "EMAIL_PASSWORD_SIGN_UP_DISABLED"
}
HTTP 400. Bloqueado no servidor, não só escondido na tela. Essa é a diferença que importa: se estivesse só sumindo o formulário, bastaria mandar a requisição na mão para criar a conta assim mesmo. 🔒
🙈 Sumindo com a página de propaganda
Tem outra coisa que incomoda. O endereço da sua instalação não abre no login: abre numa página de vendas do Nibleaf Cloud, com "Your docs, hosted and ready", preços e blog. Faz sentido para quem vende o serviço; não faz nenhum para uma documentação interna.
E não é só a raiz. Fui olhar quantas páginas dessas ficam expostas:
for r in '' pricing about blog contact self-hosting cloud terms privacy; do
curl -s -o /dev/null -w "/: %{http_code}\n" http://127.0.0.1:4310/
done
/: 200
/pricing: 200
/about: 200
/blog: 200
/contact: 200
/self-hosting: 200
/cloud: 200
/terms: 200
/privacy: 200
Nove páginas. E aqui vem a parte chata: não existe variável no
.env para desligar isso. Procurei no arquivo de
instalação inteiro e a própria documentação do projeto confirma, ao descrever
o contêiner app como "site de marketing, painel, editor e sites
publicados" na mesma peça. Está tudo junto de propósito.
A saída é pôr um porteiro na frente: um programinha que recebe a visita antes do Nibleaf e decide o que deixa passar. Ele se chama proxy reverso, e o mais simples de configurar é o Caddy. Crie a pasta e o arquivo de regras:
mkdir -p /opt/nibleaf-porta
nano /opt/nibleaf-porta/Caddyfile
Cole isto dentro (com Ctrl+Shift+V, lembrando que Ctrl+V não funciona no terminal):
:4311 {
# A raiz manda direto para a tela de login.
redir / /sign-in 302
# Cada pagina de propaganda tambem vai para o login.
redir /pricing /sign-in 302
redir /about /sign-in 302
redir /blog /sign-in 302
redir /contact /sign-in 302
redir /cloud /sign-in 302
redir /self-hosting /sign-in 302
# O cadastro fica bloqueado pela porta tambem.
redir /sign-up /sign-in 302
# Todo o resto passa para o Nibleaf normalmente.
reverse_proxy 172.17.0.1:4310 {
# Sem esta linha o Nibleaf devolve 404 em tudo. Ele decide o
# que servir pelo endereco que o navegador pediu, e um
# endereco que ele nao conhece cai no roteamento de sites
# publicados, nao no painel.
header_up Host SEU_IP_AQUI:4310
}
}
Salve, saia, e suba o Caddy:
docker run -d --name nibleaf-porta --restart unless-stopped \
-p 4311:4311 \
-v /opt/nibleaf-porta/Caddyfile:/etc/caddy/Caddyfile:ro \
caddy:2-alpine
Libere a porta nova no firewall e teste as duas lado a lado:
ufw allow 4311/tcp
for r in '' pricing about sign-up sign-in app; do
printf '/%-9s ' ""
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' http://127.0.0.1:4311/
done
/ 302 -> http://127.0.0.1:4311/sign-in
/pricing 302 -> http://127.0.0.1:4311/sign-in
/about 302 -> http://127.0.0.1:4311/sign-in
/sign-up 302 -> http://127.0.0.1:4311/sign-in
/sign-in 200 ->
/app 200 ->
Toda propaganda cai no login, e login e painel continuam respondendo normalmente. Abrindo a porta 4311 no navegador, é isto que a visita encontra agora:
Porta de entrada, só. 🚪
Aquele comentário no meio do arquivo de regras merece atenção, porque me
custou um bom tempo. Na primeira tentativa eu tinha escrito só
reverse_proxy 172.17.0.1:4310, do jeito que todo tutorial de
Caddy ensina. Os redirecionamentos funcionaram de primeira, mas
/sign-in e /app passaram a devolver
404. 😵
Levei um tempo procurando erro no Caddy até desconfiar do outro lado. O
Nibleaf usa o endereço que o navegador pediu (o cabeçalho Host)
para decidir o que servir: painel ou site de documentação publicado. Quando o
proxy repassa o pedido, ele troca esse endereço pelo do contêiner, que o
Nibleaf não reconhece, e aí a requisição cai no roteamento de sites
publicados. Como não existe site naquele endereço, sai 404. Dá para ver o
comportamento isolado, sem proxy nenhum:
# Com o endereco que o Nibleaf conhece:
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: SEU_IP_AQUI:4310' http://172.17.0.1:4310/sign-in
# 200
# Com um endereco qualquer:
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: 172.17.0.1:4310' http://172.17.0.1:4310/sign-in
# 404
O header_up Host é o que conserta: ele manda o proxy repassar
o endereço que o Nibleaf espera. É uma linha só, e sem ela nada funciona. 🎯
📊 Quanto custa isso de máquina
A pergunta que eu mais queria responder: seis contêineres parecem muito para uma VPS pequena. Pedi os números ao Docker.
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}"
NAME MEM USAGE / LIMIT
nibleaf-app-1 26.94MiB / 7.755GiB
nibleaf-server-1 164.4MiB / 7.755GiB
nibleaf-worker-1 116.4MiB / 7.755GiB
nibleaf-postgres-1 29.34MiB / 7.755GiB
nibleaf-dragonfly-1 15.01MiB / 7.755GiB
nibleaf-maxio-1 1.35MiB / 7.755GiB
Cerca de 350 MB no total, em repouso. Isso é bem menos do que eu esperava, e é a prova de que o número assustador dos 5 a 6 GB é do build, não da execução. Quem baixa a imagem pronta nunca encosta nesse teto.
No disco, o custo é maior:
ghcr.io/lord007tn/nibleaf:v0.1.1 2GB
postgres:17-alpine 424MB
docker.dragonflydb.io/dragonflydb/dragonfly:v1.39.0 196MB
coollabsio/maxio:0.4.2 140MB
Uns 2,7 GB de imagens. No meu servidor, o disco em uso passou de 11 GB
para 14 GB somando os volumes de dados. Numa VPS com 20 GB de disco isso é
uma mordida considerável; vale conferir o espaço livre com
df -h / antes de começar, não depois.
🧭 Os comandos que você vai usar toda semana
Todos rodam de dentro de /opt/nibleaf, e todos precisam do
-f docker-compose.prod.yml, senão o Docker procura o arquivo
padrão (aquele que compila do fonte) e reclama que não existe.
docker compose -f docker-compose.prod.yml ps # o que esta de pe
docker compose -f docker-compose.prod.yml logs -f # ver os registros ao vivo
docker compose -f docker-compose.prod.yml restart # reiniciar
docker compose -f docker-compose.prod.yml up -d # aplicar mudanca no .env
docker compose -f docker-compose.prod.yml down # parar tudo
Se digitar tudo isso cansar, crie um atalho: alias nib='docker
compose -f /opt/nibleaf/docker-compose.prod.yml'. Aí
nib ps resolve.
Uma última coisa que me deixou tranquila: o ps mostra que
só o app publica porta para fora. O banco, a fila e o
armazenamento ficam numa rede interna do Docker, invisíveis para a internet.
Não é você que precisa lembrar de fechá-los: o arquivo de instalação já vem
assim.
Por hoje é só, meus unicórnios! 🦄✨
Que a magia do arco-íris continue brilhando em suas vidas! Até mais! 🌈🌟
Perguntas frequentes
Quanta RAM o Nibleaf consome de verdade?
server, 116 MB no worker, 27 MB no app, 29 MB no Postgres, 15 MB no Dragonfly e 1,3 MB no maxio. O que pede muita memória é o build a partir do fonte, de 5 a 6 GB, e é por isso que se usa a imagem pronta.Por que o contêiner recusa subir com BETTER_AUTH_SECRET vazio?
${VAR:?mensagem}, que aborta a leitura do YAML quando a variável está vazia. Gere o valor com openssl rand -hex 32.O que significa o erro "Invalid origin" na tela de login?
TRUSTED_ORIGINS, que por padrão vale só o que estiver em APP_URL. A API responde 403 com {"code": "INVALID_ORIGIN"}. A correção é listar no .env todos os endereços que você usa, separados por vírgula.Preciso de um bucket S3 externo para usar o Nibleaf?
maxio, que cria o bucket sozinho. Você só precisa inventar um par de chaves no .env: é com elas que o próprio maxio nasce.Por que o download da imagem falhou no meio?
ghcr.io cortou a conexão em 320 MB, com connection reset by peer. Rodar o pull de novo resolve, porque o Docker reaproveita as camadas que já baixaram em vez de recomeçar do zero.Como impedir que qualquer pessoa crie conta na minha instalação?
DISABLE_SIGNUP=false por true no .env e rode docker compose -f docker-compose.prod.yml up -d. O bloqueio é de servidor, não só de tela: a API de cadastro passa a responder 400 com EMAIL_PASSWORD_SIGN_UP_DISABLED. Crie a sua conta antes de desligar, senão você fica trancado do lado de fora.Dá para tirar a página de propaganda e deixar só o login?
/sign-in. Só não esqueça do header_up Host, senão o painel devolve 404.Leia também
OpenSEO: instalando na VPS e auditando um site
Como instalar o OpenSEO, alternativa aberta ao Semrush, numa VPS com Docker, auditar um site de verdade e entender a chave da DataForSEO.
changedetection.io: vigiando mudanças em sites na sua VPS
Instale o changedetection.io numa VPS com Docker, vigie um site com filtro CSS, receba o aviso no celular e acabe com o alarme falso do RSS.
Project NOMAD: seu servidor offline de emergência
Instale o Project NOMAD numa VPS Ubuntu e tenha Wikipédia, mapas, cursos e IA funcionando sem nenhuma conexão com a internet.