3

Como eu melhorei a organização dos meus Apps no Homelab pessoal

Todo app novo que eu subo no homelab acaba pedindo a mesma coisa: um banco, às vezes uma fila, de preferência já provisionados antes do primeiro docker compose up. Quando rodei o litellm, precisei de Postgres. O Infisical quis Postgres e Redis. Outros projetos pediram RabbitMQ ou MongoDB. O padrão preguiçoso é cada projeto subir seu próprio Postgres + Redis no compose dele — e aí, três projetos depois, você tem três Postgres, dois Redis, conflito de porta toda hora, zero isolamento de verdade e backup espalhado em pasta que ninguém lembra.

Foi pra matar essa dor que escrevi o infra-shelf, hoje aberto no GitHub: https://github.com/IvanMicai/infra-shelf.


A ideia: uma prateleira compartilhada

Em vez de cada app trazer seu próprio banco, eu rodo uma instância de cada coisa numa rede Docker privada (infra-shelf), e cada aplicativo ganha uma credencial isolada dentro dela. Nada de entregar o superusuário pra um app qualquer.

A separação por app acontece dentro de cada serviço:

ServiçoEndereço na redeIsolamento por app
PostgreSQLpostgres:5432database + user dedicados
Redisredis:6379usuário ACL + prefixo de chave
RabbitMQrabbitmq:5672vhost + user dedicados
MongoDBmongodb:27017database + user dedicados
S3 (MinIO)aistor:9000bucket + access key (opt-in)
SignOzsignoz-otel-collector:4317service.name próprio (opt-in)

O Postgres, o Redis, o RabbitMQ e o Mongo sobem com um make up. S3 e SignOz são overlays opcionais. Tudo na mesma rede, então seus apps conectam por nome de host (postgres, redis...) sem expor porta pra fora.


Como eu uso — o CLI shelf

O coração é um binário Go único, o shelf. Provisionar um app é uma linha:

shelf setup myapp -s postgres,redis,rabbitmq,mongodb

Ele gera uma senha aleatória por serviço, roda os comandos de admin dentro dos containers (psql, redis-cli, rabbitmqctl, mongosh), grava tudo num registry e cospe um bloco .env pronto pra colar no projeto:

# === PostgreSQL ===
DATABASE_URL=postgres://myapp:QzLiMENvUupGDz@postgres:5432/myapp
DB_HOST=postgres
DB_NAME=myapp

# === Redis ===
REDIS_URL=redis://myapp:keS6texnNfjnGr@redis:6379/0
REDIS_PREFIX=myapp:

# === RabbitMQ ===
AMQP_URL=amqp://myapp:j3XwLUjuRrEP11@rabbitmq:5672/myapp
RABBITMQ_VHOST=myapp

# === MongoDB ===
MONGODB_URL=mongodb://myapp:C0jbw2HqXVkuFs@mongodb:27017/myapp?authSource=myapp
MONGODB_DATABASE=myapp

A partir daí o dia a dia é só mais alguns comandos:

shelf credentials myapp          # reimprime o .env quando eu perco
shelf add myapp -s signoz        # pluga um serviço a mais num app existente
shelf list                       # mostra todos os apps provisionados
shelf status                     # saúde dos containers de infra

Tem também uma UI web (shelf-web), e o detalhe importante é que ela não chama o CLI por baixo: as duas pontas importam a mesma biblioteca Go (internal/shelfcore) e chamam as mesmas funções. CLI e web fazem exatamente a mesma coisa, sem parsing de stdout no meio.


A tela de criação de apps

Quando eu não tô a fim de terminal, a UI web faz o mesmo provisionamento por formulário. A tela /apps tem o campo do nome do app (padrão [a-z][a-z0-9-]*), um campo opcional de environments (pra expandir em staging/production), checkboxes dos serviços e um botão Provision. Provisionado, dá pra revelar as credenciais e baixar o .env direto pela interface.

Tela de criação de apps do infra-shelf gerando os bancos

Roda em http://127.0.0.1:8080 com Basic Auth, é HTML renderizado no servidor (sem framework de frontend, SQLite puro-Go por baixo) e é o jeito que eu mais uso quando vou criar um app novo no fim de semana.


Backup — a rede de segurança

Talvez a parte que mais me dá paz. Consolidar tudo numa prateleira só também consolida o backup: é uma estratégia em vez de seis pastas perdidas.

shelf backup myapp               # um app, todos os serviços dele
shelf backup --all               # todos os apps provisionados
shelf restore myapp              # restaura do backup mais recente

Cada serviço é salvo do jeito certo pra ele:

ServiçoMétodo
PostgreSQLpg_dump --clean --if-exists
Redissnapshot das chaves do app (<app>:*)
RabbitMQexport de definitions (vhost/users/policies)
MongoDBmongodump do database do app

E dá pra agendar com retenção, que é como eu deixo rodando:

shelf schedule create myapp --cron "0 3 * * *" -z UTC --retention-days 14

Backup diário às 3h, guardando 14 dias. Em caso de catástrofe — pool que corrompe, volume que some — eu tenho de onde voltar. O registry com os secrets ainda pode ser cifrado em repouso (AES-256-GCM, via INFRA_SHELF_SECRET), então o arquivo de credenciais não fica em texto puro no disco.


