Configuring PHP Applications using Environment Variables

Configuring PHP Applications using Environment Variables

Mauro Chojrin's photo
Mauro Chojrin

Published on Aug 25, 2021

12 min read

Subscribe to our newsletter and never miss any upcoming articles

TLDR: Using a secrets manager as the central source of truth for storing and injecting secrets into PHP applications is the easiest and most secure method of using environment variables for configuration.


Every mid-sized to big web application has to deal with sensitive information. Examples of this type of information are credentials to access databases and third party APIs keys.

If this data is not encrypted at rest, attackers might get access to it and use it for malicious purposes.

When you first start it’s very common to find this information stored right into the code where the value is being used (a.k.a. hardcoded) and, while this works, it quickly becomes obvious that it’s not a good practice for the long run since making a small change means going through hundreds or even thousands of lines of code.

The next evolutionary step is to take these values and dynamically generate a config file during deployment (e.g. GitHub Action)


  return [
      'db_host' => 'localhost',
      'db_user' => 'app_user',
      'db_pass' => 'thaeD3gohdu2'
];

Or some other more elaborate formats such as YAML, XML or INI.

The advantage of using external files is the fact that when change is needed there’s a well known (and single!) location to make those changes and things just keep working as expected.

But the problem is where to store these secrets? If they’re embedded in a GitHub Action file, they still don’t offer much secrecy as any person with access to the codebase can view the values you’re supposed to be keeping away from unwanted eyes.

Configuring PHP Applications Using Environment Variables One very good way to solve this problem is configuring applications using environment variable as then, no app config values live in the codebase.

One important note: this mechanism is by no means exclusive to PHP and is just as useful if you’re working with Nodejs, Python or pretty much any other programming language.

If you were to use an environment variable in a PHP CLI application you could call it using a command such as:

export MY_VAR=value
php myscript.php

And this value would be available to your script via the getenv function function, like this:

<?php

echo getenv('MY_VAR').PHP_EOL;

There’s another way to do it, using the superglobal $_ENV, like this:

<?php

echo $_ENV['MY_VAR'];

I recommend only using getenv as in order to use the $_ENV superglobal, your php.ini file requires the value for variables_order starting with a letter “E” which may not always be the case.

Setting environment variables for a CLI application is simple, but what about in a web environment where the PHP process is spawned by an application server such as NGINX or Apache?

Application servers (for good security reasons) don’t expose system level environment variables to the PHP process, so you’ll need a different approach.

Let’s see how you can set environment variables for the most popular web servers: Apache and NGINX and we'll be using Docker so you can easily follow along with the following examples. How to Set Environment Variables for PHP Applications in Apache

Apache uses the mod_env module for defining environment variables using the SetEnv directive.

The best way to do this for PHP in Apache is by creating a separate Apache configuration file to will their values.

Using Debian and Ubuntu as an example, the config file containing the environment variables should be stored in the /etc/apache2/conf-available directory.

In our case we’ll call it apache-env-vars.conf, using the SetEnv directive to define each environment variable:

SetEnv MY_VAR "MY_VALUE"

After doing this, the configuration must be enabled in order for Apache to load it when starting, but how this is done depends upon where you’re running Apache.

If using a Virtual Machine running Debian or Ubuntu, you’ll need to create a conf file at /etc/apache2/conf/env-vars.conf, then run a2enconf env-vars to enable it to be loaded on startup.

If Apache is already running when this file is created, then must restart Apache by running sudo service apache2 restart in order for the environment variables in the conf file to be loaded.

Using Docker and the php:7-apache image is similar except the conf file will need to be created in the $APACHE_CONFDIR/conf-available/ directory, then run a2enconf env-vars as part of the container start commands. How to Set Environment Variables for PHP Applications in NGINX When it comes to NGINX, a similar strategy should be applied, the main difference is that NGINX doesn’t spawn PHP processes directly as it only proxies requests to PHP-FPM. This means the environment variables must be defined in a PHP-FPM configuration file, typically stored in /usr/local/etc/php-fpm.d/.

Setting environment variables for PHP-FPM uses the following syntax (do not quote values):

