Dê um lar decente aos segredos do seu homelab com Infisical (#dev #homelab #docker #security #selfhosted)

Overview
Pergunta rápida: quantos arquivos .env estão espalhados pelos seus projetos neste momento e quantos deles contêm a
senha de banco de dados? Pois é, foi o que imaginei. Se você já tem um homelab, você tem tudo o que precisa para
resolver isso. Neste post, vamos subir o Infisical, um gerenciador de segredos open-source e depois ler um
segredo direto do Python.
Antes de continuar, acho importante dizer que isto não é um anúncio nem um post patrocinado. Eu descobri essa solução, adicionei ao meu homelab e estou bem satisfeito com ela. Então resolvi compartilhar porque talvez ajude mais alguém.
A praga dos arquivos .env
Sempre começa pequeno. Um .env para aquele script Python, um appsettings.json para a API em C#, uma senha escrita
direto no docker-compose.yml porque você ia mover depois (não ia). Alguns meses depois, a mesma senha do Postgres
vive em seis lugares, e você já não sabe quais projetos ainda usam aquela chave da API de previsão do tempo.
Só essa terra de ninguem com sua chaves já era chato antes, mas agora que todo mundo está vibe-coding e criando microslop a torto e a direito, ficou pior.
Agentes leem os arquivos do projeto e o que estiver ali pode acabar em um commit, em um arquivo de log ou em uma conversa colada em algum lugar. Um agente não consegue vazar um segredo que nunca esteve no disco.
E reciclar uma chave vazada que está copiada em uma dúzia de arquivos? É o tipo de tarefa que faz as pessoas simplesmente... procrastinar até ser tarde demais para reciclar as chaves.
Eu sei, eu sei: você é um "vibe-coder esperto" e adicionou os harnesses mais novos e brilhantes ao seu agente, então ele não vai conseguir ler os seus segredos... né? né?!
Bom, vamos continuar falando de gerenciamento de chaves/segredos sem gambiarra. :)
Diga olá para o Infisical!
O Infisical é um gerenciador de segredos open-source. Você guarda cada segredo uma única vez, organizado por projeto, ambiente (dev/staging/prod) e caminho de pasta. As aplicações buscam esses valores durante a execução via SDK, CLI ou API REST.
Se você já usou o Azure Key Vault ou o AWS Secrets Manager, a ideia é a mesma, só que rodando no seu próprio hardware.
Você gerencia tudo através de um dashboard web. Lá você pode criar machine identities para as aplicações se autenticarem como elas mesmas (em vez de pegarem emprestado o seu login) e um registro de quem leu ou alterou o quê.
O que vem no plano gratuito?
Como você sabe, open source não quer dizer gratuito, mas eles têm um plano free e ele inclui bastante coisa: O dashboard online, a API, a CLI, os SDKs, o operator de Kubernetes, webhooks, machine identities, referência entre segredos, secret scanning e as integrações (GitHub Actions, Terraform e companhia). Para um homelab, isso cobre tudo o que você vai precisar na prática.
Alguns recursos ficam atrás de licença mesmo quando você hospeda por conta própria: versionamento de segredos com recuperação point-in-time, RBAC, rotação automática de segredos, SSO via SAML e retenção maior de logs de auditoria. Bom saber que existem, mas em casa eu não senti falta deles.
Como nada é perfeito, existe um "custo" no plano free self-hosted
Antes de sair copiando e colando, vamos ser justos com esses custos:
- Mais três containers para cuidar. O Infisical precisa de Postgres e Redis. Se você já roda Postgres para outras coisas (a maioria dos homelabs roda), são só dois.
- Ele vira uma dependência. Se as aplicações buscam segredos na inicialização e o Infisical estiver fora do ar, elas não sobem. Para um homelab isso costuma ser aceitável, mas leve em conta antes de colocar qualquer coisa crítica atrás dele.
- Backup agora importa. Todos os segredos vivem naquele banco Postgres, criptografados com a
ENCRYPTION_KEYque vamos gerar logo abaixo. Perdeu a chave, e os backups do banco viram confete. Guarde a chave (e um dump do banco) em um lugar seguro, fora da máquina que roda o Infisical. - O problema do bootstrap nunca desaparece de vez. A aplicação ainda precisa de duas variáveis de ambiente para se autenticar no Infisical. A vantagem é que são sempre as mesmas duas, com escopo definido e revogáveis, e todo o resto fica em um lugar só.
Ainda está interessado? A preguiça não te venceu ainda? Então bora montar isso!
Colocando tudo para funcionar
Vamos rodar três containers em uma rede Docker compartilhada: Postgres, Redis e o próprio Infisical. Um aviso antes de
começar: as senhas abaixo são exemplos, e agora que este post foi publicado, elas são tão secretas quanto
password1, então gere as suas.
Passo 0: a rede
Os containers se enxergam pelo nome dentro de uma rede dedicada:
1docker network create my-docker-network
Passo 1: Postgres
É aqui que o Infisical guarda tudo.
1docker pull postgres:17
2docker run \
3 -d \
4 --name postgres \
5 --restart=unless-stopped \
6 --network=my-docker-network \
7 -e POSTGRES_PASSWORD=SuperDuperPostgres42 \
8 -e PGDATA=/var/lib/postgresql/data/pgdata \
9 -p 5432:5432 \
10 --health-cmd='pg_isready -U postgres || exit 1' \
11 --health-interval=30s \
12 --health-timeout=10s \
13 --health-retries=3 \
14 --health-start-period=10s \
15 -v /mnt/postgres/data:/var/lib/postgresql/data \
16 postgres:17
Se você já tem um Postgres nessa rede, pule esta parte e reaproveite o que já existe.
Passo 2: Redis
O Infisical usa o Redis para cache e para a fila de jobs (BullMQ), e essa segunda parte define toda a configuração. Um Redis padrão, com eviction habilitada, descartaria jobs da fila silenciosamente quando faltasse memória, então vamos rodá-lo com um arquivo de configuração pequeno:
1# /srv/infisical/redis/redis.conf
2
3# --- The important one --------------------------------------------------
4# Infisical runs its job queue (BullMQ) on this instance, in addition to
5# caching. Eviction would silently drop queued jobs. Do NOT change this
6# to allkeys-lru.
7maxmemory-policy noeviction
8
9# Guardrail so a runaway can't eat the host. With noeviction, hitting this
10# limit means writes get rejected (OOM error) instead of evicted, so set
11# it generously. Infisical's working set is tiny; 512mb is huge headroom.
12maxmemory 512mb
13
14# --- Auth ----------------------------------------------------------------
15# Redis has NO authentication by default. Any container on the shared
16# docker network could otherwise read/write this instance freely.
17requirepass SuperDuperRedis42
18
19# --- Persistence ---------------------------------------------------------
20# AOF preserves pending queue jobs across restarts. Cheap insurance.
21appendonly yes
22appendfsync everysec
23
24# --- Don't touch ---------------------------------------------------------
25# timeout 0 (default): BullMQ workers hold idle blocking connections.
26# A nonzero client timeout would kill them. Leave it at 0.
27timeout 0
Agora o container. O REDISCLI_AUTH está ali para o redis-cli ping do health check conseguir se autenticar sem a
senha aparecer na linha de comando:
1docker pull redis:alpine
2docker run -d \
3 --name redis \
4 --network=my-docker-network \
5 --restart unless-stopped \
6 -v /srv/infisical/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro \
7 -v /srv/infisical/redis/data:/data \
8 -e REDISCLI_AUTH="SuperDuperRedis42" \
9 --health-cmd "redis-cli ping | grep -q PONG" \
10 --health-interval=10s \
11 --health-timeout=3s \
12 --health-retries=5 \
13 redis:alpine \
14 redis-server /usr/local/etc/redis/redis.conf
Passo 3: segredos para o gerenciador de segredos
Sim, eu percebo a ironia. O Infisical precisa de dois valores próprios:
1openssl rand -hex 16 # -> ENCRYPTION_KEY (32-char hex)
2openssl rand -base64 32 # -> AUTH_SECRET (base64, for JWT signing)
A ENCRYPTION_KEY criptografa os segredos em repouso; o AUTH_SECRET assina os tokens de sessão. Salve os dois no
seu gerenciador de senhas agora, antes de perder o histórico do terminal. A ENCRYPTION_KEY em especial: sem ela, o
conteúdo do banco fica irrecuperável. E é exatamente essa a função dela.
Passo 4: preparando o banco de dados
O Infisical ganha um banco próprio e um usuário dono dele. Conecte no Postgres:
1docker exec -it postgres psql -U postgres
E rode:
1CREATE DATABASE infisical;
2CREATE USER infisical WITH PASSWORD 'SuperDuperInfisical42';
3ALTER DATABASE infisical OWNER TO infisical;
4
5-- Switch to the new database before the next part. This is important.
6\c infisical
7
8ALTER SCHEMA public OWNER TO infisical;
9GRANT ALL ON SCHEMA public TO infisical;
10CREATE EXTENSION IF NOT EXISTS pgcrypto;
Lembre-se: se você não rodar o \c infisical, a troca de dono do schema e a criação da extensão vão acabar no banco
padrão e não é isso que a gente quer.
Passo 5: o prato principal
Agora o Infisical em si. Cole os dois valores do passo 3:
1docker pull infisical/infisical:latest
2docker run -d \
3 --name infisical \
4 --network=my-docker-network \
5 --restart unless-stopped \
6 -e NODE_ENV=production \
7 -e ENCRYPTION_KEY="paste-the-hex-key-here" \
8 -e AUTH_SECRET="paste-the-base64-secret-here" \
9 -e DB_CONNECTION_URI="postgresql://infisical:SuperDuperInfisical42@postgres:5432/infisical" \
10 -e REDIS_URL="redis://:SuperDuperRedis42@redis:6379" \
11 -e SITE_URL="http://192.168.0.42:8080" \
12 -e TRUSTED_PROXY_CIDRS="172.16.0.0/12,192.168.0.0/16" \
13 -p 8080:8080 \
14 --health-cmd "node -e \"require('http').get('http://127.0.0.1:8080/api/status',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))\"" \
15 --health-interval=15s \
16 --health-timeout=5s \
17 --health-retries=5 \
18 --health-start-period=40s \
19 infisical/infisical:latest
Algumas notas sobre esse comando:
- O
SITE_URLprecisa ser uma URL absoluta, com protocolo e tudo. Ele é usado nos links de e-mail e nos redirecionamentos de OAuth, então aponte para o endereço que você realmente vai digitar no navegador. Se você não usa nenhum desses recursos, pode pular essa parte. - O
TRUSTED_PROXY_CIDRSsó importa se o Infisical ficar atrás de um reverse proxy: ele define em quais proxies confiar para os headers de IP encaminhado, para o log de auditoria registrar o IP real do cliente em vez do IP do proxy. Se você expõe a porta diretamente, pode remover. - Se a senha do usuário do banco tiver caracteres especiais, faça o URL-encode deles na
DB_CONNECTION_URI. - Estou usando
latestpara o post continuar copiável, mas no seu homelab, fixe uma tag de versão e atualize de propósito, não por acidente. (Ou deixe nolateste bora jogar a roleta do upgrade! :D)
Na primeira inicialização ele roda as migrações do banco, então dê um minuto e acompanhe com
docker logs -f infisical se estiver curioso. Quando subir, abra o site no navegador e crie a conta de
administrador. Dali em diante, crie um projeto, escolha um ambiente e adicione os primeiros segredos. Essa parte é
toda no clique.
Um checklist rápido para você:
- Na área geral, vá em Access Control e crie uma nova Machine Identity.
- Para essa nova identidade, crie um client secret.
- Crie um projeto.
- Adicione segredos a ele.
- Depois, no controle de acesso (do projeto), adicione a Machine Identity.
- E por último, dê um papel a essa identidade no projeto. (Viewer deve ser suficiente.)
Lendo um segredo em Python
Guardar segredos é só metade do trabalho; as aplicações precisam buscá-los. Para isso, o Infisical tem as machine identities (da seção anterior, lembra?): credenciais que pertencem a um serviço em vez de a uma pessoa, então dá para definir escopo e revogar sem mexer na sua própria conta.
Depois, no seu projeto:
1pip install infisicalsdk
1import os
2from infisical_sdk import InfisicalSDKClient
3
4
5client = InfisicalSDKClient(host="http://infisical:8080")
6
7# Authenticate with a machine identity (Universal Auth)
8client.auth.universal_auth.login(
9 client_id=os.environ["INFISICAL_CLIENT_ID"],
10 client_secret=os.environ["INFISICAL_CLIENT_SECRET"],
11)
12
13secret = client.secrets.get_secret_by_name(
14 secret_name="DATABASE_URL",
15 project_id="your-project-id",
16 environment_slug="prod", # matches your environment slug (dev/staging/prod)
17 secret_path="/",
18)
19
20print(secret.secretValue)
Alguns pontos que valem destaque:
- O
hostacima funciona para um container na mesma rede Docker, ondeinfisicalresolve pelo nome. Do seu desktop, use o mesmo endereço doSITE_URLou algo comolocalhost:8080. - Os únicos segredos que sobram no ambiente são o client ID e o client secret. Todo o resto é buscado em tempo de execução e mantido em memória, então nada acaba commitado em um repositório ou embutido em uma imagem.
- A leitura tem escopo de um projeto, um ambiente e um caminho. Uma identidade de dev vazada não consegue ler prod.
Rotacionar uma senha deixa de ser caça ao tesouro: troque no Infisical, reinicie os apps que estão usando ela e pronto.
Para onde ir a partir daqui
Se você não quiser mexer em código, a CLI do Infisical injeta os segredos como variáveis de ambiente em qualquer
processo (infisical run -- python main.py), e existem SDKs para a maioria das linguagens, além de integrações com
GitHub Actions, Kubernetes e Terraform. O cofre é o mesmo, as portas é que são várias.
O homelab é o lugar perfeito para praticar como fazer isso direito. Na próxima vez que um projeto precisar de uma connection string, você vai poder usar um gerenciador de segredos ao invés de continuar na gambiarra. :D
Se quiser um exemplo pronto, criei este snippet.
Espero ter ajudado! :)
Referências: