Instalação avançada
Banco de dados
A instalação pergunta — SQLite, PostgreSQL ou MySQL. O padrão é SQLite, para não depender de nada.
PostgreSQL é o recomendado, e por um motivo funcional: ele é o único que traz pgvector, de que dependem as funções de IA local que usam busca semântica (embeddings). Com SQLite ou MySQL o resto do kit roda igual — só essas funções ficam indisponíveis.
Escolhendo Postgres na instalação, o .env já sai com o bloco que o docker-compose.yml lê, e falta só subir o container. Se ele não estiver de pé na hora da instalação, o kit avisa, pula as migrations e diz o comando para refazer:
docker compose up -d# defina KIT_ADMIN_PASSWORD no .env ANTES do seed — sem ela, o administrador nasce com a senha publicadaphp artisan migrate --seedContainer já criado e você mexeu no .env (um KIT_ADMIN_PASSWORD novo, por exemplo)? docker compose up -d não relê o arquivo — o env_file só entra na CRIAÇÃO do container. Para o valor valer de verdade, recrie-o:
docker compose up -d --force-recreatephp artisan migrate --seedPara trocar depois da instalação, suba os containers e copie as variáveis:
docker compose up -d --force-recreate # pgsql (com pgvector) + redis, relendo o .env# copie o bloco de banco de .env.docker para o seu .envphp artisan migrate --seedMySQL também tem container
Escolhendo MySQL, o .env sai apontando para 127.0.0.1:3306 com usuário root, e o kit sobe o
servidor para você. O comando é diferente do de Postgres, e o motivo importa:
docker compose up -d mysql redisphp artisan migrate --seedO MySQL é o único banco em profile próprio, porque a instalação escolhe um banco só — deixá-lo
sem profile faria toda instalação subir Postgres e MySQL juntos. Nomear os serviços na linha de
comando liga o profile do MySQL e restringe a subida ao que foi nomeado, então o Postgres do
profile padrão não sobe. Por isso o redis precisa estar escrito ali: nomear serviços desliga o
resto do padrão junto.
Dois detalhes da imagem, que explicam o que o instalador grava:
-
O usuário é
root. A imagem oficial recusa criarrootporMYSQL_USER— mantido esse usuário, o único caminho é a senha de root. -
A senha não é vazia.
mysql:8.0recusa inicializar sem senha de root, e o instalador gravasecret, a mesma que o container lê. Trazendo o seu próprio servidor, ajusteDB_PASSWORDno.env— como já se faz com um Postgres externo. -
A porta do host é
FORWARD_MYSQL_PORT, com default 3306, e não oFORWARD_DB_PORTdo Postgres. São chaves separadas porque no profileappos dois bancos sobem juntos, e uma variável só faria os dois disputarem a mesma porta. Se já houver um MySQL na sua máquina, o Docker recusa comBind for 0.0.0.0:3306 failed: port is already allocated— troque a chave:Janela do terminal FORWARD_MYSQL_PORT=3399 docker compose up -d mysql redise ajuste
DB_PORTno.envpara a mesma porta.
A opção da IA local não muda: busca semântica e embeddings dependem de pgvector, que só existe no
Postgres.
Domínio local no fim da instalação
Terminado o trabalho pesado — migrations, seeders e assets —, o kit:install faz a última
pergunta: cadastrar um domínio local para abrir o projeto em http://meu-projeto.test no lugar
de http://localhost:8000.
Cadastrar um domínio local (ex.: http://meu-projeto.test)? [y/N]Qual domínio? › meu-projeto.testTrês coisas valem saber antes de responder:
-
É opt-in. A resposta padrão é não: Enter segue com
http://localhost:8000, exatamente como antes. A pergunta só aparece quando há terminal — em CI, build Docker ou--no-interactiona etapa nem acontece. Não existe flag para ligá-la ou desligá-la, e ela também não depende do--force. -
A sugestão vem do nome que você escolheu na primeira pergunta:
Loja do Ferroviraloja-do-ferro.test. Você pode digitar outro — semhttp://, sem barra e sem espaço, e dentro dos limites do DNS (63 caracteres por parte do nome, 253 no total). O sufixo é.test, reservado pela RFC 6761;.localé recusado, porque a RFC 6762 o reserva ao mDNS. -
Domínio público pede um sim a mais. Se o que você digitar não terminar em
.test,.localhost,.exampleou.invalid— os quatro sufixos que a RFC 6761 reserva ao uso local —, o comando pergunta de novo, dizendo o que vai acontecer:minha-empresa.com.br é um domínio público. Apontar mesmo assim para 127.0.0.1? [y/N]A resposta padrão é não, e por um motivo concreto: apontar um domínio real para
127.0.0.1impede o acesso ao site de verdade nesta máquina, e a linha fica nohostsaté alguém removê-la à mão — remover não faz parte do que okit:installfaz. É a proteção contra o erro de digitação que custa mais caro. -
Aceitando, duas coisas acontecem: uma linha
127.0.0.1é acrescentada ao arquivohostsda máquina, e a chaveAPP_URLdo seu.envpassa a serhttp://meu-projeto.test— que é o endereço que o próprio comando imprime no fim, já com o nome novo.
No Windows a etapa executa o cadastro, pedindo elevação pelo UAC: uma janela se abre, e o que
decide se deu certo é o kit reler o arquivo depois — código de saída de processo elevado não
prova nada. No Linux e no macOS ela imprime a linha pronta para você colar com sudo, e
ajusta a APP_URL do mesmo jeito.
Nada disso aborta a instalação — nem uma pergunta interrompida no meio. Elevação negada, antivírus
bloqueando o arquivo ou pwsh ausente viram aviso no fim, com o comando pronto para colar (o do
seu sistema: Start-Process … -Verb RunAs no Windows, sudo tee -a no Linux e no macOS). O aviso
também diz em que estado a APP_URL ficou: o normal é ela não ser tocada, para a tela final não
mostrar um endereço que não responde.
Se o domínio já resolve para esta máquina — instalação anterior, ou Laravel Herd e Valet, que
respondem por *.test sozinhos —, o arquivo hosts não é tocado e só a APP_URL é ajustada. Note
o “para esta máquina”: só 127.0.0.0/8 e ::1 contam. Um DNS corporativo com curinga responde por
qualquer nome, inclusive um que você acabou de inventar, e tratar isso como “já está pronto” deixaria
a tela final apontando para um servidor de terceiro. Nesse caso o comando avisa qual endereço
respondeu e cadastra a linha local por cima.
A receita manual completa, com as armadilhas de elevação, o FORWARD_APP_PORT e o efeito sobre
login social e Vite, está em Domínio local.
O nome dos containers
Nenhum serviço do docker-compose.yml declara container_name. O prefixo de todo container e de
toda rede vem de COMPOSE_PROJECT_NAME, no .env, e o kit:install grava ali o nome que você
escolheu — em minúsculas e com hífen, que é o formato que o Compose aceita:
$ docker compose psminha-app-pgsql-1minha-app-redis-1Sem essa chave vale starter-kit, que é o piso escrito no próprio docker-compose.yml.
Dois pontos práticos:
- Projeto que já nasceu de uma versão anterior do kit não recebe a chave pelo
kit:update, que não mexe em.env. Rodephp artisan kit:install --custom, que refaz nome e cor, ou acrescente a linha à mão. - Trocar o nome depois de já ter subido containers cria volumes novos. Os dados antigos
continuam no volume do nome anterior; migre-os antes, ou troque o nome antes do primeiro
up.
A aplicação containerizada e o banco
O profile app sobe a aplicação inteira em container. Ele fala com o Postgres por padrão; para
apontá-lo ao MySQL, defina no .env:
DOCKER_DB_SERVICE=mysqle ligue os dois profiles juntos, senão o container do banco não sobe e o host não existe:
docker compose --profile app --profile mysql up -d --buildUm aviso honesto: nessa combinação um container de Postgres sobe ocioso, porque os serviços do
profile app dependem dele para ordenar o boot. A alternativa foi medida e é pior — sem essa
dependência, docker compose --profile app up -d sobe a aplicação sem banco nenhum e sem erro.
Os containers por profile
Os 12 serviços de docker-compose.yml sobem em grupos: pgsql e redis sempre, sem profile
(docker-compose.yml:pgsql::29, docker-compose.yml:redis::85); mysql no profile próprio, subido pelo nome — docker compose up -d mysql redis, nunca pelo
profile sozinho, que traria o pgsql junto
(docker-compose.yml:profiles: [mysql]:55); llamacpp e llamacpp-embeddings com ai ou full
(docker-compose.yml:profiles: [ai, full]:106); mailpit com mail ou full
(docker-compose.yml:profiles: [mail, full]:164); nginx, app, queue e scheduler com app
(docker-compose.yml:profiles: [app]:180); e reverb e pulse com app ou realtime
(docker-compose.yml:profiles: [app, realtime]:335).
flowchart TD
%% DG-18
accTitle: Containers por profile do Docker
accDescr: Os 12 serviços do docker-compose.yml, agrupados pelos profiles que sobem juntos. redis e reverb só valem de fato com o .env.docker; o .env.example padrão não os usa.
subgraph sempre ["Sempre (sem profile)"]
pgsql[("pgsql")]
redis[("redis")]
end
subgraph perfil_mysql ["profile: mysql"]
mysql[("mysql")]
end
subgraph perfil_ai ["profile: ai, full"]
llamacpp["llamacpp"]
llamacpp_embeddings["llamacpp-embeddings"]
end
subgraph perfil_mail ["profile: mail, full"]
mailpit["mailpit"]
end
subgraph perfil_app ["profile: app"]
nginx["nginx"]
app["app"]
queue["queue"]
scheduler["scheduler"]
end
subgraph perfil_realtime ["profile: app, realtime"]
reverb["reverb"]
pulse["pulse"]
end
env_example[".env.example (padrão): CACHE_STORE=database, BROADCAST_CONNECTION=log"] -.->|"deixa ocioso"| redis
env_docker[".env.docker: CACHE_STORE=redis, BROADCAST_CONNECTION=reverb"] -->|"faz valer"| redis
env_docker -->|"faz valer"| reverb
O .env.example que a instalação usa por padrão não liga redis nem reverb de fato — o cache
continua em database e o broadcast em log (.env.example:CACHE_STORE:59,
.env.example:BROADCAST_CONNECTION:55). Os dois containers só passam a ser lidos com o
.env.docker (.env.docker:CACHE_STORE:27, .env.docker:BROADCAST_CONNECTION:63), o arquivo que a
seção anterior usa para o profile app.
Comandos
composer dev # servidor + fila + vite + reverb juntos (e pail, fora do Windows)composer test # pint + phpstan + filacheck + a suíte inteiracomposer test:kit # só os testes do kit (a fundação), em paralelocomposer lint # formata o códigocomposer lint:check # só verifica a formatação, sem alterar nada (o que a CI roda)composer filament:check # só o lint específico de Filament (FilaCheck)composer refactor:preview # o que o Rector reescreveria (dry-run) — FORA do composer testcomposer refactor:apply # aplica a reescrita do Rector — FORA do composer testcomposer upgrade:filament # roda o vendor/bin/filament-v5 (filament/upgrade já está no require-dev)php artisan kit:install --force # reinstala do zero (APAGA o SQLite) e refaz as perguntasphp artisan kit:install --custom # refaz só nome e cor, sem tocar no bancophp artisan kit:install --no-custom # instala sem perguntar nadaphp artisan kit:install --no-npm # pula a instalação e o build dos assets front-endphp artisan kit:install --no-seed # não popula o banco (papéis, usuário inicial, agentes de IA)php artisan kit:install --onboarding # semeia o tutorial guiado sem perguntar; não liga o interruptorphp artisan kit:install --no-support # pula o convite para dar uma estrela ao kit no GitHub# --create-project é uso interno do post-create-project-cmd: apaga o que só serve ao repositório do kitphp artisan kit:admin # troca e-mail e senha do administrador (pede confirmação)php artisan kit:admin --email=x --senha=y --force # sem perguntas — evite: a senha fica no histórico do shellphp artisan kit:info # mostra como o projeto está customizado e de onde cada valor vemphp artisan kit:update # traz melhorias de uma versão nova do kitphp artisan kit:tenancy # liga o modo multi-tenant (opt-in)Os aprofundamentos de qualidade que ficavam sob esta seção — FilaCheck, Rector, a suíte de testes, as imagens do README e a varredura SFDIPOT — estão em Qualidade de código.
Personalize seu projeto
Os cinco primeiros o instalador já pergunta (ver a instalação) — a lista abaixo é para mudar depois, ou para quem pulou as perguntas.
php artisan kit:info mostra o valor atual de cada item abaixo, e se ele está vindo do banco ou do .env.
| # | O quê | Onde | Perguntado na instalação? |
|---|---|---|---|
| 1 | Nome | APP_NAME no .env |
✅ |
| 2 | Banco de dados | bloco DB_* no .env |
✅ |
| 3 | Credenciais do seeder | KIT_ADMIN_EMAIL / KIT_ADMIN_PASSWORD no .env |
✅ |
| 4 | Cor primária | KIT_COR_PRIMARIA no .env (nome de uma cor da paleta do Filament), ou KIT_COR_PRIMARIA_HEX com um hexadecimal livre — o hex vence o nome quando os dois estão preenchidos |
✅ |
| 5 | Multi-tenancy | php artisan kit:tenancy, e o termo exibido em config/kit.php → tenancy.label |
✅ |
| 6 | Arte do login | nenhuma: ela mostra o nome da aplicação (APP_NAME) sozinha. Para trocar por uma imagem sua, envie em /admin/configuracoes-da-aplicacao |
✅ (pelo nome) |
| 7 | Acesso aos painéis | o papel de cada usuário (/admin → Papéis, campo Painel); a regra que o lê é App\Models\User::canAccessPanel() |
— |
| 8 | Matriz de permissões | database/seeders/PapeisSeeder.php |
— |
| 9 | Health checks | KitServiceProvider::configureHealthChecks() |
— |
| 10 | Comandos da UI | config/command-center.php |
— |
| 11 | Backups | destino e agenda em config/backup.php |
— |
| 12 | Agente de IA | /admin → Agentes de IA (ou database/seeders/AssistenteSeeder.php) |
— |
| 13 | Idiomas do painel | config/kit.php → idiomas (lista de locales; com um só, o seletor não aparece) |
— |
| 14 | Retenção das trilhas | KIT_RETENCAO_EXCECOES_DIAS / KIT_RETENCAO_EMAILS_DIAS no .env |
— |
| 15 | Disco da mídia | MEDIA_DISK no .env (local por padrão — privado, servido por URL assinada) |
php artisan kit:midia-privada migra a mídia já gravada em disco público |
| 16 | Import e export CSV | a Action em cada app/Filament/**/Pages/List*.php (ligada ou comentada); a permissão em config/filament-shield.php → policies.methods; a retenção do histórico em KIT_RETENCAO_IMPORTACOES_DIAS / KIT_RETENCAO_EXPORTACOES_DIAS no .env |
ressemeie ShieldPermissionsSeeder + PapeisSeeder depois de mexer no config |
Os onze últimos não entram nas perguntas porque são código ou dado de tela, não um valor que caiba num prompt de terminal. O instalador os lista no resumo final, com o arquivo de cada um.
⚠️ O item 5 é o único que não é “edite um arquivo” depois de instalado: o
kit:tenancyrodamigrate:fresh --seede apaga os dados. Ele exige árvore git limpa e confirmação explícita. Respondido na instalação, ele não apaga nada — o banco ainda nem existe, e é essa a hora certa de decidir.
A cor primária vale para os três painéis. Com o modo multi-tenant ligado, a cor de cada organização vence esta dentro de
/app/{slug}— o/admine o/infracontinuam com a do projeto. Para uma paleta completa, e não só aprimary, o caminho continua sendo->colors([...])em cadaapp/Providers/Filament/*PanelProvider.php.
Como a instalação acontece por dentro
O composer create-project roda dois scripts do próprio kit
(composer.json:post-root-package-install:204, composer.json:post-create-project-cmd:207): o
primeiro só copia .env.example para .env se ele ainda não existir; o segundo chama
php artisan kit:install --create-project. Dentro de KitInstall::handle()
(app/Console/Commands/KitInstall.php:handle:93), a ordem real é: as cinco perguntas de
customização (app/Console/Commands/KitInstall.php:customizar():236) vêm antes de gerar a
APP_KEY e preparar o SQLite; migrar e semear só acontecem se o banco responder
(app/Console/Commands/KitInstall.php:if ($this->bancoAcessivel):124,
app/Console/Commands/KitInstall.php:if ($this->bancoAcessivel && ! $this->option('no-seed')):128);
e a senha do administrador é gerada antes do db:seed
(app/Console/Commands/KitInstall.php:garantirSenhaDoAdministrador:399,
app/Console/Commands/KitInstall.php:'db:seed':437) — nunca o contrário, senão o banner imprimiria
uma senha que o seeder já gravou como outra.
O OnboardingSeeder não faz parte do DatabaseSeeder. Depois do seed padrão, o instalador só
o executa com a resposta sim à pergunta Popular os tutoriais guiados? (padrão: não) ou com
--onboarding. Sem terminal e sem essa opção, não semeia o tutorial; --no-seed pula também essa
etapa. Semear não liga o interruptor: veja Tutorial guiado.
sequenceDiagram
%% DG-15
accTitle: Sequência da instalação
accDescr: O post-create-project-cmd do Composer aciona o kit:install, que pergunta a customização antes de migrar, gera a senha do administrador antes do db:seed, e só migra e semeia com o banco acessível.
participant composer as composer create-project
participant kit_install as kit:install
participant banco as Banco de dados
participant seeders as Seeders
composer->>composer: post-root-package-install copia .env.example -> .env (só se faltar)
composer->>kit_install: post-create-project-cmd invoca kit:install --create-project
kit_install->>kit_install: customizar() - nome, banco, credenciais, cor, tenancy
kit_install->>kit_install: gerarAppKey(), prepararBancoSqlite()
kit_install->>banco: tenta conectar (conferirConexao)
alt banco acessível
kit_install->>banco: migrate --graceful --force
kit_install->>kit_install: senha gerada (ou a definida em KIT_ADMIN_PASSWORD)
kit_install->>seeders: db:seed --force (ShieldPermissionsSeeder, PapeisSeeder, UsuarioAdminSeeder, AssistenteSeeder, GuardaPromptSeeder)
opt sem --no-seed e com sim em Popular os tutoriais guiados, ou --onboarding
kit_install->>seeders: db:seed --class=OnboardingSeeder --force (fora do DatabaseSeeder, não liga o uso)
end
else banco inacessível
kit_install-->>composer: aviso - rode migrate --seed depois
end
kit_install-->>composer: banner (mostra a senha só se foi gerada agora)
A ordem dos cinco seeders vem de DatabaseSeeder::run()
(database/seeders/DatabaseSeeder.php:run:16) — TenantsSeeder entra depois deles, só com o modo
multi-tenant ligado. O banner nunca imprime password: a senha é gerada com 24 caracteres
alfanuméricos e só aparece uma vez, na execução em que nasceu
(app/Support/SenhaDoAdministrador.php:garantirNoEnv:149); quem já definiu a
própria em KIT_ADMIN_PASSWORD não a vê impressa de volta.