"E a performance?"

A pergunta óbvia ao consolidar é se juntar tudo numa instância não piora as coisas. E aqui preciso ser honesto, porque a primeira versão deste post exagerou: economia de memória não é o motivo pra usar o infra-shelf.

Dentro de uma mesma instância Postgres, isolar cada app por database (ou schema) custa quase nada de RAM — shared_buffers, WAL e os processos de background são por instância, não por database (wiki do PostgreSQL). Ou seja: se o seu caso é multi-tenancy (um projeto só, com dezenas ou centenas de schemas), consolidar não economiza praticamente nada e ainda junta o blast radius. Aí não compensa — não suba uma ferramenta pra isso; schema na mesma instância resolve melhor.

O ganho de memória só aparece num caso específico: quando a alternativa seria um container de banco completo por projeto, cada um com seu próprio postmaster, shared memory e workers. É esse overhead fixo, pago N vezes, que some ao juntar tudo. Num relato de homelab colapsando seis containers Postgres separados em um, a RAM caiu de ~3.2 GB pra ~1.8 GB (DiyMediaServer) — real, mas modesto, e mesmo assim não é o ponto.

O ponto, no meu caso, é operacional. Meu homelab não roda o mesmo banco várias vezes pro mesmo app — ele roda apps diferentes: uns com Postgres, outros com Mongo, outros só com fila. O que eu queria não era espremer MB, era parar de manter um stack de banco por projeto:

Container por projetoPrateleira compartilhada
Provisionamentomanual, por projetoum comando (shelf setup)
Backup / restoreespalhado, ad-hocum sistema só, com schedule
Credenciaissuperuser por toda parteisoladas por app
Conflito de portacomumnenhum (rede interna)
Serviços heterogêneosum stack por projetouma prateleira

E o custo honesto disso, que eu devia ter destacado desde o começo: todos os apps ficam presos na mesma major version de cada banco. Quer um projeto no Postgres 16 e outro no 18? Aí o container dedicado ganha. Pra um único projeto multi-tenant, idem — como o caso que o Pilati levantou nos comentários, e ele tá certo.

Correção: a primeira versão desta seção tratava economia de memória como benefício principal, com uma tabela de "overhead duplicado". Isso não se sustenta — valeu ao Pilati pelo comentário. Reescrevi pra deixar claro onde o ganho existe (instâncias separadas) e onde não existe (schemas numa instância só).


Como subir

Instalação em um comando:

curl -fsSL https://raw.githubusercontent.com/IvanMicai/infra-shelf/main/scripts/install.sh | bash

O script confere os pré-requisitos (git, docker, compose v2), clona o repo, cria o .env, builda os dois binários (usa Go local se tiver, senão builda dentro de um container) e sobe a stack core. Depois:

cd ./infra-shelf
# 1. trocar as senhas padrão no .env
# 2. shelf setup myapp -s postgres,redis,rabbitmq,mongodb
# 3. make app   →   http://127.0.0.1:8080

E pra quem usa Claude Code: o projeto traz uma skill (infra-shelf-install) que automatiza o install e ainda conecta o seu projeto à rede infra-shelf — ela verifica os pré-requisitos, troca as senhas padrão, provisiona o primeiro app e ajusta o compose do seu projeto pra entrar na rede externa.

Carregando publicação patrocinada...
3

Nº de containers de banco 1 por app (cresce sempre) 1 por serviço (fixo)
Memória (overhead duplicado) alto baixo

Isso aqui não se reflete assim não, é uma inverdade.

em alguns benchmarks que eu fiz a diferença de memória entre usar schemas e db isolados no postres é muito pequena. Em partes porque o postgres não tem uma engine que "reaproveita recursos".

A minha situação era multi-tenancy (centenas de schemas na mesma instância) e a economia de memória e processamento simplesmente não ia compesar o risco de manter tudo junto.

Meus DBs usam em média 50MB de RAM.

Compensa o custo de ter uma ferramenta gerenciando isso apenas para economizar alguns poucos MBs?

1

Valeu demais pelo comentário, Pilati — e você tá certíssimo. 🙏

Dentro de uma mesma instância, schema/DB isolado custa quase nada de RAM: shared_buffers, WAL e os processos de background são por instância, não por database. Pra multi-tenancy (centenas de schemas no mesmo projeto) consolidar não economiza quase nada e ainda junta o blast radius — não compensa mesmo, concordo 100%.

O número que citei (3.2→1.8GB) é de um caso diferente: colapsar vários containers/instâncias Postgres separados em um só — aí o overhead fixo que se paga N vezes some. Mesmo assim é ganho modesto, não o ponto. Fui infeliz em destacar memória na tabela; já tô corrigindo o post.

No meu caso o objetivo nem é economizar RAM: é homelab com apps diferentes (uns com Postgres, outros Mongo, outros só fila), e o que eu queria era um provisionamento único + backup/restore + credencial isolada por app, sem subir um container de banco por projeto.
Pra um único projeto multi-tenant eu não usaria isso — schema na mesma instância resolve melhor.

(O custo real que eu devia ter destacado: todos os apps ficam presos na mesma major version do banco. Esse sim incomoda às vezes.)

Valeu de novo pela correção.