Cockpit CMS v2 en producción: doce lecciones de cuatro sitios
Self-hosted, open-source, headless. Las cosas que nos mordieron en producción y los patrones que valen su peso.
- Publicado
- Lectura
- 11 minutos
- Temas
- Cockpit
Headless CMS
PHP
Self-hosted - Escrito por
- Pedro Thomaz
- Disciplina
- Webdesign & Ingeniería
- Compartir
Hemos lanzado cuatro sitios en producción con Cockpit CMS v2 en los últimos 18 meses. Incluyendo este. No es el headless CMS más buscado en Google — esa corona pertenece a Sanity, Strapi, Contentful, en algún orden — pero Cockpit encaja en un nicho que los nombres famosos ignoran: workloads de contenido pequeñas a medianas en infraestructura barata, editadas por gente que no quiere una sesión de formación de 90 minutos.
Aquí van doce cosas que aprendimos a las malas. Si estás evaluando Cockpit, esto te ahorra una semana.
1. Cockpit v2 no es Cockpit v1
Si un tutorial es anterior a 2024, tíralo. La forma de la API cambió. El layout de directorios cambió. El admin UI cambió. El data model cambió. v2 es, a efectos prácticos, otro producto. Filtra los resultados de búsqueda por "v2" o "2024+".
2. Self-hosting significa que eres dueño del WAL
El storage default de Cockpit v2 es SQLite. SQLite usa Write-Ahead Logging por defecto. Los archivos .sqlite-wal y .sqlite-shm importan. Si haces rsync solo del archivo .sqlite a un servidor nuevo, pierdes writes recientes. O haces checkpoint primero (PRAGMA wal_checkpoint(FULL)) o copias los tres archivos juntos.
3. El caché de schema es real
Después de editar un model JSON, Cockpit cachea el schema parseado en storage/data/app.memory.sqlite. Hasta que borres ese archivo, el admin UI muestra campos viejos. Mételo en tu deploy script.
4. i18n es por campo, no por documento
Marcas campos como i18n: true en el schema. Cockpit los guarda como field (locale default) y field_pt, field_es, etc. La API sirve el locale pedido vía ?locale=pt. Planifica el schema con eso en mente; meterlo a posteriori es trabajo manual de SQL.
5. La API key en la URL es un tiro al pie
No pases la API key como query parameter. Acaba en logs, en historial de browser, en referrer headers. Usa el header HTTP api-key. Construye tu fetch helper para forzarlo.
6. Los assets de imagen necesitan proxy
La API de imágenes de Cockpit exige la API key. Si referencias URLs de Cockpit directo desde el frontend, expones la key. Escribimos un proxy PHP de 30 líneas que toma ?id=&w=, hace fetch con la key del lado servidor, y devuelve WebP con headers de cache. Esto vale para cualquier headless CMS con asset API privada.
7. Self-HTTPS loopback se rompe en algunos hosts
Cuando PHP en ejemplo.com intenta llamar a https://ejemplo.com/cockpit/api/..., algunos shared hosts rechazan el loopback. file_get_contents() devuelve false sin error. Solución: usa cURL (funciona donde file_get_contents no) con fallback sensato. No asumas que tu entorno local coincide con producción.
8. Cockpit no tiene webhook nativo para "contenido cambió"
Hay un event system dentro del PHP de Cockpit, pero no hay webhook outbound out-of-the-box al guardar. Si necesitas invalidar un CDN al publicar, escribes el puente: un addon pequeño de Cockpit que escucha collection.save.after y hace POST al endpoint de purge del CDN.
9. El JSON del model es tu source of truth
El UI de Cockpit te deja construir models visualmente, pero mete los archivos JSON del model en version control. Guárdalos en el repo junto al código del sitio. Si tu instancia de Cockpit muere, puedes reconstruir el schema entero desde esos archivos. Los datos en sí viven en SQLite — haz backup aparte.
10. Roles y API keys están desacoplados
Una API key tiene un role atado a la creación. Cambiar el role de una API key en el admin UI obliga a borrar la key y crear una nueva. No cuelgues API keys en un deploy antes de decidir qué permisos necesita.
11. Markdown no es el default — WYSIWYG es
Si quieres devs escribiendo en Markdown y editores escribiendo en WYSIWYG, eliges el widget WYSIWYG. El output es HTML. Embebe Prism.js o similar en el lado de rendering si quieres code blocks con syntax highlighting; Cockpit no hace eso por ti.
12. La comunidad es pequeña pero responsiva
Los GitHub issues de Cockpit reciben atención directa del maintainer (Artur Heinze). La documentación es irregular — lee el source. Es el precio de admisión de un CMS sin VC detrás: menos Stack Overflow, más leer modules/Content/Controller/Api.php cuando algo se comporta raro. Lo encontramos más fiable que lidiar con paid support de proveedores con VC.
Los mensajes de error, y lo que significan de verdad
Ninguno de estos produce un stack trace. Todos parecen otra cosa — por eso cuestan horas. Todos ocurrieron en este sitio.
Un modelo recién creado devuelve 404 en la API
Creaste tender_source, el admin lo muestra, y /api/content/items/tender_source responde 404. El modelo se guardó con otro nombre: Content/Helper/Model.php ejecuta preg_replace('/[^A-Za-z0-9]/', '', $name) al crear, y tender_source quedó como tendersource. Los guiones bajos y medios desaparecen en silencio, sin aviso ni error. En un nombre de modelo, solo letras y dígitos.
createModel() y updateModel() devuelven false en vez de lanzar
Una escritura de modelo fallida no es una excepción. Ambos devuelven false, así que el try/catch no atrapa nada y un importador que solo vigile excepciones anunciará éxito sin haber creado nada. Comprueba el valor devuelto y confirma después con exists() — la fuente de la verdad es lo guardado, no el retorno.
Un POST parcial creó un ítem duplicado en vez de actualizar
saveItem() fusiona cuando el payload lleva _id — array_merge($current, $item) — así que enviar dos campos actualiza dos campos y deja el resto intacto. Sin _id, el mismo payload se fusiona con los valores por defecto del modelo e inserta un ítem nuevo. Entre actualizar y duplicar hay una clave de diferencia.
Las traducciones parecen haber desaparecido tras guardar
Los campos i18n se escriben con claves de sufijo en el payload por defecto — title_pt, body_es — pero leer el ítem sin ?locale= devuelve solo el idioma por defecto, sin ninguna clave de sufijo en la respuesta. No se perdió nada; la lectura está localizada. Pasa ?locale=pt para verlas. Y si filtras a la vez, el filtro debe ser una cadena JSON, no un array de query anidado.
Página en blanco tras cambiar la versión de PHP
Cockpit 2.14 exige PHP 8.3. En cualquier versión anterior, bootstrap.php llega al platform_check.php de Composer, que llama a die() — no es una excepción. Ningún try/catch lo intercepta, así que cualquier página que arranque Cockpit sale en blanco, sin nada en el cuerpo de la respuesta. Si tu alojamiento usa una versión de PHP para la web y otra para el cron, la tarea programada muere así mientras el sitio sigue funcionando.
¿Buscando entries-batchedit.tag?
Entonces estás leyendo documentación de Cockpit v1. Los archivos .tag de riot no existen en v2 — la edición por lotes vive en Content/views/collection/items.php. La mayoría de las respuestas sobre Cockpit que hay en la web son anteriores a v2 y describen otro producto; ver la lección 1.
Preguntas frecuentes
¿Puedo seguir tutoriales de Cockpit CMS anteriores a 2024 para la v2?
No — Cockpit CMS v2 no es la v1, así que debes descartar los tutoriales anteriores a 2024. Muchos patrones y comportamientos cambiaron entre las versiones principales.
¿Cómo hago una copia de seguridad segura de una base de datos SQLite de Cockpit autoalojado?
Como SQLite usa Write-Ahead Logging (WAL), copia los archivos .sqlite, -wal y -shm juntos, o haz un checkpoint primero. Autoalojar significa que el WAL es tuyo, por lo que copiar solo el archivo principal arriesga una copia inconsistente.
Mis cambios de esquema no aparecen tras editar un modelo, ¿por qué?
Cockpit tiene una caché de esquema real, así que elimina storage/data/app.memory.sqlite tras editar modelos. El JSON del modelo es tu verdadera fuente de verdad, así que ponlo bajo control de versiones.
¿Cómo gestiona Cockpit la internacionalización y cómo debo pasar la clave de API?
La i18n es por campo, no por documento — trabajas con field, field_pt y field_es, solicitando una localización con ?locale=pt. Pasa las credenciales en la cabecera api-key en lugar de en la URL, lo cual es una trampa.
¿Cómo purgo una CDN cuando el contenido cambia en Cockpit?
Cockpit no tiene un webhook saliente integrado para eventos de contenido modificado, así que escribe un addon en collection.save.after para purgar la CDN. Para las imágenes, usa un proxy que mantenga la clave en el servidor y devuelva WebP.
En resumen
Si necesitas un CMS que sea: gratis, self-hostable en infra barata, rápido de editar, con i18n first-class y REST API en serio — Cockpit es la opción más fuerte en 2026. Si necesitas edición colaborativa en tiempo real, un ejército de integraciones out-of-the-box, o documentación de la mano — elige otro. El trade-off es honesto. Lo pagamos con gusto.
Cuéntanos lo que solo existió en palabras.
o escribe a info@amplifiedcreations.com · responde un miembro sénior del equipo