Operação
Deploy
Requisitos, Docker, vários contêineres e hospedagem própria.
O Nimbly suporta deploy tanto em contêineres quanto manual numa VPS. Os contêineres empacotam a aplicação e o runtime juntos para atualizações de imagem repetíveis. O deploy manual numa VPS é igualmente válido para servidores Apache/PHP autogerenciados, quando você prefere controle direto sobre o servidor. Escolha o modelo que combina com a forma como o site será operado.
Todo site responde /health para a verificação de um balanceador de carga ou contêiner: 200 com ok quando o PHP roda e ext/data pode ser gravado, 503 caso contrário. A resposta nunca vai para cache. Uma rota ext/uri/health a substitui.
Atrás de um balanceador de carga que termina o TLS, informe o endereço dele no .env:
TRUSTED_PROXIES=10.0.0.0/8
Endereços e faixas, separados por vírgulas. Uma requisição que vem diretamente de um deles recebe esquema, porta e endereço do visitante de X-Forwarded-Proto, X-Forwarded-Port e X-Forwarded-For, para que o cookie de sessão seja secure, os links absolutos comecem com https:// e a pausa de login e as estatísticas contem o visitante, e não o balanceador. De qualquer outro endereço, e sem essa configuração, esses cabeçalhos são ignorados.
O log de acesso do próprio servidor web, e o que o lê (as regras para bots abaixo), também precisam do endereço do visitante. As imagens de contêiner cuidam disso ao iniciar, a partir da mesma configuração. Num servidor próprio, ative o mod_remoteip e coloque as linhas que este comando mostra no <VirtualHost> do site (não no .htaccess), de novo a cada mudança da configuração:
sudo a2enmod remoteip
./nimbly host:proxy-conf
Um bloqueio por endereço não impede nada atrás de um balanceador de carga: a conexão vem do balanceador. Faça o balanceador ou o firewall dele recusar os endereços que as regras encontrarem.
Bots
Bots são bem-vindos quando seguem o robots.txt, pedem só endereços que o site publicou e leem no máximo uma página por segundo. O Nimbly responde corretamente e deixa o bloqueio para o servidor:
- Armadilha. Todo site tem um endereço,
/nb-mais doze caracteres derivados doPEPPER, que orobots.txtproíbe e para o qual toda página tem um link invisível ([#bot-tripwire-link#]na shell da página). Um pedido a ele recebe o status418. O endereço é fixo por site e muda de um site para outro; uma shell de página própria também precisa do shortcode. - Regras do servidor.
core/cli/setup/fail2ban/contém três filtros do fail2ban e um arquivo de jail para um servidor com logs de acesso do Apache: bloqueio a qualquer resposta418, a mais de 60 requisições de página por minuto e a 20 respostas de não encontrado em dez minutos. Imagens, estilos, scripts, chamadas de API e telas do admin não são requisições de página, e uma imagem, estilo, script ou documento ausente não conta como não encontrado: é um link quebrado do próprio site. Copie os filtros para/etc/fail2ban/filter.d/e o arquivo de jail para/etc/fail2ban/jail.d/, acrescente seus próprios servidores aignoreipe rode cada filtro comfail2ban-regexnos seus logs antes de ativá-lo.
Deploy com Docker
A partir do checkout do projeto, gere os arquivos da imagem da aplicação em ext/:
./nimbly docker:init
Faça commit dos ext/Dockerfile, ext/.dockerignore e ext/.github/workflows/docker-publish.yml gerados no repositório ext/. A cada push, o GitHub Actions faz o build da imagem da aplicação a partir da imagem master atual do Nimbly e a publica no GHCR. O core publica continuamente ghcr.io/nimbly-web-systems/nimbly-core:master; os builds de aplicação gerados usam essa imagem e clonam o código master correspondente para o build dos assets.
Para testar o deploy localmente sem esperar que o GitHub Actions publique uma nova imagem do core, faça o build de uma imagem local do core e passe-a como base do build da aplicação:
docker build -t nimbly-core-local:master -f docker/prod/Dockerfile .
docker build -t nimbly-app-local:test \
--build-arg NIMBLY_BASE_IMAGE=nimbly-core-local:master \
-f ext/Dockerfile ext
Escolha a fonte de verdade dos dados de produção antes do deploy:
- Imutável, de desenvolvimento para produção: edite o conteúdo em desenvolvimento, faça commit em
ext/e publique uma nova imagem. A produção não é usada como editor. Nenhum token Git com escrita é necessário. - Edição em produção: os editores mudam conteúdo, envios ou registros no site em produção. Nesse modo, a produção grava em
ext/data/, e essas mudanças precisam voltar para o repositórioext/antes que o próximo deploy substitua o contêiner.
Para edição em produção, a imagem de produção precisa incluir ext/.git e o contêiner precisa receber GIT_TOKEN. As agendas de prod e stage incluem o ext:sync, que faz commit das mudanças nos dados e as envia de volta ao repositório ext/. No GitHub, use um token fine-grained restrito ao repositório ext/, com Contents: Read and write. O contêiner configura as credenciais do Git a partir desse token ao iniciar; não faça commit do token nem o coloque dentro da imagem.
Para testes locais com Docker ou uma VPS autogerenciada, um arquivo env fora do Git serve:
docker run --rm --env-file .env.prod ghcr.io/your-org/your-nimbly-app:main
Mantenha .env, .env.prod, .env.stage e arquivos parecidos fora do Git. Faça commit só de arquivos de exemplo, como .env.example ou .env.prod.example, com valores fictícios. Em plataformas de contêineres gerenciadas, configure esses valores como variáveis de ambiente ou segredos de runtime:
- AWS ECS/Fargate: guarde os segredos no AWS Secrets Manager ou no SSM Parameter Store e referencie-os no bloco
secretsda definição da tarefa. - AWS App Runner: configure as variáveis de ambiente e os segredos de runtime no serviço.
- Outros hosts de contêineres: use o mecanismo de segredos e variáveis de ambiente da plataforma, não o build da imagem Docker.
Exemplo mínimo de Compose para produção:
services:
web:
image: ghcr.io/your-org/your-nimbly-app:main
restart: unless-stopped
env_file:
- .env.prod
ports:
- "80:80"
environment:
APP_ENV: prod
SITE_URL: https://example.com
SITE_NAME: Example
MAIL_SERVICE: resend
MAIL_FROM: no-reply@example.com
MAIL_FROM_NAME: Example
volumes:
- nimbly-data:/var/www/nimbly/ext/data
volumes:
nimbly-data:
Se o conteúdo de produção é editado no próprio lugar, não acrescente .git ao ext/.dockerignore. Sem ext/.git ou GIT_TOKEN, o contêiner ainda inicia, mas o ext:sync não consegue enviar as mudanças feitas em produção, e elas podem se perder no próximo deploy de imagem.
Atualizar contêineres é atualizar imagens: envie o ext/, deixe o workflow publicar uma nova imagem, faça pull dela no host e reinicie o contêiner. O contêiner roda o system:setup ao iniciar e o agendador a cada minuto. No modo de edição em produção, garanta que o ext:sync tenha enviado os dados atuais de produção antes de substituir o contêiner.
Vários contêineres
Um site pode rodar em vários contêineres idênticos atrás de um balanceador de carga. Não há configuração para isso: tudo o que um site guarda é um arquivo em ext/ (registros, envios, sessões, jobs, estado do agendador, estatísticas de requisições, caches), então os contêineres só precisam compartilhar essa pasta.
- Um volume. Monte o mesmo volume em
/var/www/nimbly/ext/dataem todos os contêineres. Com edição em produção, monte oext/inteiro, para que haja um único checkout Git para oext:sync, e não um por contêiner. O volume precisa suportar travas de arquivo (flock): NFSv4 e Amazon EFS suportam. - O mesmo
.env. Todos os contêineres recebem o mesmoPEPPERe as mesmas configurações. - Balanceador de carga. Aponte a verificação dele para
/healthe definaTRUSTED_PROXIES(acima) com o endereço dele. Não são necessárias sessões fixas: um login num contêiner vale nos outros. - Agendador. Todos os contêineres o rodam a cada minuto; uma trava no volume compartilhado faz com que só um deles execute o trabalho, então cada tarefa roda uma vez.
- Gravações. As mudanças nos registros de um recurso acontecem uma de cada vez em todos os contêineres, então dois editores salvando o mesmo registro não perdem nada.
services:
web:
image: ghcr.io/your-org/your-nimbly-app:main
restart: unless-stopped
env_file:
- .env.prod
deploy:
replicas: 2
volumes:
- nimbly-data:/var/www/nimbly/ext/data
- nimbly-thumbs:/var/www/nimbly/ext/static/_thumb_
volumes:
nimbly-data:
driver_opts:
type: nfs
o: addr=10.0.0.10,nfsvers=4.1,rw
device: ":/nimbly/example/data"
nimbly-thumbs:
driver_opts:
type: nfs
o: addr=10.0.0.10,nfsvers=4.1,rw
device: ":/nimbly/example/thumbs"
As miniaturas ficam em cache em ext/static/_thumb_. Monte ali também um segundo volume compartilhado: senão cada contêiner gera as suas, e as miniaturas de uma imagem excluída ficam para trás nos contêineres que não trataram a exclusão.
Todo contêiner roda o system:setup ao iniciar e depois torna www-data dono dos arquivos do site, incluindo o volume compartilhado. Por isso o volume precisa permitir que o root troque donos (no NFS: no_root_squash, ou um export que já pertença a www-data), e um volume com muitíssimos arquivos deixa a inicialização lenta. Dois contêineres podem iniciar ao mesmo tempo num volume vazio.
core/tests/multi-node.sh testa isso numa estação de trabalho: ele inicia um site descartável em dois contêineres numa pasta (docker/dev/docker-compose.nodes.yml) e verifica o login, gravações simultâneas num registro, o agendador e as estatísticas de requisições. Ele só rodou num disco local, não num volume de rede nem atrás de um balanceador de carga real.
Deploy manual numa VPS
O deploy manual numa VPS é suportado para instalações autogerenciadas. A base de um release é o mesmo caminho usado no CI:
./nimbly deps
APP_ENV=prod SITE_NAME="My Site" ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD="change-me" PEPPER="$PEPPER" ./nimbly system:setup
./nimbly build
./nimbly help
find core -name '*.php' -print0 | xargs -0 -n1 php -l
Quando não há PHP no host, use a CLI e o lint de PHP via Docker:
APP_ENV=prod SITE_NAME="My Site" ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD="change-me" PEPPER="$PEPPER" ./nimbly --docker system:setup
docker compose -f docker/dev/docker-compose.yml run --rm --build nimbly sh -lc "find core -name '*.php' -print0 | xargs -0 -n1 php -l"
Mantenha o .env no servidor de destino e garanta que ele tenha o APP_ENV de produção, um PEPPER estável, o SITE_URL correto e as configurações de e-mail. Rodar o system:setup de novo é seguro, inclusive quando um deploy precisa criar pastas, rotas, funções, .jobs ou .htaccess que faltam.
Agendador e jobs
Em deploys manuais numa VPS, instale o orquestrador do agendador uma vez por servidor. Registre todo projeto publicado; o agendamento é necessário mesmo sem tarefas próprias da aplicação:
sudo php /var/www/site/core/cli/nimbly.php scheduler:install
sudo php /var/www/site/core/cli/nimbly.php scheduler:project:add site /var/www/site
sudo php /var/www/site/core/cli/nimbly.php scheduler:cron:install --user=www-data
Isso cria /etc/cron.d/nimbly-scheduler, que roda /usr/local/bin/nimbly-scheduler-orchestrator a cada minuto como www-data, e /etc/logrotate.d/nimbly-scheduler, que rotaciona o log do agendador semanalmente. O orquestrador então dispara os agendadores dos projetos registrados de forma independente. Em contêineres Docker o agendador roda sozinho, então não é preciso configurar o cron do host.
Rode o schedule:init uma vez para criar os arquivos de agenda do projeto em ext/cli/:
./nimbly schedule:init
Isso gera três arquivos:
| Arquivo | Usado quando |
|---|---|
ext/cli/schedule.inc |
APP_ENV=dev ou nenhum arquivo do ambiente encontrado |
ext/cli/schedule.prod.inc |
APP_ENV=prod |
ext/cli/schedule.stage.inc |
APP_ENV=stage |
As agendas de prod e stage incluem o ext:sync, que a cada hora faz commit das mudanças de dados em ext/ e as envia de volta ao repositório. A agenda de dev não. Ajuste cada arquivo separadamente para tarefas específicas de cada ambiente.
Ao migrar uma VPS existente, primeiro instale o orquestrador, registre os projetos, rode scheduler:run --dry-run, rode o wrapper uma vez à mão como www-data e confira /var/log/nimbly-scheduler.log. Faça backup do crontab antigo de www-data em /etc/nimbly/backups/ e depois remova só as linhas schedule:run por projeto que foram migradas. Não remova cron jobs que não têm relação com isso.
O git pull pelo admin continua disponível em Settings como uma opção simples de atualização para sites autogerenciados. Para sites com processo de release, prefira um caminho de deploy repetível: faça pull da revisão Git desejada, rode setup, build e lint, e garanta que o agendador esteja ativo.