Cockpit CMS v2 em produção: doze lições de quatro sites
Self-hosted, open-source, headless. As coisas que nos morderam em produção e os padrões que valem o peso.
- Publicado
- Leitura
- 11 minutos
- Temas
- Cockpit
Headless CMS
PHP
Self-hosted - Escrito por
- Pedro Thomaz
- Disciplina
- Webdesign & Engenharia
- Partilhar
Lançámos quatro sites em produção com Cockpit CMS v2 nos últimos 18 meses. Incluindo este. Não é o headless CMS mais pesquisado no Google — essa coroa pertence ao Sanity, Strapi, Contentful, por alguma ordem — mas o Cockpit encaixa num nicho que os nomes famosos ignoram: workloads de conteúdo pequenas a médias em infraestrutura barata, editadas por pessoas que não querem uma sessão de formação de 90 minutos.
Eis doze coisas que aprendemos à força. Se estás a avaliar Cockpit, isto poupa-te uma semana.
1. Cockpit v2 não é Cockpit v1
Se um tutorial é anterior a 2024, deita fora. A forma da API mudou. O layout de diretorias mudou. O admin UI mudou. O data model mudou. v2 é, para efeitos práticos, um produto diferente. Filtra os resultados de pesquisa por "v2" ou "2024+".
2. Self-hosting significa que és o dono do WAL
O storage default do Cockpit v2 é SQLite. SQLite usa Write-Ahead Logging por defeito. Os ficheiros .sqlite-wal e .sqlite-shm interessam. Se fizeres rsync só do ficheiro .sqlite para um servidor novo, perdes writes recentes. Ou fazes checkpoint primeiro (PRAGMA wal_checkpoint(FULL)) ou copias os três ficheiros juntos.
3. O cache de schema é real
Depois de editar um model JSON, o Cockpit faz cache do schema parseado em storage/data/app.memory.sqlite. Até apagares esse ficheiro, o admin UI mostra campos antigos. Mete isto no teu deploy script.
4. i18n é por campo, não por documento
Marcas campos como i18n: true no schema. O Cockpit guarda como field (locale default) e field_pt, field_es, etc. A API serve o locale pedido via ?locale=pt. Planeia o schema com isto em mente; pôr i18n depois é trabalho manual de SQL.
5. A API key no URL é um tiro no pé
Não passes a API key como query parameter. Vai parar a logs, ao histórico do browser, a referrer headers. Usa o header HTTP api-key. Constrói o teu fetch helper para o forçar.
6. Assets de imagem precisam de proxy
A API de imagens do Cockpit exige a API key. Se referenciares URLs do Cockpit diretamente do frontend, expões a key. Escrevemos um proxy PHP de 30 linhas que aceita ?id=&w=, faz fetch com a key no servidor, e devolve WebP com headers de cache. Isto é verdade para qualquer headless CMS com asset API privada.
7. Self-HTTPS loopback parte em alguns hosts
Quando PHP em exemplo.com tenta chamar https://exemplo.com/cockpit/api/..., alguns shared hosts recusam o loopback. file_get_contents() devolve false sem erro. Solução: usa cURL (funciona onde file_get_contents não funciona) com fallback sensato. Não assumas que o teu ambiente local é igual ao de produção.
8. Cockpit não tem webhook nativo para "conteúdo mudou"
Há um event system dentro do PHP do Cockpit, mas não há webhook outbound out-of-the-box em save. Se precisas de invalidar um CDN no publish, escreves a ponte: um pequeno addon do Cockpit que ouve collection.save.after e faz POST para o endpoint de purge do CDN.
9. O JSON do model é a tua source of truth
O UI do Cockpit deixa-te construir models visualmente, mas mete os ficheiros JSON do model em version control. Guarda-os no repo ao lado do código do site. Se a tua instância do Cockpit morrer, podes reconstruir o schema todo a partir desses ficheiros. Os dados em si vivem em SQLite — faz backup à parte.
10. Roles e API keys estão desacoplados
Uma API key tem uma role anexa à criação. Mudar a role de uma API key no admin UI obriga a apagar a key e criar uma nova. Não pendures API keys num deploy antes de decidir que permissões precisa.
11. Markdown não é o default — WYSIWYG é
Se queres devs a escrever em Markdown e editores a escrever em WYSIWYG, escolhes o widget WYSIWYG. O output é HTML. Embebe Prism.js ou similar no lado de rendering se quiseres code blocks com syntax highlighting; o Cockpit não faz isso por ti.
12. A comunidade é pequena mas responsiva
As GitHub issues do Cockpit recebem atenção direta do maintainer (Artur Heinze). A documentação é irregular — lê o source. É o preço de admissão de um CMS sem VC por trás: menos Stack Overflow, mais ler modules/Content/Controller/Api.php quando algo se comporta de forma estranha. Achamos isto mais fiável do que lidar com paid support em fornecedores com VC.
As mensagens de erro, e o que querem mesmo dizer
Nenhuma destas produz um stack trace. Todas se parecem com outra coisa qualquer — é por isso que custam horas. Todas aconteceram neste site.
Um modelo acabado de criar devolve 404 na API
Criaste tender_source, o admin mostra-o, e /api/content/items/tender_source responde 404. O modelo foi gravado com outro nome: o Content/Helper/Model.php corre preg_replace('/[^A-Za-z0-9]/', '', $name) ao criar, e tender_source ficou tendersource. Underscores e hífenes desaparecem em silêncio, sem aviso nem erro. Num nome de modelo, só letras e dígitos.
createModel() e updateModel() devolvem false em vez de lançar
Uma gravação de modelo que falha não é uma exceção. Ambos devolvem false, por isso o try/catch não apanha nada e um importador que só vigie exceções anuncia sucesso sem ter criado nada. Verifica o valor devolvido e confirma a seguir com exists() — a fonte da verdade é o que está gravado, não o retorno.
Um POST parcial criou um item duplicado em vez de atualizar
O saveItem() funde quando o payload leva _id — array_merge($current, $item) — por isso enviar dois campos atualiza dois campos e deixa o resto quieto. Sem _id, o mesmo payload funde com os valores por omissão do modelo e insere um item novo. Entre atualizar e duplicar vai uma chave.
As traduções parecem ter desaparecido depois de gravar
Escrevem-se os campos i18n com chaves de sufixo no payload por omissão — title_pt, body_es — mas ler o item de volta sem ?locale= devolve só a língua por omissão, sem nenhuma chave de sufixo na resposta. Não se perdeu nada; a leitura é que é localizada. Passa ?locale=pt para as ver. E quando filtras ao mesmo tempo, o filtro tem de ser uma string JSON, não um array de query aninhado.
Página em branco depois de mudar a versão de PHP
O Cockpit 2.14 exige PHP 8.3. Em qualquer versão anterior, o bootstrap.php chega ao platform_check.php do Composer, que chama die() — não é uma exceção. Nenhum try/catch a intercepta, por isso qualquer página que arranque o Cockpit sai em branco, sem nada no corpo da resposta. Se o teu alojamento tem uma versão de PHP para a web e outra para o cron, a tarefa agendada morre assim enquanto o site continua a funcionar.
À procura de entries-batchedit.tag?
Então estás a ler documentação do Cockpit v1. Os ficheiros .tag do riot não existem no v2 — a edição em lote vive em Content/views/collection/items.php. A maior parte das respostas sobre Cockpit que há na web é anterior ao v2 e descreve outro produto; ver a lição 1.
Perguntas frequentes
Posso seguir tutoriais de Cockpit CMS anteriores a 2024 para a v2?
Não — o Cockpit CMS v2 não é a v1, por isso deve deitar fora os tutoriais anteriores a 2024. Muitos padrões e comportamentos mudaram entre as versões principais.
Como faço uma cópia de segurança segura de uma base de dados SQLite do Cockpit auto-alojado?
Como o SQLite usa Write-Ahead Logging (WAL), copie os ficheiros .sqlite, -wal e -shm em conjunto, ou faça checkpoint primeiro. Auto-alojar significa que o WAL é seu, por isso copiar apenas o ficheiro principal arrisca uma cópia inconsistente.
As minhas alterações de schema não aparecem depois de editar um modelo — porquê?
O Cockpit tem um cache de schema real, por isso apague storage/data/app.memory.sqlite depois de editar modelos. O JSON do modelo é a sua verdadeira fonte de verdade, por isso coloque-o sob controlo de versões.
Como é que o Cockpit trata a internacionalização e como devo passar a chave de API?
A i18n é por campo, não por documento — trabalha com field, field_pt e field_es, pedindo uma localização com ?locale=pt. Passe as credenciais no cabeçalho api-key em vez de no URL, o que é uma armadilha.
Como purgo um CDN quando o conteúdo muda no Cockpit?
O Cockpit não tem webhook de saída integrado para eventos de conteúdo alterado, por isso escreva um addon em collection.save.after para purgar o CDN. Para imagens, use um proxy que mantenha a chave do lado do servidor e devolva WebP.
Em resumo
Se precisas de um CMS que seja: gratuito, self-hostable em infra barata, rápido de editar, com i18n first-class e REST API a sério — o Cockpit é a melhor opção em 2026. Se precisas de edição colaborativa em tempo real, um exército de integrações out-of-the-box, ou documentação de mão dada — escolhe outro. O trade-off é honesto. Pagamo-lo com gosto.
Conta-nos o que só existiu em palavras.
ou escreve para info@amplifiedcreations.com · responde um membro sénior da equipa