Buy

Composer & Cache Permissions

Look back at the Symfony Deployment article: we now have a parameters file! Woo! Next, we need to run composer install - which was the original reason the site didn't work - and then warm up the Symfony cache. We're really close to a functional site. We won't need to dump the Assetic assets - we're not using Assetic. But we will need to do some asset processing later.

Running composer install

Let's add a new task to run composer install. In the hook file, add "Install Composer deps". Use the composer module and tell it to run the install command. We also need to set the working_dir: use {{ ansistrano_release_path.stdout }}:

11 lines ansible/deploy/after-symlink-shared.yml
---
- name: Set up infrastructure-related parameters
... lines 3 - 6
- name: Install Composer dependencies
composer:
command: install
working_dir: '{{ ansistrano_release_path.stdout }}'

Perfect! One gotcha with the composer module is that, by default, it runs composer install --no-dev. That means that your require-dev dependencies in composer.json will not be downloaded:

71 lines composer.json
{
... lines 2 - 30
"require-dev": {
"sensio/generator-bundle": "^3.0",
"symfony/phpunit-bridge": "^3.0",
"doctrine/data-fixtures": "^1.1",
"hautelook/alice-bundle": "^1.3"
},
... lines 38 - 69
}

For production, that's a good thing: it will give you a small performance boost. Just make sure that you're not relying on anything in those packages!

Also, in Symfony 3, if you use --no-dev, then some of the post-install Composer tasks will fail, because they need those dependencies. To fix that, we need to set an environment variable: SYMFONY_ENV=prod.

No problem! In deploy.yml, add a new key called environment. And below, SYMFONY_ENV set to prod:

33 lines ansible/deploy.yml
---
- hosts: aws
... lines 3 - 27
environment:
SYMFONY_ENV: prod
... lines 30 - 33

Thanks to this, the Composer post-install tasks will not explode. And that's good... it's not great when your deployment explodes.

Oh, and important note: for this all to work, Composer must be already installed on your server. We did that in our provision playbook.

Clearing & Warming Cache

Before we try this, let's tackle one last thing: clearing the Symfony cache... which basically means running two console commands.

To make this easier, in deploy.yml, add a new variable: release_console_path. Copy the ansistrano_release_path.stdout variable and paste it: {{ ansistrano_release_path.stdout }}/bin/console:

35 lines ansible/deploy.yml
---
- hosts: aws
... lines 3 - 14
vars:
release_console_path: "{{ ansistrano_release_path.stdout }}/bin/console"
# Ansistrano vars
... lines 19 - 35

Cool! Back in the hook file, add a new task to clear the cache. Use the command module to simply say {{ release_console_path }} cache:clear --no-warmup --env=prod:

17 lines ansible/deploy/after-symlink-shared.yml
... lines 1 - 6
- name: Install Composer dependencies
... lines 8 - 11
- name: Clear the cache
command: '{{ release_console_path }} cache:clear --no-warmup --env=prod'
... lines 14 - 17

That's basically the command that you see in the docs.

If you're not familiar with the --no-warmup flag, it's important. In Symfony 4, instead of running cache:clear and expecting it to clear your cache and warm up your cache, cache:clear will only clear your cache. Then, you should use cache:warmup separately to warm it up. By passing --no-warmup, we're imitating the Symfony 4 behavior so that we're ready.

Add the second task: "Warm up the Cache". Copy the command, but change it to just cache:warmup --env=prod:

17 lines ansible/deploy/after-symlink-shared.yml
... lines 1 - 11
- name: Clear the cache
command: '{{ release_console_path }} cache:clear --no-warmup --env=prod'
- name: Warm up the cache
command: '{{ release_console_path }} cache:warmup --env=prod'

Now, technically, since the cache/ directory is not shared between deploys, we don't really need to run cache:clear: it will always be empty at this point! But, I'll keep it.

Ok! Phew! I think we've done everything. Let's deploy! Find your local terminal and run the playbook:

ansible-playbook -i ansible/hosts.ini ansible/deploy.yml --ask-vault-pass

Use beefpass as the vault password and deploy to master. Then... wait impatiently! Someone fast forward, please!

Yes! No errors! On the server, move out of current/ and then back in. Check it out! Our vendor/ directory is filled with goodies!

Fixing the File Permissions

Moment of truth: try the site again: mootube.example.com. Bah! It still doesn't work. Let's find out why. On the server, tail the log file:

sudo tail /var/log/nginx/mootube.example.com_error.log

Ooooh:

PHP Fatal error: The stream or file "var/logs/prod.log" could not be opened

Of course! We have permissions problems on the var/ directory! Fixing this is actually a very interesting topic. There is an easy way to fix this... and a more complex, but more secure way.

