The prod Environment
APP_ENV and APP_DEBUG, a cache built once, an install without dev packages, and the two directories PHP must be able to write.
APP_ENV=prod APP_DEBUG=0 php bin/console cache:clearAn environment is a named set of configuration that the same code runs under. Symfony chooses one from the APP_ENV variable, usually dev, test or prod. APP_DEBUG is a separate switch. It turns on the debug machinery: the profiler, the detailed exception page, and the check that rebuilds compiled configuration whenever a source file changes. Production wants APP_ENV=prod and APP_DEBUG=0.
What changes in prod
With debug off, configuration is compiled once into var/cache/prod and the source files are never consulted again. Editing a YAML file on the server changes nothing until the cache is rebuilt. cache:clear rebuilds it and warms the new cache in the same run. Development dependencies are left out of the install, and the autoloader is built as a classmap: composer install --no-dev --optimize-autoloader.
Environment variables and writable paths
Parsing .env files on every request is avoidable. composer dump-env prod writes .env.local.php, a plain PHP array that Symfony loads instead of reading the .env files. Variables set in the real environment still take precedence over it.
At runtime PHP writes to two directories only: var/cache and var/log. Everything else in the project can be read-only, which is worth enforcing. The trap is ownership. If the cache is built by one user, root for instance, and requests are served by another, PHP cannot write there and every request fails with a permission error.
Example
// public/index.php
declare(strict_types=1);
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
// The Runtime component reads APP_ENV and APP_DEBUG from the real
// environment, or from .env.local.php, and passes them in $context.
// Nothing else in the application decides which environment is active.
return static function (array $context): Kernel {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
// The same closure written as an arrow function. Identical behaviour,
// and the form you will more often see in a Flex skeleton:
//
// return static fn (array $context): Kernel => new Kernel(
// $context['APP_ENV'],
// (bool) $context['APP_DEBUG'],
// );When to use it
- Write the build step of a deploy script so the prod cache is compiled before any request arrives.
- Diagnose a site that keeps serving old configuration after a YAML change on the server.
- Lock a container's filesystem to read-only except for var/cache and var/log.
- Strip development packages out of a release without breaking autoloading.
More examples
Clearing the cache, short form and long form
Setting the variables inline on the command keeps the build honest: the cache that gets written belongs to the environment named on that line, so a forgotten APP_ENV cannot leave a dev cache in a prod deployment. The two-step form is useful when you want to clear on one machine and warm on another, or when you want the warmup failure to be a separate exit code in your deploy script.
# cache:clear deletes var/cache/prod and warms the new cache in one run
APP_ENV=prod APP_DEBUG=0 php bin/console cache:clear
# the same work spelled out in two steps, which is what the line above does
APP_ENV=prod APP_DEBUG=0 php bin/console cache:clear --no-warmup
APP_ENV=prod APP_DEBUG=0 php bin/console cache:warmupInstalling dependencies and freezing the environment
Reach for this during the build step of a release. --no-dev leaves out test and debug packages, so nothing that is only meant for development can be loaded in production. dump-env is a plain optimisation: it collapses the .env chain into one PHP array. Use the --empty variant when every value is injected as a real environment variable and you want the file to carry no secrets.
# no dev packages, classmap autoloader
composer install --no-dev --optimize-autoloader
# writes .env.local.php so the .env files are not parsed on each request
composer dump-env prod
# the same result without Composer on the server, using symfony/dotenv
php bin/console dotenv:dump prodWhat the filesystem has to allow
Use this on a traditional server where a deploy user builds the release and a web server user serves it. In a container the same rule explains why a read-only root filesystem works as long as var/cache and var/log are writable mounts, and why a cache warmed at image build time must be owned by the user the container runs as.
# the only two paths PHP writes to at runtime
chown -R www-data:www-data var/cache var/log
chmod -R u+rwX var/cache var/log
Discussion