Part 1 / 3 - Deploying Symfony 4 with git


We're going to cover deploying Symfony 4 using Git. Using Git for deployment is not something I can strongly recommend, but it does work. I would suggest you look for a tool specifically made for deployment, such as Deployer instead.

Even so, I get asked how to deploy Symfony with git frequently enough to warrant a video or two. In this video we will cover a basic deploy of Symfony 4 using git.

Deploy Symfony 4 with Git

To begin with you should have a pre-configured LAMP or LEMP stack. If you have been following along with previous videos, please ensure you are working from a fresh build as changes to the initial Apache or nginx configurations may have occurred, which may lead you to incorrect configurations.

The high level overview of proceedings is as follows:

  • We work with Git on our local machine, committing code as normal
  • We setup a remote repository on our Production server
  • We add the remote repository to our local Git setup
  • We git push to the remote whenever we want to deploy some code to prod

We will need to setup the remote repository. We will also need to ensure we get our permissions sorted out. And we will need to write at least one Git Hook script to automate part of the deployment. A Hook is simply a point along the typical git work flow into which we can 'hook' into, to make our own customisations to the process.

Specific to the Symfony 4 git deploy, we will need to have setup our environment variables ahead of time.

I'm going to abbreviate the majority of the setup here, as it's entirely the same as in this video.

We will focus on the differences.

Local Setup

If you're deploying with Git, it's a safe bet to assume you use Git in your project.

Upon cloning the demo project repo you will already have Git configured and running for your project.

If you're following along with a different repo, and don't yet have Git configured:

cd {your project}
git init
git add .
git commit "initial commit"

That's all we need for our local setup.

Remote / Production Server Setup

Start by connecting to your remote server:

ssh you@your-server

# e.g

ssh root@104.236.215.199

We're connecting as root, but this is not recommended in the real world.

Our production server will need two extra pieces of software that you should already be familiar with:

  • Git
  • Composer

Make sure to globally install composer on your server, if not done so already.

Also, install Git:

sudo apt-get install git

Directory Permissions

If you're anything like me, you dislike permissions problems. Getting your permissions right is essential, and a bit of prep upfront can save us a bunch of headaches down the line.

As you've followed along with either the LAMP or LEMP tutorials, at this point you should have a directory structure like this:

cd /var/www

mkdir crvfakeexample.com

ls -la

total 16
drwxr-xr-x  4 root     root     4096 Jan 16 10:59 .
drwxr-xr-x 14 root     root     4096 Jan 11 09:41 ..
drwxr-xr-x  2 root     root     4096 Jan 16 10:59 crvfakeexample.com
drwxr-xr-x  2 root     root     4096 Jan 11 09:41 html

This is not quite right.

