If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeNo matter how you deploy, eventually, you hit the same problem: handling sensitive configuration, like your production database password or Loggly token. Depending on your app, this info will need to be stored in different places, like parameters.yml
for a Symfony 3 app or as environment variables for Symfony 4.
But no matter where the config needs to ultimately live, the problem is more or less the same: how can we put secret things onto the server in an automated way?
Like with everything, there are a few good answers. And this is where things can get complicated. For parameters.yml
, one option is to store the production parameters.yml
in a private S3 bucket. Then, during deployment, use the s3
Ansible module to download that into your project.
Another option - the way that we will do it - is to store a parameters.yml.dist
file in our project, and make it dynamic by printing Ansible variables inside it. To keep things secure, those variables will be stored in the Ansible vault.
Let's create a new vault to store the secret values:
ansible-vault create ansible/vars/deploy_vault.yml
Choose a safe password... something safer than what I'll choose: beefpass
. Here's the plan: we will define some new variables here, then use them inside parameters.yml.dist
. So, what needs to be dynamic? For now the secret
, loggly_token
, database_host
, database_user
and database_pass
.
Back in the vault, create some variables: vault_symfony_secret
set to udderly secret $tring
and vault_loggly_token
set to our production loggly token... this long string:
# ansible/vars/deploy_vault.yml
---
vault_symfony_secret: 'udderly secret $string'
vault_loggly_token: 'fb4aa5b2-30a3-4bd8-8902-1aba5a683d62'
Oh, and, get your own token... because this is fake.
Then, vault_database_host: 127.0.0.1
, vault_database_user: root
, and vault_database_password
set to null
:
# ansible/vars/deploy_vault.yml
---
vault_symfony_secret: 'udderly secret $string'
vault_loggly_token: 'fb4aa5b2-30a3-4bd8-8902-1aba5a683d62'
vault_database_host: 127.0.0.1
vault_database_user: root
vault_database_password: null
In the provision playbook, we actually install a MySQL server locally. That's why I'm using the local database server... and no, I haven't bothered to create a proper user with a decent password. But you should.
But also, if I were using AWS for a real application, I would use Amazon's RDS - basically, a hosted MySQL or PostgreSQL database - so that I don't need to manage it on my own. In that case, the database host would be something specific to my RDS instance. But, it's the same idea.
Tip
If you decide to use Amazon's RDS - basically, you need to perform the next steps:
You can automate the creation of the RDS instance, though it’s not as important because these instances are not destroyed and recreated in the same way as EC2 instances.
Save this file and quit. We now have a new, but encrypted, file with those variables:
$ANSIBLE_VAULT;1.1;AES256 | |
37633866356562303665643432386636313937613063373666613739313732313130633364333138 | |
3635376437663463326538633961663931393136643962610a636635363630656264343630613731 | |
30323432303666373366366237393637333932373962373730626465663330326462396532363330 | |
3561346639616234610a353530366337616164633864363038336437623165623534663166633966 | |
31656536326665613063343537333564656334616638663866653934373566303232323036653566 | |
63366436346139333966316364656265633462303233623162353630626661353930343939396661 | |
36326338383538363138633836636235663734303464396236396132306430636362393232366264 | |
34653762303761356262316234333631386262396666313037643661653336613133396439313362 | |
61326464323131646132393538333236643664356539636464373937666530366334313335663637 | |
62383230653762396662653265613133343137336264353233303932313330663831316565636134 | |
35646135303966333639616361306236393134656263386666643532353534313662376232653566 | |
64393665343230353362643363663031636334626466656130616562363038396335323530613135 | |
36313232323766663564666638646562366437616233376632323164303635356631346361376633 | |
6231386263646661383539313439316663333231643261653834 |
Inside deploy.yml
, under vars_files
, add ./vars/deploy_vault.yml
:
- hosts: aws | |
vars_files: | |
- ./vars/deploy_vault.yml | |
- ./vars/vars.yml | |
... lines 7 - 30 |
At this point, we could go directly into parameters.yml.dist
and start using those vault_
variables. But, as a best practice, I like to create a separate vars file - deploy_vars.yml
- where I assign each of those vault_
variables to a normal variable. Just, stay with me.
Re-open the vault file - type beefpass
:
ansible-vault edit ansible/vars/deploy_vault.yml
And copy everything. Then, in deploy_vars.yml
, paste that. Now, for each variable, create a new variable that's set to it, but without the vault_
prefix:
symfony_secret: "{{ vault_symfony_secret }}" | |
loggly_token: "{{ vault_loggly_token }}" | |
database_host: "{{ vault_database_host }}" | |
database_user: "{{ vault_database_user }}" | |
database_password: "{{ vault_database_password }}" |
This is totally optional. The advantage is that you can quickly see a list of all available variables, without needing to open the vault. I can just look in here and say:
Oh! Apparently there is a variable called
symfony_secret
!
Back in deploy.yml
, import this new file: ./vars/deploy_vars.yml
:
- hosts: aws | |
vars_files: | |
- ./vars/deploy_vault.yml | |
- ./vars/vars.yml | |
- ./vars/deploy_vars.yml | |
... lines 8 - 30 |
Finally, in parameters.yml.dist
, let's print some variables! For database_host
, print {{ database_host }}
. Repeat that for database_user
, database_password
, and then down below for symfony_secret
, and loggly_token
:
# This file is a "template" of what your parameters.yml file should look like | |
# Set parameters here that may be different on each deployment target of the app, e.g. development, staging, production. | |
# http://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration | |
parameters: | |
database_host: "{{ database_host }}" | |
database_port: ~ | |
database_name: mootube | |
database_user: "{{ database_user }}" | |
database_password: "{{ database_password }}" | |
# You should uncomment this if you want use pdo_sqlite | |
#database_path: '%kernel.project_dir%/var/data/data.sqlite' | |
mailer_transport: smtp | |
mailer_host: 127.0.0.1 | |
mailer_user: ~ | |
mailer_password: ~ | |
# A secret key that's used to generate certain security-related tokens | |
secret: "{{ symfony_secret }}" | |
redis_host: localhost | |
loggly_token: "{{ loggly_token }}" |
That's it! We put secret things in the vault and then print them inside the parameters file.
Let's try it. Run the playbook with the same command:
ansible-playbook -i ansible/hosts.ini ansible/deploy.yml
Yep! This fails because it can't decrypt the vault file. From now on, we need to add a --ask-vault-pass
flag. And then type, beefpass
:
ansible-playbook -i ansible/hosts.ini ansible/deploy.yml --ask-vault-pass
If this gets really annoying, you can store the password in a file and use --vault-password-file
to point to it. Just don't commit that file to your repository!
And... done! Let's go check it out! Move out of the current/
directory and then back in:
cd ..
cd current
Deep breath: open parameters.yml
. Yes! Everything has its dynamic vault value!
Ok, we're getting really close! Next, let's run Composer and fix some permissions!
"Houston: no signs of life"
Start the conversation!
// composer.json
{
"require": {
"php": ">=5.5.9",
"doctrine/doctrine-bundle": "^1.6", // 1.6.8
"doctrine/orm": "^2.5", // v2.7.2
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"sensio/distribution-bundle": "^5.0.19", // v5.0.20
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
"symfony/monolog-bundle": "^3.1.0", // v3.1.0
"symfony/polyfill-apcu": "^1.0", // v1.4.0
"symfony/swiftmailer-bundle": "^2.3.10", // v2.6.3
"symfony/symfony": "3.3.*", // v3.3.5
"twig/twig": "^1.0||^2.0", // v1.34.4
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
"predis/predis": "^1.1", // v1.1.1
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.6
"symfony/phpunit-bridge": "^3.0", // v3.3.5
"doctrine/data-fixtures": "^1.1", // 1.3.3
"hautelook/alice-bundle": "^1.3" // v1.4.1
}
}
# ansible/requirements.yml
-
src: DavidWittman.redis
version: 1.2.4
-
src: ansistrano.deploy
version: 2.7.0
-
src: ansistrano.rollback
version: 2.0.1