By Tcoch

Docker compose & symfony : the scope of environment variables


Why this article ?

Handling environment variables, between host machine, .env file(s), and container, is not always easy. Is my variable used when building the Dockerfile? When using docker compose? Is it passed to my container? Can it be used by my app ?
This article will address this question usingFrankenPHP for building a Symfony app.
I will focus on the APP_ENV environment variable and its actual value, depending on some cases.

Passing the environment variables

When using Symfony Docker image, some environment variables are defined to be used inside the container. Namely:
services:
  php:
    [...]
    environment:
      SERVER_NAME: ${SERVER_NAME:-localhost}, php:80
      MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
      MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
      DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-15}&charset=${POSTGRES_CHARSET:-utf8}
      MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
      MERCURE_PUBLIC_URL: ${CADDY_MERCURE_PUBLIC_URL:-https://${SERVER_NAME:-localhost}:${HTTPS_PORT:-443}/.well-known/mercure}
      MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
      SYMFONY_VERSION: ${SYMFONY_VERSION:-}
      STABILITY: ${STABILITY:-stable}
For starter, let's focus on the second one: it has the advantage to define an environment variable called MERCURE_PUBLISHER_JWT_KEY based on the definition of another environment variable called CADDY_MERCURE_JWT_SECRET, so there will be less confusion in the explantation. I think.

MERCURE_PUBLISHER_JWT_KEY will be an environment variable available in the container. Its value will either be the value of the CADDY_MERCURE_JWT_SECRET environment variable on the host machine, or the string default value !ChangeThisMercureHubJWTSecretKey!.

Now, how do you set the CADDY_MERCURE_JWT_SECRET variable value? Here's some options:
  • The environment variable exists on your host machine: it can be used
  • A more flexible option is to use a .env file, which is read by the docker tool when building the image, and in which you will set the environment value to the value you need. You can also put the value in another file, then you will need to build the image with docker compose --env-file <my_file> build
  • Alternatively, you can define the variable value in the start of your docker command, for example CADDY_MERCURE_JWT_SECRET=MySecret docker compose build
Now that this is clearer, let's deploy an actual Symfony app.

Deploying an app in dev environment

Creating an app in a dev environment is quite simple... and well explained here : https://github.com/dunglas/symfony-docker/tree/main.

When you check the various files and containers, you'll see that the APP_ENV variable has a default dev value in the frankenphp_dev image. Which means, any container using this image, will have APP_ENV=dev as its core. Cool, that's what I wanted anyway.

Deploying an app in test environment

To create the same app as before, but for the purpose of testing, we could assume that it works just as dev. Then, inside your container, you perform bin/console dump-env:test, and you're good. Well, spoiler : no.

Let's connect to that container, to see what's going on.
$ git clone https://github.com/dunglas/symfony-docker.git .
Clonage dans '.'...
[...]
$ docker compose build
[...]
  Service php  Built
$ docker compose up -d
[+] Running 1/1
  Container frankenphp_test_env-php-1  Started
$ docker exec -it frankenphp_test_env-php-1 bash
root@6c03bca33d57:/app# set | grep ENV
APP_ENV=dev
root@6c03bca33d57:/app# bin/console cache:clear

    // Clearing the cache for the dev environment with debug true

 [OK] Cache for the "dev" environment (debug=true) was successfully cleared.
OK, so, by default / by design, APP_ENV is set to dev by building the frankenphp image, and we see that here. The environment variable is indeed set to dev, and when using a console command, you see that our app uses this dev environment.
No worries, let's perform bin/console dump-env:test !
root@6c03bca33d57:/app# composer dump-env test
Successfully dumped .env files in .env.local.php
root@6c03bca33d57:/app# bin/console cache:clear

    // Clearing the cache for the dev environment with debug true


 [OK] Cache for the "dev" environment (debug=true) was successfully cleared.
Mmmh ... Symfony console still think it is in dev environment! Let me check my .env.local.php file, maybe something as gone wrong.
root@6c03bca33d57:/app# cat .env.local.php
<?php

// This file was generated by running "composer dump-env test"

return array (
    'APP_ENV' => 'test',
    'SYMFONY_DOTENV_PATH' => './.env',
    'APP_SECRET' => '***',
);
Seemed to have worked fine...
Yet, my app doesn't know it... Yes, because your server environment variable (which means in this case, the container environment variables) are preferred over the app definitions! This container-level environment variable actually override any definition that you could have in a .env file or even .env.local file.

Running tests

When using this app in your personal computer, you are in the dev environment. However, you can still run your tests, in the test environment!

For this to work, we have PHPUnit to thank, especially, the configuration given below:
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         [...]>

<php>
    <ini name="display_errors" value="1" />
    <ini name="error_reporting" value="-1" />
    <server name="APP_ENV" value="test" force="true" />
    <server name="SHELL_VERBOSITY" value="-1" />
</php>

[...]
This configuration allows for your code to know to run the PHPUnit in the test environment, with all the according definitions.