netgusto/devserver-bundle
Symfony bundle that integrates Netgusto's DevServer, giving you a lightweight local development server with automatic request routing and a simple console command to start serving your application.
netgusto/devserver-bundle is a thin wrapper around PHP's built-in web server, packaged as a Symfony console command. It exists to solve two annoyances of php -S/server:run in a Symfony app: subdomain-based routing (multi-tenant, per-tenant hosts) and assets being compiled before the server starts so you don't stare at an unstyled page on every boot. It's aimed at Symfony 2.8/3.x-era projects using Assetic; if you're on Symfony 4+ with Encore, most of its value is already gone.
Before wiring anything up, be honest about the state of the package: zero stars, zero dependents, no recipe. Treat it as unvetted code you are adopting, not a dependency you are consuming.
composer require --dev netgusto/devserver-bundle
If it isn't on Packagist (likely), add a VCS repository entry to composer.json and require it as dev-master — then pin the commit hash in composer.lock and vendor-review the command class, since you're now executing third-party code on every bin/console boot in dev.
Register it in the dev environment only:
// app/AppKernel.php (Symfony 2/3)
if (in_array($this->getEnvironment(), ['dev'], true)) {
$bundles[] = new Netgusto\DevServerBundle\NetgustoDevServerBundle();
}
// config/bundles.php (Symfony 4+, if you backport it)
Netgusto\DevServerBundle\NetgustoDevServerBundle::class => ['dev' => true],
Don't guess the command name — the first thing to do is ask the container:
bin/console list | grep -i -E 'server|dev'
bin/console help <command-name>
That help output is your real documentation: it will list the host, port, and subdomain options the version you installed actually supports, plus whether it exposes a flag to skip the asset dump. Second place to look is Configuration.php in the bundle (if it has one) and the command class itself — that's where the router script and the dumper invocation live.
First use case: you're building a multi-tenant app where acme.myapp.dev and globex.myapp.dev should resolve to the same code but different tenants. Start the server, then hit a tenant subdomain and confirm $request->getHost() returns it rather than localhost:
php bin/console <devserver-command> --host=0.0.0.0 --port=8000
curl -H 'Host: acme.myapp.dev' http://127.0.0.1:8000/
Skip /etc/hosts entirely. Use lvh.me or nip.io, which resolve *.lvh.me to 127.0.0.1 via public DNS. You get arbitrary subdomains with zero machine setup and zero sudo:
http://acme.lvh.me:8000/
http://globex.lvh.me:8000/
Configure your tenant resolution to key off the leftmost label and to tolerate the .lvh.me suffix (or strip it), so the same code path works in dev and prod.
Subdomain-aware routing. With the Host header preserved, normal Symfony host matching works:
tenant_home:
path: /
host: "{tenant}.lvh.me"
defaults: { _controller: AppBundle:Tenant:home }
Note the port is not part of the host pattern, but it is part of the scheme-and-host Symfony validates against if you hardcode http://acme.lvh.me anywhere — build URLs with $request->getSchemeAndHttpHost() instead.
Make the asset step explicit in your workflow. The auto-compilation is a convenience for the first page load, not a file watcher. The reliable pattern is to keep a terminal running the dumper in watch mode (if your asset pipeline supports it) and use the bundle's built-in dump only as a bootstrap. Wrap both in a Composer script so onboarding is one command:
"scripts": {
"dev": [
"php bin/console assets:install --symlink web",
"php bin/console <devserver-command> --host=0.0.0.0 --port=8000"
]
}
Docker/VM: bind 0.0.0.0, not localhost. The default bind to 127.0.0.1 is invisible from the host machine, which is the single most common "it started but I can't reach it" report.
Don't fight it for TLS. If you need HTTPS (OAuth callbacks, Secure cookies, service workers), put a real reverse proxy in front (nginx, Caddy, or symfony serve) and use this bundle only for the Host-header/routing behavior — or just drop it, because a proxy does subdomains trivially.
Compose with a real router when you outgrow it. For anything beyond a single developer's laptop, replace it with symfony serve --domain or dnsmasq + nginx server blocks. The migration is mechanical; keeping it in place past that point costs more than it saves.
It is almost certainly abandoned. Symfony shipped WebServerBundle (server:run, server:start), and that in turn was superseded by the Symfony CLI (symfony serve), which handles subdomains via --domain, provides a local TLS CA, and is actively maintained. If you're starting fresh in 2024+, use the CLI. Adopt this bundle only to unblock a legacy project.
Assets: verify which dumper it calls. "Automatic assets compilation" in this era usually means assetic:dump. If you've moved to Webpack Encore, the step is a no-op at best and a slow startup at worst. Read the command class and check whether it shells out to a console command (which you can override/replace) or calls a service directly.
The built-in server is single-process. One slow request blocks everything — including the profiler toolbar's XHR back to the app, which can produce phantom deadlocks where the page "hangs" waiting on its own sub-request. On PHP 7.4+ you can set PHP_CLI_SERVER_WORKERS=4 in the environment before launching to get concurrency; older PHP cannot.
Host/session mismatch bites in three places:
framework.trusted_hosts / Request::setTrustedHosts() will 400 your wildcard subdomains if configured. Add ^(.*\.)?lvh\.me$ or remove the restriction in dev.acme.lvh.me won't be sent to globex.lvh.me — set framework.session.cookie_domain: ".lvh.me" if you need cross-tenant sessions (usually you don't, and this is a bug, not a feature).router.request_context.host in parameters.yml is what CLI-generated URLs use; it won't match your subdomains. Set it per request or ignore CLI URL generation in dev.Ports don't auto-increment. "Address already in use" means kill the stale php -S (the bundle may not clean up on Ctrl-C in all shells) — lsof -i :8000 is faster than guessing.
Xdebug: the server runs as a separate PHP process. XDEBUG_MODE=debug on the parent shell is not inherited unless you export it before launching, and xdebug.start_with_request=yes is required since there's no browser-triggered cookie handling in php -S.
Extension points are limited, so use decoration. You can override the command service in services_dev.yml, or replace the bundle's router script with your own if you need custom static-file handling — but the router script is the load-bearing part, so diff it against your app's web/ docroot assumptions. If your front controller isn't web/app_dev.php (Symfony 4+ public/index.php), expect to patch it.
Finally: audit before you ship. With no stars and no dependents, there's no community pressure that produced this code and none that will fix it. Read the ~200 lines, confirm it doesn't eval or write outside the project, and keep the require-dev boundary strict so it can never reach production.
How can I help you explore Laravel packages today?