Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Production Secrets

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Whenever you add a new secret, you need to make sure that you add it to the dev environment and the prod environment. That's because each set of secrets, or "vault" as I've been calling it, is specific to the environment. This vault of secrets, for example, will only be loaded in the dev environment. So, unless we also add MAILER_DSN to the prod vault, the prod environment will be... yep! Totally broken. And a busted production environment is... a bummer.

Creating the Production (prod) Vault

So, how do we add MAILER_DSN to the prod vault? With the same command as before: secrets:set, but this time with --env=prod:

php bin/console secrets:set --env=prod MAILER_DSN

I'll paste in my production SendGrid value... which you can't see because the command hides the input to be safe.

Cool! Just like last time, because this is the first time we've added a key to the prod vault, it automatically created the vault for us... which means that it created the decrypt and encrypt keys.

Production Encrypt & Decrypt Keys

And just like with the dev environment, the encrypt key file is safe to commit to your repository. Heck, you could post it onto the Internet! It only gives people the power to add things to your vault, which is probably something that you do want any developer to be able to do.

But the decrypt key file should not be committed to the repository. It is incredibly sensitive: it has the power to decrypt all of your production secrets! We decided that it was probably ok to commit the dev decrypt key... because the dev keys are probably not very sensitive. But you should not commit this one. Or, if you do - just realize that everyone who has access to view files in your repository will have access to all your secrets... and you might as well just commit them as plain-text values.

We'll talk more about the decrypt key in a few minutes.

Add the new vault files to git:

git add config/


git status

Oh! This did not add the private decrypt key. That's no accident: our .gitignore file is specifically ignoring this:

27 lines .gitignore
###> symfony/framework-bundle ###
... lines 2 - 4
... lines 6 - 8
... lines 10 - 27

This line was added when we updated the symfony/framework-bundle recipe.

Listing & Revealing prod Secrets

Anyways, just like with the dev vault, we can list the secrets:

php bin/console secrets:list --env=prod

And because my app does have the decrypt key, we can add --reveal to see their values:

php bin/console secrets:list --env=prod --reveal

Secrets are Committed

Ok, let's commit!

git commit -m "Adding MAILER_DSN to prod vault"

Do you realize how awesome that was? We just safely committed a secret value to the repository! Secrets are version controlled, which means that we can see when a secret is added on a pull request and can even check later to see why and when a secret was added. That's a huge step!

Deploying with the Decrypt Key

Now, instead of needing to figure out how and where to securely store all our sensitive values so that we can add them to our app when we deploy, there is now just one sensitive value: the decrypt key file.

When we deploy to production, the only thing we need to worry about now is creating that decrypt file with this long value inside. Or, you can base64_encode the key's value and set it on a special environment variable called SYMFONY_DECRYPTION_SECRET. You can use a PHP trick to get the exact value to set on that env var:

php -r 'echo base64_encode(require "config/secrets/prod/prod.decrypt.private.php");'

The point is, on production you either need to re-create the prod.decrypt.private.php file or set the SYMFONY_DECRYPTION_SECRET environment variable. How? That depends completely on your deploy. For example, with SymfonyCloud - which is what we use - we set the decrypt key as a SymfonyCloud "variable".

However you deploy, whatever is responsible for deploying your app should be the one, um, "thing" that has access to the private key.

Seeing the prod Secret Value

Let's go make sure this whole prod vault idea works. Right now, if we refresh the page, it still shows us the null value because we are still in the dev environment.

Open up your .env file and, temporarily, change APP_ENV to prod:

62 lines .env
... lines 1 - 15
###> symfony/framework-bundle ###
... lines 18 - 20
... lines 22 - 62

Then, find your console and clear the cache:

php bin/console cache:clear

I don't need to add --env=prod now because we are already in the prod environment thanks to the APP_ENV change.

Ok, go try it! Refresh and... yes! That's the value from the prod vault! Symfony automatically used the private key to decrypt it.

And if the Decrypt Key is Missing?

What would happen if the decrypt key wasn't there? Let's find out! Temporarily delete the decrypt key - but make sure you can get it back: if you lose this key, you won't ever be able to decrypt your secrets and you'll need to create a new private key and re-add them all again. That would be... a bummer.

Refresh now to see... oh! Giant 500 page... but we can't see the error. Check out the logs:

tail var/log/prod.log

And... there it is:

Environment variable not found: "MAILER_DSN".

If you don't have the private key... bad things will happen. Let's go undelete that private key file. Refresh: all better. Let's also change back to the dev environment to make life nicer:

62 lines .env
... lines 1 - 15
###> symfony/framework-bundle ###
... lines 18 - 20
... lines 22 - 62

