norsys/rothenberg
Rothenberg is a Laravel package from Norsys for building JSON:API-compliant APIs. It provides resource serialization, relationship handling, and query parameter support while following Laravel conventions for clean, maintainable integration.
Rothenberg is a thin, opinionated wrapper that puts a Symfony-capable toolchain (PHP, Composer, Symfony CLI, and usually a database) behind Docker so the host machine stays clean. The mental model to hold onto: every command you would normally run in a terminal is executed inside a container, with your project directory bind-mounted in. Once that clicks, the tool mostly disappears from your day.
Prerequisites
symfony binary, no node required on the host.git. That is genuinely the whole install surface.First three steps
PATH (global install or a clone whose bin/ you export). Confirm the exact subcommand names with rothenberg --help / rothenberg list before memorising anything — this is a small package and command names have room to drift between versions.docker-compose.yml (or compose.yaml), the Dockerfile/.docker/ directory, .env/.env.local, and any Makefile it dropped in. Those four artefacts are the tool — the CLI is mostly sugar over them.# typical first run
rothenberg --help
rothenberg create my-app # or: rothenberg init / install
cd my-app
rothenberg console about # bin/console, executed in the container
rothenberg composer require symfony/ux-turbo
First real use case: stop installing PHP on new laptops. Onboard a teammate with git clone + one bootstrap command, and verify success by hitting the app in a browser and running the test suite — if both work, the environment is correct and nobody had to touch php.ini.
Route everything through the wrapper, then alias it. The highest-value habit is never running php, composer, or bin/console directly on the host, because a single host-side composer install silently produces a vendor/ built against the wrong PHP version or missing extensions. Codify this in a Makefile so muscle memory never guesses:
cc: ; rothenberg console cache:clear
migrate: ; rothenberg console doctrine:migrations:migrate -n
test: ; rothenberg exec php vendor/bin/phpunit
sh: ; rothenberg exec php sh
logs: ; docker compose logs -f
Drop into a shell for multi-step work. For anything longer than two commands (refactors, composer + cache:clear + test chains, interactive make:* generators), exec ... sh beats prefixing every line. Interactive Symfony Maker commands in particular behave much better with a real TTY.
Database host names are service names, not localhost. Inside the container, DATABASE_URL must point at the compose service (mysql://app:app@database:3306/app), never 127.0.0.1. Keep two env files mentally separate: Symfony's .env.local for application config, and compose's environment block for infrastructure config. They are read by different programs at different times and are the #1 source of "but the variable is set".
Cache the expensive layers. A named volume for the Composer cache (~/.composer/cache inside the container) turns a 90-second composer require into a 10-second one. Do the same for node_modules if you bolt on asset building, and keep var/ out of the bind mount when the file-watching gets noisy.
Reuse the same image in CI. The strongest argument for this package is environment parity. Have GitHub Actions / GitLab CI build from the same Dockerfile and invoke the same make test target. You avoid the classic drift where CI runs PHP 8.3 and laptops run 8.2.
Bundle development. Point the container's composer.json repositories at a path or bind-mount the bundle source into a throwaway Symfony app. Rothenberg's stated use case explicitly covers bundles — this is the pattern that saves the most time, because you get instant feedback against a real kernel without publishing to Packagist.
IDE integration. Configure PHPStorm's interpreter as a Docker Compose remote interpreter (service = the PHP service) and set path mappings from your project root to the container mount point. Without the mapping, breakpoints bind but never fire, which looks like "Xdebug is broken" and is not.
Xdebug on demand. Keep it off by default (it roughly halves throughput) and expose an env flag — XDEBUG_MODE=debug or XDEBUG_MODE=coverage — that you flip per-invocation for coverage runs or step debugging.
License is NOASSERTION. This is the single biggest blocker for professional adoption: unclear licensing means you need a human to look at the LICENSE/composer.json before you make it a team dependency. Everything else here is a technicality by comparison — get this cleared first, or vendor/fork it internally with the intent recorded.
Small-project risk. At ~24 stars, expect thin documentation and slow issue turnaround. Pin to a tag or commit SHA rather than tracking a branch, and keep the generated compose/Dockerfile checked into git so that if the CLI disappears tomorrow you still have a working, hand-maintainable environment. The CLI is disposable; the artefacts are not.
File ownership on Linux. Containers that run as root leave var/cache, var/log, and vendor/ unwritable by your host user. Either match UID/GID via build args (user: "${UID}:${GID}"), or accept the occasional sudo chown -R $USER .. Symptoms: "failed to open stream: Permission denied" only after a cache warmup.
macOS/Windows bind-mount slowness. Symfony's var/cache dev directory is thousands of small files. Use :cached/VirtioFS, Mutagen, or an NFS volume for the mount. In WSL2, keep projects inside the Linux filesystem (\\wsl$ paths) — a repo under /mnt/c is 5–10× slower and it will feel like Symfony is at fault.
Port collisions. Default 8000/3306/5432/8080 clash with anything already running. Set COMPOSE_PROJECT_NAME per project and move host ports into .env-driven variables so two checkouts can coexist.
Stale container config. Editing Dockerfile or docker-compose.yml without docker compose build --no-cache (or at least up -d --build) gives you a container that silently ignores your change. When behaviour makes no sense, rebuild before you debug.
Debugging the wrapper itself. Most subcommands just shell out to docker compose. When something fails, re-run the underlying command manually (docker compose exec php bin/console ...) to see the real error and the real exit code — wrappers often swallow stderr or flatten exit codes, which breaks && chains in CI.
Don't commit generated secrets or the runtime .env. Bootstrap output frequently contains an APP_SECRET and DB passwords. Keep .env as the committed template and let .env.local / .env.<env> stay ignored.
Assets and Node are usually out of scope. Don't assume the PHP-side tooling covers Webpack Encore / Vite / yarn. Plan for a separate Node service or a host-side build; pretending otherwise is how you get an app that serves stale CSS in every environment.
Extension points worth knowing: a compose override file (compose.override.yaml) for personal tweaks like Xdebug or an extra service, the Dockerfile for adding PHP extensions, and env vars for toggling behaviour. Use the override file for anything personal so git pull never fights you.
How can I help you explore Laravel packages today?