env[MY_SECRET] = 123
env[OTHER_SECRET] = abc
`

So, in order to store your environment variables in NGINX and PHP-FPM, it’s a good idea to create a new file at /usr/local/etc/php-fpm.d/env-vars.conf and put the definitions in it.

Just as in the case of Apache, it makes sense to create this file when running the container. How Should Environment Variables be Populated? Environment variables are the best solution for providing your PHP application with secrets and configuration data, but where should they be stored? We know they should not be stored in source code, but what are the other options?

One problem with storing environment variables values in the web server or the operating system configuration files is the fact that they live outside of your project, which increases the complexity of deploying your code in multiple locations. Are .env Files the Answer? An .env file is a plain text file which contains environment variables definitions which are designed so your PHP application will parse them, bypassing the Apache, NGINX and PHP-FPM.

The usage of .env files is popular in many PHP frameworks such as Laravel which has built-in support for parsing .env files, or using the vlucas/phpdotenv library.

Using .env files in PHP is simple, all that’s needed is a mechanism to read the file, parse it’s contents and populate the environment, right?

Well… actually there are a few other issues to pay attention to such as validation and checking for existence of required values.

As usual, why go through all this trouble when someone else has already done it for you, right? Exactly, you can leverage the vlucas/dotenv package and simply make use of it by requiring it using composer.

To add this package to your application, run composer require vlucas/dotenv, then require the autoload file located at your vendor directory like this:


require_once 'vendor/autoload.php';

And you’ll be ready to use the library like this:

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(__DIR__); $dotenv->load();

After that you can simply access the values stored in the .env file using getenv. Where Should the Contents of .env Files be Stored?

Clearly, since .env files hold potentially sensitive data they should not be stored in the publicly accessible webroot as NGINX and Apache see them as a text file and don’t know they should be protected by default. And they definitely should never be committed to your source code repository.

Usually they are stored at the root directory of your application. How to Use an .env File for Laravel Applications

Laravel uses .env files as part of its configuration and automatically bundles in the vlucas/dotenv package. In fact, when you create a new project a sample .env file is created for you.

One difference you can find when working with .env files in Laravel projects as opposed to non-Laravel ones is the existence of a helper called env which can be used to access the values of environment variables, like this:

$val = env(‘ENV_VAR’);

While there’s the possibility of using the second parameter to provide a default value, it’s not recommended. Why? Because having to browse PHP code in order to see how a default value was set is confusing. It’s therefore much better to to define everything in the .env file so there is only one place to look to see how an application is configured. How to supply secrets as environment variables to PHP applications via .env files using Docker

Values stored within .env files can be passed to PHP applications in Docker in several ways. Passing Environment Variables when Running the Container

One option is to populate the environment variables for the container using the --env-file option.

docker run -it --env-file=.env your-image

The limitation of this approach is that it’s only useful for PHP CLI applications, as container (system) defined environment variables will not be passed through by web servers for security reasons. Passing Environment Variables using a Volume Mount

A more common way to use an .env file would be to mount it into the docker container:

docker run -v $(pwd)/.env:/usr/src/app/.env -it my-app

From there, your application can access the .env file stored at /usr/src/app/.env within the container simply by doing the following:

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(‘/usr/src/app’);
$dotenv->load();

But before deciding that .env files are the way to go, it’s important to understand the risks and drawbacks to using .env files for app config and secrets. Why .env Files are Not the Best Solution

While .env files are certainly better than hard-coding secrets in source code, they create a new set of problems:

There’s a chance .env files will be accidentally committed to a code repository .env files synchronization across environments can be tricky There’s the risk of sensitive unencrypted data being shared over not-so-secure channels Syntax is not standard across tools

So… how should you handle your application’s secrets? Using a Secrets Manager for PHP Applications

In order to avoid these problems and others, using an environment variables manager such as Doppler can make life easier.

All of the environment variables for your application can be easily managed through the Doppler dashboard and secrets can be fetched using their CLI or API.

To see what it’s like to use a secrets manager with PHP, let’s look at a couple of examples using Doppler to populate the secrets to PHP applications using environment variables. How to Supply Secrets as Environment Variables to PHP Applications in Docker using Doppler

The general idea of the following examples is to use Doppler as the single source of truth when it comes to secrets management and use the Doppler CLI to download secrets and make them available to your Docker PHP application.

There are basically two decisions to be made here:

Do you want to have the Doppler CLI available inside your Docker containers? Do you prefer to use web server configuration files or .env files?

In the following sections, I’ll explore the different options so you can choose the one that makes most sense for you. Doppler CLI Generated Apache Conf File

If you prefer to keep your Docker image as is, you can run the Doppler CLI in the deployment or container hosting environment to generate the conf file and mount inside the container.

The exact commands you’ll need to start your container depend on whether you choose configuration or .env files.

We’ll use Apache conf files as the example here, but the process is essentially the same, just syntactically different for PHP-FPM or a .env file. External Apache Conf File Mount

In order to download the latest version of your secrets into an Apache configuration file, you’ll need to use the Doppler CLI to generate the Apache conf file before running your container:

doppler secrets download --no-file --format json | jq -r '. | to_entries[] | "SetEnv \(.key) \"\(.value)\""' > env-vars.conf

The mount the env-vars.conf file inside the container:

docker run --rm -it --init \ --name php \ -v "$(pwd)/app":/var/www/ \ -v "$(pwd)/env-vars.conf":/etc/apache2/conf-enabled/apache-env-vars.conf \ -p 8080:80 \ php:7.4-apache

Apache will then automatically read the conf when starting up.

If you prefer using .env files, the command to download the secrets is slightly different:


Then you can start your container like this:

```docker run --rm -it --init \
    --name php \
    -p 8080:80 \
    -v "$(pwd)/app":/var/www/ \
    -v "$(pwd)/.env":/var/www/.env \
    php:7.4-apache