So... that's it! You have a dev vault and a prod vault, you can commit your encrypted secrets via Git and you only need to handle one sensitive value at deploy: the private decrypt key.

But... what if a developer needs to locally override a value in the dev environment? For example, in our dev vault, MAILER_DSN uses the null transport so that emails are not sent. What if I need to temporarily change Mailtrap so that I can test the emails?

The answer: the "local" vault... a little bit of coolness that will open up a couple of neat possibilities. That's next.

Leave a comment!

Login or Register to join the conversation
Luc H. Avatar

Hi, it is not clear to me how these production secrets (and committing this to git) helps when I have several production servers running my software, when I commit the production vault to the git in which I develop my software.

I also see it as quite complicated when I have - lets say - 10 variables in my dev vault. Then I would need to write to the users of my software to manually create the production vault by calling php bin/console secrets:set --env=prod <one-of-my-10-variables> 10 times. This will probably overstrain my users. Wouldn't it be great to be able to ask for all defined variables with a command like php bin/console secrets:set --env=prod --read-from-dev?


Hey Luc,

It depends on how "secret" you want to make your dev credentials. Most devs probably not worry too much about it and you can completely set them in your .env file unsecured because well, it's not prod! You may not care too much about dev. Just ask yourself, what if your dev credentials will be compromised? Any serious happened? Probably the only thing you have to do is to regenerate them and that's it.

But about prod - we definitely want to do care of them properly. So you may think of securing on prod secrets. And actually that's where Symfony secrets feature help, and it's really powerful! You may set all the secrets locally and then deploy the project to the production server where you just will need to run one command to decrypt your secrets, you won't need to set all those secrets as env vars, etc. So, it should make easier (speed up) your deployment process. The fact that your secrets are version-controlled is awesome, as we can (anybody on your team can) add new secrets to the vault even doesn't know values of the existent ones. And unlike the real env vars or .env files - secrets are encoded, so nobody can see real values of them by accident.

I don't know about your software of course, it may depends, but as I see they will have to set prod credentials anyway, whether by calling "bin/console secrets:set" or setting it via real env vars on production, or setting them in .env.prod. So, not sure if setting via the command will be more difficult.

> Wouldn't it be great to be able to ask for all defined variables with a command like php bin/console secrets:set --env=prod --read-from-dev?

Probably your secrets not so secure then? Probably easier would be to just set them in ".env" file instead and it will be read in both dev/prod envs?

Anyway, it depends on your project, and you can still continue using .env files and real env vars for this, nothing is wrong with them. So, as I said, it depends on your project. Just give it a try and see if it's something you need or no.

I hope this helps!


Luc H. Avatar
Luc H. Avatar Luc H. | Victor | posted 2 years ago | edited

Hi Victor,

Sorry, I think I didn't write my problem clear enough, since you missed my point. I understand how the encrypted vault helps me to guard my prod secrets. My problem is, that encrypting the credentials for MY prod does not help others using my software, since they will use a DIFFERENT prod. Let my give you an example. Let us assume that my software needs the following values: DATABASE_DSN and MAILER_DSN.

Now I add the secret content for MY prod:

php bin/console secrets:set --env=prod DATABASE_DSN<br />php bin/console secrets:set --env=prod MAILER_DSN

Then I commit this to git.

Now when a different user gets my software from my gitlab repo, he cannot decrypt these values (obviously) and also doesn't need them. He has to set HIS secret values, by calling the commands from above. The problem is: how does he know that he has to do that, i.e. define exactly these two values? It would be great to have a way to read the KEYS he has to set, that what I meant by the --read-from-dev parameter. That would then work like how the parameters file was created in symfony 2: it started an interactive mode to ask the user for all needed values.

The other problem I have is that I have TWO prod environments, with different credentials. I can create only ONE prod vault and commit to git.

I think the secrets system is a great idea, but it can only be used in exactly ONE use case: I have a team of developers working on a software, which is not used by others, for which there is only ONE prod environment.


Hey Luc,

Ah, I see now... yeah, you probably don't want to share *your* production credentials with your users. :)

> he know that he has to do that, i.e. define exactly these two values?

Well, that's what your README should say I suppose. You should write a little docs about how to run your software, at least how to start it. Let your users know how in the README for example. But yeah, I agree, it would be good to have it working as parameters. Though, I think the idea behind of this is a bit different. You think about it as set all the necessary parameters at once, but good practice is to use Symfony secrets on the go, like you need a new parameter - create it and set to the vault, something like that. I mean, your job is to add new parameters to it instead of create them all at once.

> The other problem I have is that I have TWO prod environments, with different credentials. I can create only ONE prod vault and commit to git.

