Strapi mounts each plugin's admin API at its own top-level path
(/content-manager, /upload, /i18n, ...), not nested under /admin as
previously assumed. That broke the dashboard widgets: recent/count
documents calls landed on the frontend instead of Strapi. List every
prefix actually used by the installed plugins (core-bundled ones plus
users-permissions and cloud from cms/package.json) explicitly in the
Caddyfile.
Replace the separate CMS subdomain with path-based routing in Caddy:
/admin and /uploads go to Strapi, everything else to the frontend.
Strapi 5 nests its whole admin panel (UI and API) under /admin, so this
one prefix is enough and never collides with the frontend's own /api
routes. Drops CMS_DOMAIN and the unused STRAPI_URL env var on the cms
service; PUBLIC_STRAPI_URL now points at the same origin as the site.
Authentication is unchanged: Strapi's own admin login still gates the
panel, this only changes how it's reached.
Production traffic enters through Caddy, which terminates TLS for the public
domain and the CMS subdomain and sets HSTS and the other security headers.
PostgreSQL, Strapi and Nuxt publish no ports of their own.
docker-compose.dev.yml publishes the ports on localhost and drops Caddy for
local testing. It is a separate file rather than an override.yml so it can only
be applied when passed explicitly, never by accident in production.