How To Deploy a Symfony Application to Production on a Linux box

In this article I will not cover a multi-host setup, as this is supposed to be a one app, dedicated instance, although the same would apply to the latter case. For a Symfony application deployment, we will be using a Linux box (Ubuntu in this case).

Let’s suppose you have acquired your box, and so far, only got an IP address and SSH, root credentials. I would also assume that you know how to log to a server using SSH. This article will employ Debian based commands, which were tested on an Ubuntu 16.04 LTS.

First things first, let’s update the base packages :

sudo apt-get update
sudo apt-get upgrade

Let’s get nano for simple config files editing :

apt-get install nano


The next step would be adding custom repositories to get the latest PHP versions, as the stock versions are not necessarily what we would aim for :

sudo add-apt-repository ppa:ondrej/php

This command will probably fail, as the “add-apt-repository” command might not be available from scratch. Hopefully, you can install it by issuing the following command :

sudo apt-get install software-properties-common python-software-properties

Once this is done, let’s update the available packages list

sudo apt-get update

Now, all the PHP packages are available (as of this moment, the latest version available is 7.2, and 7.1 being the latest stable, but it’s up to you on which to chose)

# PHP 7.2
sudo apt-get install php7.2-cli php7.2-intl php7.2-opcache php7.2-zip php7.2-xml php7.2-mysql php7.2-mbstring libapache2-mod-php7.2

# OR 7.1
sudo apt-get install php7.1-cli php7.1-intl php7.1-opcache php7.1-zip php7.1-xml php7.1-mysql php7.1-mbstring libapache2-mod-php7.1


Now we can start setting MySQL up

apt-get install mysql-server mysql-client

Now let’s create a MySQL user and database, login using your root credentials, (-p is optional, for password)

mysql -u root -p xxxxx

First create a schema, let’s call it “app” (oh whatever you like) :

create database app;

Then, create a database user, and grant the necessary privileges :

CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'P4SSW0RD';

Composer & GIT

Let’s get composer and git for dependency management

apt-get install git
apt-get install curl
apt-get install php7.2-curl # php7.1-curl if you are going with PHP 7.1

php -r "copy('', 'composer-setup.php');"
# try http if https doesnt work

php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"

php composer-setup.php
php -r "unlink('composer-setup.php');"

mv composer.phar /usr/local/bin/composer


At this point, we can start setting up apache to handle our app, but first, let’s set the default timezone for php

# use /7.1/ for PHP 7.1
nano /etc/php/7.2/apache2/php.ini 
date.timezone = Africa/Casablanca

Let’s back the default vhost up

cd /etc/apache2/sites-available
sudo mv 000-default.conf default-bkp.conf

then edit it :

nano /etc/apache2/sites-available/000-default.conf

This virtual host setting, assumes the app will be located in /var/www/app, and includes the default rewrite tags. So basically, there would be no need for a .htaccess to rewrites paths. If however, the latter is required for other needs, you should “AllowOverride”.

<VirtualHost *:80>

    DocumentRoot /var/www/app/public
    <Directory /var/www/app/public>
        AllowOverride None
        Order Allow,Deny
        Allow from All

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

    # uncomment the following lines if you install assets as symlinks
    # or run into problems when compiling LESS/Sass/CoffeScript assets
    # <Directory /var/www/project>
    #     Options FollowSymlinks
    # </Directory>

    ErrorLog /var/log/apache2/symfony_error.log
    CustomLog /var/log/apache2/symfony_access.log combined

Do not forget to enable the Apache Rewrite module

a2enmod rewrite

File Permission

I bet this is the trickiest part of any manual app deployment, I must admit that I always forget how to properly set the file permissions, although I master the theory, I tend to forget a lot. Hopefully, the ACL packages makes it easy to handle this, so let’s get it :

sudo apt-get install acl

Let’s create a system user for our app, you will be prompted to chose a password :

adduser appadmin

Now we will let the system know that the root app folder belongs to the Apache user, which is normal since the app will be run under it’s process :

sudo chown -R www-data:www-data /var/www/app

But, our user should also have some permissions on the folder in order for you to deploy either using FTP/SFTP or GIT :

setfacl -R -m u:appadmin:rwx /var/www/app/


Finally, we can get to the fun part, and actually making the app available for our potential (millions) or users awaiting for our kick-ars app. The system user we created earlier can log into the system and deploy through SFTP. This is my personal preference over setting up an FTP server, as it works in the same way, is more secure and is less burdensome, or pull from a git repository.

Either way, once the base files are deployed, we need to run some commands (I assumed you already know that /var/log, /var/cache and /vendor folders are in the gitignore / ignored from file transfer)

Let’s set the app to production :

export APP_ENV=prod (Previously SYMFONY_ENV)

Now, we should install composer requirements excluding dev packages (The app is going live, so it would be awkward to have that sweet debug bar right ?)

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

One last thing, remember that database schema we previously created for our app ? We should let Symfony know about it through environment variables

export DATABASE_URL=mysql://appuser:P4SSW0RD@

At this point, you can issue any command line instructions, like doctrine:schema:update or cache:clear, you are all set.

PS: if, for any reason, you don’t like using environment variables (although I don’t see why you wouldn’t) you can use the dotenv package. It contains the same variables but in a .env file.

Happy coding.

Leave a comment