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 w
rite 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.