Symfony7 .env Setup (Docker, .env, .env.prod, .env.local)

.env (commited to project):

### Project .env with defaults for all environments and dev defaults
#
# Additionally we use:
# - .env.uat, .env.prod with environment specific defaults
# - .env.local uncommited, with local overrides at servers (APP_ENV, secrets, eg. database credentials)
#
# Furthermore we use the following command to make .env vars available in terminal/bash scripts. This allows to NOT type secrets in command call options.
# eval "$(docker compose exec studyamo-php bin/console app:dotenv --export)"
#
#
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
#  * .env                contains default values for the environment variables needed by the app
#  * .env.local          uncommitted file with local overrides
#  * .env.$APP_ENV       committed environment-specific defaults
#  * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
# Important notice: In docker enviroments "real env vars" must be defined for the php container!
# Local shell env vars are ignored!


.env.prod (environment specific non-sensitive settings, commited):

# Non-sensitive variables for "prod" environment that differ from .env
# Sensitive variables are defined in .env.local (uncommitted)
# Note: APP_ENV cannot be set here, as this file is only called if APP_ENV is already set in .env.local

# Debugging (toolbar) if not defined: dev->true, prod->false
APP_DEBUG=false

MY_VAR=foo


.env.local (only on server, sensitive settings/secrets, uncommited):

# .env.local file for sensitive env variables like secrets.
# The variables defined here take precedence over the ones defined in the main .env file
# APP_ENV also needs to be defined here, otherwise .env.[APP_ENV] cannot be loaded

# Environment (dev, prod)
APP_ENV=prod

# Secrets go here...


If you want to make the .env vars also available as real ENV vars you can do, e.g. at your prod server (example for docker compose):

  • ssh foo@prod.example.com
  • vi ~/.profile
    • # ... Other stuffa above...
      cd ~/your-project-dir
      eval "$(docker compose exec your-project-php bin/console app:dotenv --export)"
      ls -la
      git status
      

 

src/DotEnvCommand.php

<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
    name: 'app:dotenv',
    description: 'Show or export .env variables.',
)]
class DotEnvCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->addOption('plain', null, InputOption::VALUE_NONE, 'Show variables in plain format')
            ->addOption('export', null, InputOption::VALUE_NONE, 'Show variables as export statements')
            ->setHelp(<<<'HELP'
To export variables into your current terminal session, run:
<info>eval "$(php bin/console app:dotenv --export)"</info>
HELP
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // If no options given, show help
        if (!$input->getOption('plain') && !$input->getOption('export')) {
            return $this->getApplication()->find('help')->run($input, $output);
        }

        $vars = [];
        // Nice: symfony has information about which vars are loaded from .env files
        $dotenvVars = explode(',', $_ENV['SYMFONY_DOTENV_VARS'] ?? '');
        
        foreach ($dotenvVars as $key) {
            if (isset($_ENV[$key])) {
                $vars[$key] = $_ENV[$key];
            }
        }

        // Sort keys alphabetically
        ksort($vars);

        foreach ($vars as $key => $value) {
            if ($input->getOption('export')) {
                // Properly escape the value for shell
                $escapedValue = addcslashes($value, '"\\$`');
                $output->writeln("export {$key}=\"{$escapedValue}\"");
            } else {
                $output->writeln("{$key}={$value}");
            }
        }

        return Command::SUCCESS;
    }
}