The owner of the crvfakeexample.com directory needs to be the user we will be uploading (git push'ing) as. In our case, we will use root.

The web server also needs to access these files. Let's change the owning group to www-data:

sudo chown root:www-data /var/www/crvfakeexample.com

ls -la

total 16
drwxr-xr-x  4 root     root      4096 Jan 16 10:59 .
drwxr-xr-x 14 root     root      4096 Jan 11 09:41 ..
drwxr-xr-x  2 root     www-data  4096 Jan 16 10:59 crvfakeexample.com
drwxr-xr-x  2 root     root      4096 Jan 11 09:41 html

Any members of the www-data group, which includes the www-data user, can now access our code.

We will still need to address the var subdirectory, which we will do so shortly. The var subdirectory will highly likely need write permissions for the www-data group.

If you find Unix permissions confusing, please watch my [beginner friendly Linux permissions tutorial].

Bare Grills

Next we need our bare git repository. If you want to know more about this, please watch this video where we covered this in more depth.

Start by creating the directory that will hold our bare git repository. This can be anywhere on your file system. For simplicity, I keep this next to the web directory.

sudo mkdir /var/www/crvfakeexample.git
sudo chown -R $(whoami):$(whoami) /var/www/crvfakeexample.git

ls -la

total 16
drwxr-xr-x  4 root     root      4096 Jan 16 10:59 .
drwxr-xr-x 14 root     root      4096 Jan 11 09:41 ..
drwxr-xr-x  2 root     www-data  4096 Jan 16 10:59 crvfakeexample.com
drwxr-xr-x  2 root     root      4096 Jan 16 12:51 crvfakeexample.git
drwxr-xr-x  2 root     root      4096 Jan 11 09:41 html

Next initialise the directory as our bare git repository:

cd crvfakeexample.git

git init --bare
Initialised empty Git repository in /var/www/crvfakeexample.git/

ls -la

total 40
drwxr-xr-x 7 root root 4096 Jan 16 12:52 .
drwxr-xr-x 5 root root 4096 Jan 16 12:50 ..
drwxrwxr-x 2 root root 4096 Jan 16 12:52 branches
-rw-rw-r-- 1 root root   66 Jan 16 12:52 config
-rw-rw-r-- 1 root root   73 Jan 16 12:52 description
-rw-rw-r-- 1 root root   23 Jan 16 12:52 HEAD
drwxrwxr-x 2 root root 4096 Jan 16 12:52 hooks
drwxrwxr-x 2 root root 4096 Jan 16 12:52 info
drwxrwxr-x 4 root root 4096 Jan 16 12:52 objects
drwxrwxr-x 4 root root 4096 Jan 16 12:52 refs

Adding Automation

Now we need to add in the automation.

When our code is git push'ed from our local PC to this server, we need composer to do its magic.

Why?

Because as part of our .gitignore file we specifically exclude the vendor directory. So our git push won't send up any third party dependencies.

# on your server

touch /var/www/crvfakeexample.git/hooks/post-receive
chmod +x /var/www/crvfakeexample.git/hooks/post-receive
vim /var/www/crvfakeexample.git/hooks/post-receive

Into this file we need the following:

#!/bin/bash

export GIT_WORK_TREE=/var/www/crvfakeexample.com
export GIT_DIR=/var/www/crvfakeexample.git

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

cd $GIT_WORK_TREE

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

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

Again, all of this is covered in greater detail in this video.

Our First Git Deploy With Symfony 4

In order to deploy, we need to run a git push.

In order to run git push, we need to have configured a remote.

From our local PC, in the Symfony project directory, add in:

# from your local

git remote add prod {your_remote_user}@{your_remote_ip}:/var/www/crvfakeexample.git

# e.g.

git remote add prod root@104.236.215.199:/var/www/crvfakeexample.git

Remember whatever user you connect with needs to the owning user of the folders, as per the permissions issues discussed above.

Now to run the git push:

git push prod master

# asked for password here

Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 283 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Already on 'master'
remote: Loading composer repositories with package information
remote: Installing dependencies from lock file
remote: Package operations: 70 installs, 0 updates, 0 removals
remote:   - Installing ocramius/package-versions (1.2.0): Downloading (100%)
remote:   - Installing symfony/flex (v1.0.60): Downloading (100%)
remote: 
remote: Prefetching 68 packages 
remote:   - Connecting (100%)
remote:   - Downloading (100%)
remote: 
remote:   - Installing symfony/polyfill-mbstring (v1.6.0): Loading from cache
remote:   - Installing doctrine/lexer (v1.0.1): Loading from cache
remote:   - Installing doctrine/inflector (v1.3.0): Loading from cache
remote:   - Installing doctrine/collections (v1.5.0): Loading from cache
remote:   - Installing doctrine/cache (v1.7.1): Loading from cache
remote:   - Installing doctrine/annotations (v1.6.0): Loading from cache
remote:   - Installing doctrine/common (v2.8.1): Loading from cache
remote:   - Installing symfony/doctrine-bridge (v4.0.3): Loading from cache
remote:   - Installing doctrine/doctrine-cache-bundle (1.3.2): Loading from cache
remote:   - Installing symfony/routing (v4.0.3): Loading from cache
remote:   - Installing symfony/http-foundation (v4.0.3): Loading from cache
remote:   - Installing symfony/event-dispatcher (v4.0.3): Loading from cache
remote:   - Installing psr/log (1.0.2): Loading from cache
remote:   - Installing symfony/debug (v4.0.3): Loading from cache
remote:   - Installing symfony/http-kernel (v4.0.3): Loading from cache
remote:   - Installing symfony/finder (v4.0.3): Loading from cache
remote:   - Installing symfony/filesystem (v4.0.3): Loading from cache
remote:   - Installing psr/container (1.0.0): Loading from cache
remote:   - Installing symfony/dependency-injection (v4.0.3): Loading from cache
remote:   - Installing symfony/config (v4.0.3): Loading from cache
remote:   - Installing psr/simple-cache (1.0.0): Loading from cache
remote:   - Installing psr/cache (1.0.1): Loading from cache
remote:   - Installing symfony/cache (v4.0.3): Loading from cache
remote:   - Installing symfony/framework-bundle (v4.0.3): Loading from cache
remote:   - Installing symfony/console (v4.0.3): Loading from cache
remote:   - Installing jdorn/sql-formatter (v1.2.17): Loading from cache
remote:   - Installing doctrine/dbal (v2.6.3): Loading from cache
remote:   - Installing doctrine/doctrine-bundle (1.8.1): Loading from cache
remote:   - Installing doctrine/data-fixtures (v1.3.0): Loading from cache
remote:   - Installing doctrine/doctrine-fixtures-bundle (3.0.2): Loading from cache
remote:   - Installing symfony/yaml (v4.0.3): Loading from cache
remote:   - Installing zendframework/zend-eventmanager (3.2.0): Loading from cache
remote:   - Installing zendframework/zend-code (3.3.0): Loading from cache
remote:   - Installing ocramius/proxy-manager (2.1.1): Loading from cache
remote:   - Installing doctrine/migrations (v1.6.2): Loading from cache
remote:   - Installing doctrine/doctrine-migrations-bundle (v1.3.1): Loading from cache
remote:   - Installing doctrine/instantiator (1.1.0): Loading from cache
remote:   - Installing doctrine/orm (v2.6.0): Loading from cache
remote:   - Installing egulias/email-validator (2.1.3): Loading from cache
remote:   - Installing erusev/parsedown (1.6.4): Loading from cache
remote:   - Installing ezyang/htmlpurifier (v4.9.3): Loading from cache
remote:   - Installing sensio/framework-extra-bundle (v5.1.3): Loading from cache
remote:   - Installing composer/ca-bundle (1.1.0): Loading from cache
remote:   - Installing sensiolabs/security-checker (v4.1.7): Loading from cache
remote:   - Installing symfony/asset (v4.0.3): Loading from cache
remote:   - Installing symfony/expression-language (v4.0.3): Loading from cache
remote:   - Installing symfony/inflector (v4.0.3): Loading from cache
remote:   - Installing symfony/property-access (v4.0.3): Loading from cache
remote:   - Installing symfony/options-resolver (v4.0.3): Loading from cache
remote:   - Installing symfony/intl (v4.0.3): Loading from cache
remote:   - Installing symfony/polyfill-intl-icu (v1.6.0): Loading from cache
remote:   - Installing symfony/form (v4.0.3): Loading from cache
remote:   - Installing monolog/monolog (1.23.0): Loading from cache
remote:   - Installing symfony/monolog-bridge (v4.0.3): Loading from cache
remote:   - Installing symfony/monolog-bundle (v3.1.2): Loading from cache
remote:   - Installing symfony/polyfill-apcu (v1.6.0): Loading from cache
remote:   - Installing symfony/security (v4.0.3): Loading from cache
remote:   - Installing symfony/security-bundle (v4.0.3): Loading from cache
remote:   - Installing swiftmailer/swiftmailer (v6.0.2): Loading from cache
remote:   - Installing symfony/swiftmailer-bundle (v3.1.6): Loading from cache
remote:   - Installing twig/twig (v2.4.4): Loading from cache
remote:   - Installing symfony/twig-bridge (v4.0.3): Loading from cache
remote:   - Installing symfony/translation (v4.0.3): Loading from cache
remote:   - Installing symfony/validator (v4.0.3): Loading from cache
remote:   - Installing twig/extensions (v1.5.1): Loading from cache
remote:   - Installing symfony/twig-bundle (v4.0.3): Loading from cache
remote:   - Installing pagerfanta/pagerfanta (v1.0.5): Loading from cache
remote:   - Installing white-october/pagerfanta-bundle (v1.1.2): Loading from cache
remote: Generating optimized autoload files
remote: ocramius/package-versions:  Generating version class...
remote: ocramius/package-versions: ...done generating version class
remote: 
remote:  // Clearing the cache for the prod environment with debug                      
remote:  // false                                                                       
remote: 
remote:  [OK] Cache for the "prod" environment (debug=false) was successfully cleared.  
remote: 
remote: 
remote:  // Warming up the cache for the prod environment with debug                    
remote:  // false                                                                       
remote: 
remote:  [OK] Cache for the "prod" environment (debug=false) was successfully warmed.   
remote: 
To root@104.236.215.199:/var/www/crvfakeexample.git
   3610a87..ee3d209  master -> master

We see all of this output in our local terminal.

Unfortunately we lose all the nice terminal colouring.

Because we've already setup the Environment Variables we don't need to worry about anything extra at this point. In the real world, if your current change involved a new environment variable or several, be sure to add them in advance.

If this is your first deploy, be sure to run the database setup commands specific to this project:

# from your server
cd /var/www/crvfakeexample.com

php bin/console doctrine:database:create
php bin/console doctrine:schema:create
php bin/console doctrine:fixtures:load

Don't Forget var

# from the server

mkdir /var/www/crvfakeexample.com/var
chown $(whoami):www-data /var/www/crvfakeexample.com/var
cd /var/www/crvfakeexample.com

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

Depending on your directory permissions, this may not be a huge deal. It's a good idea to get this sorted as a precaution, all the same.

We must do this as the var directory is another directory excluded in our .gitignore file, and as such won't be copied up by the git push. This is far from ideal, but as a starting point, I'm ok with this being "good enough" for now.

And with that, we have done our first deploy of Symfony 4 using Git.

Episodes