Hm, that's an edge case I think, but if you have 2 prod envs - you probably have 2 different symfony env? Like "prod" and "prod2"? I haven't tried this, but I'm not sure that "prod" is hardcoded somewhere in Symfony secrets mechanism that means you probably may create as many vaults as you need, just match Symfony env name, and the system will put it into different folders like "config/secrets/prod/" and "config/secrets/prod2/". I probably don't see the problem here, but I may not understand your problem completely or I just didn't have such use case so didn't see the problem personally.

> I think the secrets system is a great idea, but it can only be used in exactly ONE use case: I have a team of developers working on a software, which is not used by others

I don't know 100% the exact reasons behind this Symfony secrets feature, though I think that's the main idea behind of it. It helps with private projects where you have a team of developers working on it. Anyway, if you have your own use cases, and especially if they are not single and some users also have similar problems - please, feel free to open an issue on symfony/symfony repo describing your use cases and discuss if we can expand implementation to cover your use cases. If they are valid use cases - I think people will support this idea. Or at least you will get the exact reasons why it won't be implemented or wasn't be implemented.


Kiuega Avatar
Kiuega Avatar Kiuega | posted 2 years ago | edited

Hello, does that mean that we still have to generate an environment variable SYMFONY_DECRYPTION_SECRET containing the decryption key for the secret variables? And will this environment variable be visible in the repository?
So someone with access to the repository could do bad things if they use the environment variable to decrypt our secret keys?


If it helps others, we're chatting about this on a different thread :) https://symfonycasts.com/sc...

php-programmist Avatar
php-programmist Avatar php-programmist | posted 3 years ago

Hi, there is a little typo in this text:

Now, instead of needing to figure our how

instead of

Now, instead of needing to figure out how

Hey Alexey!

Totally agree! :) I fixed it in https://github.com/knpunive... . Thanks for looking over the script in advance, I see the video is not ready for this chapter yet ;)


Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.3.0",
        "ext-iconv": "*",
        "antishov/doctrine-extensions-bundle": "^1.4", // v1.4.2
        "aws/aws-sdk-php": "^3.87", // 3.110.11
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^2.0", // 2.0.6
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // 2.1.2
        "doctrine/orm": "^2.5.11", // v2.7.2
        "doctrine/persistence": "^1.3.7", // 1.3.8
        "easycorp/easy-log-handler": "^1.0", // v1.0.9
        "http-interop/http-factory-guzzle": "^1.0", // 1.0.0
        "knplabs/knp-markdown-bundle": "^1.7", // 1.8.1
        "knplabs/knp-paginator-bundle": "^5.0", // v5.0.0
        "knplabs/knp-snappy-bundle": "^1.6", // v1.7.0
        "knplabs/knp-time-bundle": "^1.8", // v1.11.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.23
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.8.2
        "liip/imagine-bundle": "^2.1", // 2.3.0
        "nexylan/slack-bundle": "^2.1", // v2.2.1
        "oneup/flysystem-bundle": "^3.0", // 3.3.0
        "php-http/guzzle6-adapter": "^2.0", // v2.0.1
        "sensio/framework-extra-bundle": "^5.1", // v5.5.3
        "symfony/asset": "5.0.*", // v5.0.2
        "symfony/console": "5.0.*", // v5.0.2
        "symfony/dotenv": "5.0.*", // v5.0.2
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "5.0.*", // v5.0.2
        "symfony/framework-bundle": "5.0.*", // v5.0.2
        "symfony/mailer": "5.0.*", // v5.0.2
        "symfony/messenger": "5.0.*", // v5.0.2
        "symfony/monolog-bundle": "^3.5", // v3.5.0
        "symfony/security-bundle": "5.0.*", // v5.0.2
        "symfony/sendgrid-mailer": "5.0.*", // v5.0.2
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "5.0.*", // v5.0.2
        "symfony/twig-pack": "^1.0", // v1.0.0
        "symfony/validator": "5.0.*", // v5.0.2
        "symfony/webpack-encore-bundle": "^1.4", // v1.7.2
        "symfony/yaml": "5.0.*", // v5.0.2
        "twig/cssinliner-extra": "^2.12", // v2.12.0
        "twig/extensions": "^1.5", // v1.5.4
        "twig/inky-extra": "^2.12" // v2.12.0
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.3.0
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/browser-kit": "5.0.*", // v5.0.2
        "symfony/debug-bundle": "5.0.*", // v5.0.2
        "symfony/maker-bundle": "^1.0", // v1.14.3
        "symfony/phpunit-bridge": "5.0.*", // v5.0.2
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "5.0.*" // v5.0.2