Part 2 / 3 - Deploying Symfony 4 with git - Sharing Folders


In the previous video we covered how to upload / deploy our code using Git. We went down the route of simplicity, whereby our local git push command would copy all our local data that is tracked by git up to our remote server, right into the live /var/www/crvfakeexample.com directory.

There are some drawbacks to doing this.

As we're serving data from this directory to live site visitors, we might break our site during a deploy if changing a file the visitor is trying to access, whilst they are trying to access it. The bigger your site in terms of active visitors, the more likely this problem is to occur.

Also, rolling back to a previous version is difficult, as it means local and remote changes are required.

A better strategy is to use the concept of builds.

In order to do this, however, we need to re-do our directory structure, update our post-receive hook file, and share our var directory.

This is a little easier to achieve with Symfony 4 than it was during the Symfony 2 / Symfony 3 equivalent.

Revised Directory Structure

Rather than have all our code stored under /var/www/crvfakeexample.com, we are going to move the code into a subdirectory called code. Not the most imaginative name, but it works.

We're also going to add in a shared directory, into which we will move the var directory.

Our new directory structure will be:

  • /var/www/crvfakeexample.com/shared/var
  • /var/www/crvfakeexample.com/code

We'll take care of setting up the appropriate symlinks in our post-receive hook file.

I'm working on the assumption you're following along in a controlled demo environment. If in live, don't be so hasty, and adapt accordingly:

# server side

rm -rf /var/www/crvfakeexample.com/*
rm -rf /var/www/crvfakeexample.com/.*

These two commands remove any files and folders, including hidden files / folders.

Next, create the directory structure:

# server side

cd /var/www/crvfakeexample.com
mkdir code
mkdir -p shared/var

mkdir -p creates folders and subfolders in one go, creating any -parent directories as needed.

At this point we also need to re-do the required Symfony permissions for the var directory:

cd shared

HTTPDUSER=$(ps axo user,comm | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\  -f1)
sudo setfacl -dR -m u:"$HTTPDUSER":rwX -m u:$(whoami):rwX var
sudo setfacl -R -m u:"$HTTPDUSER":rwX -m u:$(whoami):rwX var

Lastly, as far as permissions are concerned, I'm going to set the shared directory and any subdirectories to have the owning user and owning group of www-data:

# server side

sudo chown -R www-data:www-data /var/www/crvfakeexample.com/shared

This should hopefully have preempted any permissions problems that we might have otherwise faced.

Updating post-receive

Next we need to update our post-receive hook file.

Again from the server:

vim /var/www/crvfakeexample.git/hooks/post-receive

Here are the changes in full:

#!/bin/bash

WEB_ROOT=/var/www/crvfakeexample.com

export GIT_WORK_TREE=$WEB_ROOT/code
export GIT_DIR=/var/www/crvfakeexample.git

git --work-tree=$GIT_WORK_TREE --git-dir=$GIT_DIR checkout -f master

rm -rf $GIT_WORK_TREE/var
ln -sfn $WEB_ROOT/shared/var $GIT_WORK_TREE

cd $GIT_WORK_TREE

composer install --no-dev --optimize-autoloader

php bin/console cache:clear --no-warmup
php bin/console cache:warmup

This is roughly what we had before, with a couple of changes.

Firstly, because we need to use the path /var/www/crvfakeexample.com in two places, we have extracted this out to a separate variable: WEB_ROOT.

This variable is not exported, as it's only required in this file, not in the wider system environment.

Any references to this path have been replaced with the variable.

Secondly, we rm -rf var to remove the unwanted uploaded var contents from our local. We don't want our local contents, only the existing contents from the server. This keeps around any sessions, logs, and so on from previous builds.

Doing a git push prod master now should see us right. But as we've changed the directory structure we do need to tell Apache / nginx.

Revised Apache Vhost Configuration

<VirtualHost *:80>
    ServerName crvfakeexample.com
    ServerAlias www.crvfakeexample.com

    DocumentRoot /var/www/crvfakeexample.com/code/public
    <Directory /var/www/crvfakeexample.com/code/public>
        AllowOverride None
        Require all granted
        Allow from All

        <IfModule mod_rewrite.c>
            Options -MultiViews
            RewriteEngine On
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteRule ^(.*)$ index.php [QSA,L]
        </IfModule>
    </Directory>

    # uncomment the following lines if you install assets as symlinks
    # or run into problems when compiling LESS/Sass/CoffeeScript assets
    # <Directory /var/www/crvfakeexample.com/code>
    #     Options FollowSymlinks
    # </Directory>

    # optionally disable the RewriteEngine for the asset directories
    # which will allow apache to simply reply with a 404 when files are
    # not found instead of passing the request into the full symfony stack
    <Directory /var/www/crvfakeexample.com/code/public/bundles>
        <IfModule mod_rewrite.c>
            RewriteEngine Off
        </IfModule>
    </Directory>
    ErrorLog /var/log/apache2/crvfakeexample.com_error.log
    CustomLog /var/log/apache2/crvfakeexample.com_access.log combined

    # optionally set the value of the environment variables used in the application
    SetEnv APP_ENV ${APP_ENV}
    SetEnv APP_DEBUG ${APP_DEBUG}
    SetEnv APP_SECRET ${APP_SECRET}
    SetEnv DATABASE_URL ${DATABASE_URL}
</VirtualHost>

Be sure to run an Apache reload after changing this config:

service apache2 reload

Revised nginx Server Configuration

server {
    listen 80 default;
    server_name crvfakeexample.com www.crvfakeexample.com;
    root /var/www/crvfakeexample.com/code/public;

    location / {
        # try to serve file directly, fallback to index.php
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/run/php/php7.2-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;

        # optionally set the value of the environment variables used in the application
        fastcgi_param APP_ENV prod;
        fastcgi_param APP_SECRET some_new_secret_123;
        fastcgi_param APP_DEBUG 0;
        fastcgi_param DATABASE_URL "mysql://not_root:different_pass@127.0.0.1:3306/symfony_demo";
        fastcgi_param MAILER_URL null://localhost;

        # When you are using symlinks to link the document root to the
        # current version of your application, you should pass the real
        # application path instead of the path to the symlink to PHP
        # FPM.
        # Otherwise, PHP's OPcache may not properly detect changes to
        # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
        # for more information).
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        # Prevents URIs that include the front controller. This will 404:
        # http://domain.tld/index.php/some-path
        # Remove the internal directive to allow URIs like this
        internal;
    }

    # return 404 for all other php files not matching the front controller
    # this prevents access to other php files you don't want to be accessible.
    location ~ \.php$ {
        return 404;
    }

    error_log /var/log/nginx/crvfakeexample.com_error.log;
    access_log /var/log/nginx/crvfakeexample.com_access.log;
}

Be sure to run an nginx reload after changing this config:

service nginx configtest
service nginx reload

The only change in both files is to add /code onto the existing file paths.

Testing The Revised Git Deployment

If you have pushed up code already, such as from the previous video, you will need to make a local change to your project before you can git push successfully.

# from your local

cd {project dir}
touch fake-change
git add fake-change
git commit -m "a fake change"

git push prod master

# output here

It is strange that we have to fake a change to deploy our code. This is a big reason why I don't like using Git like this.

This approach is half way to where I'd like to be. We've got a shared var directory, but we're still overwriting our live code when doing a deploy.

In the next video we will add in the concept of a builds directory.

Episodes