For now, let's use the simple way: I really want our app to work! Add a new task: "Setup directory permissions for var". Use the file module. But, quickly, go back to deploy.yml and make another variable: release_var_path set to the same path {{ ansistrano_release_path.stdout }}/var:

36 lines ansible/deploy.yml
---
- hosts: aws
... lines 3 - 14
vars:
release_console_path: "{{ ansistrano_release_path.stdout }}/bin/console"
release_var_path: "{{ ansistrano_release_path.stdout }}/var"
... lines 18 - 36

Now, back in after-symlink-shared.yml, set the path to {{ release_var_path }}, state to directory, mode to 0777 and recurse: true:

24 lines ansible/deploy/after-symlink-shared.yml
... lines 1 - 17
- name: Setup directory permissions for var/
file:
path: "{{ release_var_path }}"
state: directory
mode: 0777
recurse: true

On deploy, this will make sure that the directory exists and is set to 777. That's not the best option for security... but it should get things working!

Deploy one more time:

ansible-playbook -i ansible/hosts.ini ansible/deploy.yml --ask-vault-pass

Type beefpass, deploy to master... and watch the magic. I can see the new directory permissions task... and it finishes.

Refresh the site! Eureka! Yea, it's still a 500 error, but this comes from Symfony! Symfony is running! Change the URL to http://mootube.example.com/about. It works! Yea, it's super ugly - we need to do some work with our assets - but it does work. The homepage is broken because our database isn't setup. But this static page proves our deploy is functional! Victory!

Now, let's smooth out the missing details... like the insecure permissions, the database and our assets... because this site is horrible to look at!

Leave a comment!

  • 2018-01-27 Chris

    It's weird and to be honest I actually don't know what the problem was. It's pretty inconclusive for me how this problem could happen to me.

    For the second problem it's nice to see that you are fixing this issue. Thanks for the fast reply. With it it's assured that this approach is valid and the error is not caused through a mistake of my own.

    Many greetings and keep up the good work!
    Chris

  • 2018-01-25 weaverryan

    Hey Chris!

    I'm glad you figured out the first issue :). What was the fix? Your original code looked good to me, so I was also surprised that the bundle wasn't originally being installed on your server.

    About your second issue, this is fascinating! I'm afraid that I caused this bug with the new DoctrineFixturesBundle system :). I've already added a comment on the issue you linked and created an issue on Symfony's main repository. Basically, I think we have a "bug", or a situation we hadn't thought of. But the solution is 100% fine. In fact, it's basically what the "fix" would do internally - so there's no downside to that fix, other than it's annoying that you need to write all that code :).

    Cheers!

  • 2018-01-24 Chris

    I manged it to install liip/imagine-bundle onto my ec2 server,

    Installing jms/serializer-bundle (2.3.1): Loading from cache - Installing imagine/imagine (v0.7.1): Loading from cache - Installing liip/imagine-bundle (1.9.1): Loading from cache -

    but now I get

    [Symfony\\Component\\Config\\Exception\\FileLoaderLoadException] \n Class Doctrine\\Bundle\\FixturesBundle\\Fixture not found in /var/www/project/ \n releases/20180124103155Z/app/config/services.yml (which is being imported f \n rom \"/var/www/project/releases/20180124103155Z/app/config/config.yml\").

    After digging into it, I used this approach: https://github.com/doctrine/DoctrineFixturesBundle/issues/225#issuecomment-352538507

    If there is a better solution, please let me know.

    Have a nice day,
    Chris

  • 2018-01-23 Chris

    In another project I currently use the ansistrano workflow (and I have tested this additionally with your finished downloaded version).

    When I start to deploy I get this message:

    An error occurred when executing the \"'cache:clear --no-warmup'\" command: \n PHP Fatal error: Uncaught Error: Class 'Liip\\ImagineBundle\\LiipImagineBundle' not found in /var/www/project/releases/20180123144814Z/app/AppKernel.php:23 Stack trace: #0 /var/www/project/releases/20180123144814Z/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php(448): AppKernel->registerBundles()

    In deploy.yml I set


    environment:
    SYMFONY_ENV: prod

    In after-symlink-shared.yml:


    - name: Clear the cache
    command: "{{ release_console_path }} cache:clear --no-warmup --env=prod"

    - name: Warm up the cache
    command: "{{ release_console_path }} cache:warmup --env=prod"

    In composer.json I add:


    "require": {
    "liip/imagine-bundle": "^1.9"
    }

    Can you please help me to get my deployment going? At this point after researching for hours I'm clueless what to do next.

    Thank you very much :)