Running It Locally
Serve the project with symfony server:start, get HTTPS locally, and let the CLI wire up Docker services for you.
symfony server:start -d # same command as: symfony serve -dSymfony needs no web server of its own in development. Any server whose document root is public/ works. The Symfony CLI adds one that knows about the project, which removes most of the setup.
Starting it
symfony server:start runs in the foreground and streams logs; Ctrl-C stops it. symfony serve is the same command under a shorter name. Add -d to detach:
symfony server:start -d # detached symfony serve -d # identical symfony server:log # follow the logs of a detached server symfony server:stop
Which PHP it uses
The server picks the PHP binary, and .php-version in the project root pins it. symfony local:php:list shows what is installed. Prefixing a command — symfony php -v, symfony composer install — runs it under that same version, which is how you avoid installing dependencies with one PHP and serving with another.
HTTPS locally
symfony server:ca:install installs a local certificate authority once per machine. Restart the server afterwards and it serves https://127.0.0.1:8000 with a certificate your browser trusts. This matters for anything that behaves differently over TLS: secure cookies, service workers, third-party widgets.
Docker, without the wiring
If the project has a compose.yaml, the CLI reads the running services and exports environment variables for them — a PostgreSQL service becomes DATABASE_URL, Redis becomes REDIS_URL. symfony var:export --debug prints exactly what it is injecting. The application keeps reading plain environment variables and never learns Docker exists.
Example
<?php
// src/Controller/EnvironmentController.php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class EnvironmentController extends AbstractController
{
// A page that reports what is actually serving it. Useful right
// after setting up TLS or switching .php-version.
#[Route('/_local', name: 'app_local_info', methods: ['GET'])]
public function info(Request $request): Response
{
// Request is handed to the method by Symfony because of the
// type declaration; nothing has to be fetched or constructed.
$lines = [
'php: ' . PHP_VERSION,
'scheme: ' . $request->getScheme(), // http or https
'host: ' . $request->getHttpHost(),
'secure: ' . ($request->isSecure() ? 'yes' : 'no'),
];
return new Response('<pre>' . implode("\n", $lines) . '</pre>');
}
}When to use it
- Serve a new project without configuring Apache or Nginx.
- Reproduce a bug that only appears over HTTPS, such as a cookie with the Secure flag.
- Run a Postgres container for local development without hard-coding its port into .env.
More examples
Pinning the PHP version for the project
Reach for this when two projects on one machine need different PHP versions, or when composer install resolves dependencies against a PHP the web server does not use. The file is committed, so teammates get the same version.
# Pin an exact version for this project only.
echo 8.5 > .php-version
# Which versions the CLI can see.
symfony local:php:list
# Run tooling under the pinned version rather than the system default.
symfony php -v
symfony composer install
symfony console cache:clear
# Restart so the server picks the change up.
symfony server:stop
symfony server:start -dTrusted HTTPS on localhost
Needed as soon as something refuses to work over plain HTTP: cookies marked Secure, OAuth redirect URLs that must be https, or a browser API restricted to secure contexts. The named domain also keeps absolute URLs stable between restarts.
# Once per machine: install a local certificate authority.
symfony server:ca:install
# Restart; the server now answers on https.
symfony server:stop
symfony server:start -d
# Optional: a stable hostname instead of 127.0.0.1:8000
symfony proxy:start
symfony proxy:domain:attach my-app
# the project is now reachable at https://my-app.wipDatabase in Docker, application on the host
The usual local arrangement: services in containers, PHP on the host where the debugger and the editor already work. Note the port is published without a fixed host port, so Docker picks a free one and the CLI reports whichever it chose.
# compose.yaml
services:
database:
image: postgres:17
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: app
ports:
- "5432"
# docker compose up -d
# symfony var:export --debug -> prints DATABASE_URL, DATABASE_HOST, ...
# symfony server:start -d -> the application sees those variables
Discussion