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ço | Endereço na rede | Isolamento por app |
|---|---|---|
| PostgreSQL | postgres:5432 | database + user dedicados |
| Redis | redis:6379 | usuário ACL + prefixo de chave |
| RabbitMQ | rabbitmq:5672 | vhost + user dedicados |
| MongoDB | mongodb:27017 | database + user dedicados |
| S3 (MinIO) | aistor:9000 | bucket + access key (opt-in) |
| SignOz | signoz-otel-collector:4317 | service.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.

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ço | Método |
|---|---|
| PostgreSQL | pg_dump --clean --if-exists |
| Redis | snapshot das chaves do app (<app>:*) |
| RabbitMQ | export de definitions (vhost/users/policies) |
| MongoDB | mongodump 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 projeto | Prateleira compartilhada | |
|---|---|---|
| Provisionamento | manual, por projeto | um comando (shelf setup) |
| Backup / restore | espalhado, ad-hoc | um sistema só, com schedule |
| Credenciais | superuser por toda parte | isoladas por app |
| Conflito de porta | comum | nenhum (rede interna) |
| Serviços heterogêneos | um stack por projeto | uma 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.