These examples allow you to use Doppler without modifying an existing image such as php:7.4-apache. Now let’s take a look at the recommended approach which uses a custom Docker image with the Doppler CLI embedded. Apache Conf File Generated Inside Container

Another way to go about it is to have the Doppler CLI inside the container and have it generate the Apache conf file at container run time. The benefit of this approach is that all you have to do in order to get the latest version of your secrets is to simply restart the container. No redeploy necessary and no files to mount.

Using Apache as an example, we’ll need to extend the php:7-apache Docker image to embed the Doppler CLI and change the entrypoint script so it generates the Apache conf file before starting Apache.

Here is a complete Dockerfile example:

FROM php:7-apache

# Install Doppler CLI
RUN (curl -Ls --tlsv1.2 --proto "=https" --retry 3 https://cli.doppler.com/install.sh || wget -t 3 -qO- https://cli.doppler.com/install.sh) | sh && \
    apt-get update && apt-get install jq -y && \
    apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/*

COPY app /var/www/

# Override existing entrypoint to generate Apache conf file
COPY docker-php-entrypoint /usr/local/bin/

The code for the custom docker-php-entrypoint would be something like the following:

#!/bin/sh
set -e

echo '\n[info]: Generating env vars using Doppler CLI\n'
    doppler secrets download --no-file | jq -r '. | to_entries[] | "SetEnv \(.key) \"\(.value)\""' > $APACHE_CONFDIR/conf-available/doppler-env-vars.conf
    a2enconf doppler-env-vars > /dev/null

# Copied over from original entrypoint script
if [ "${1#-}" != "$1" ]; then
    set -- apache2-foreground "$@"
fi

exec "$@"

Be sure to give the entrypoint executable permissions:

chmod +x docker-php-entrypoint

Now let’s build the Docker image:

docker build -t doppler-php-apache .

Go to the Doppler dashboard for your project, generate a Service Token, then export that in your terminal:

export DOPPLER_TOKEN=’dp.st.xxxx;

And finally, run the container:

docker run --rm -it --init \
    --name doppler-php \
    -p 8080:80 \
    -e DOPPLER_TOKEN="$DOPPLER_TOKEN" \
    doppler-php-apache

Secrets Management for PHP Applications Sample Code

This article is already really long but I’d encourage you to check out this sample repository from Doppler that has additional working examples of concepts what I’ve demonstrated in this article.

Secrets management for PHP and Apache Secrets management for PHP, NGINX and PHP-FPM Secrets management for PHP using .env files Secrets management for Laravel applications Running on Laravel Forge? Use Doppler's Laravel Forge Integration

If you manage your deployments with Laravel Forge, integrating Doppler is as easy as a few clicks to have your secrets automatically available to your applications and updated all the time.

Just follow the instructions here. Using Doppler’s API

Another nice feature of Doppler is it’s API which allows you to create custom clients, perhaps interact with it from within your application.

All you have to do is issue a REST request to Doppler’s server, provide your access token and you’re good to go.

Here’s an example using cURL:


$token = getenv('DOPPLER_TOKEN');

if (empty($token)) {
        die ('Please provide DOPPLER_TOKEN as environment variable');
}

$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://$token@api.doppler.com/v3/projects",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => [
    "Accept: application/json"
  ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}

Of course, you can use the HTTP client of your choice but bear in mind that hitting the API on every request will be extremely bad for the performance of your applications so you’ll likely want to save the output from Doppler’s API to a local config file so it only needs to be done for the first request after deployment.

If you want to know more about Doppler’s API check out this doc. Summary In this article you’ve learned different ways to provide secrets to your PHP application covering Apache, NGINX with PHP-FPM and .env files.

If you want to give Doppler a try you can sign up for a free community plan.

Proudly part of