Part 1/3 - Deploying with rsync

Our first deploy process is going to be extremely basic - and in the real world, risky.

This said, "basic" does not necessarily mean simple. If this process is new to you then as a heads up, things can go wrong and that's why we're practicing on a demo server.

We will make use rsync.

Now, rsync is great. It allows you to reliably copy files from your local machine up to your server with a bare minimum of fuss.

It's also built in and good to go on both Linux and OSX.

I'll get back to why I consider this particular approach to using rsync to be risky in just a moment.

Building The Project

In order to get a production-ready version of our site, we must complete a 'build' process.

Whilst this step can (and will) get more involved later on in this series, for the moment our build process is simple:

SYMFONY_ENV=prod composer install --no-dev --optimize-autoloader

Most of this command should likely be familiar to you. In fact, it's the command taken right from the Symfony deployment documentation page.

For clarity, --no-dev means we won't download anything inside the require-dev section of our project's composer.json file. We don't (or shouldn't!) need any of the dev stuff for our production build, so including it would just make our build size that much bigger.

The --optimize-autoloader flag converts the autoload rules from both your own project's composer.json, and each of the composer.json files from each vendor/{whatever} directory into a big PHP array called vendor/composer/autoload_classmap.php. An example of this is given in the video, or you can read more on the Composer docs.

The use of SYMFONY_ENV environment variable (SYMFONY_ENV=prod) ensures that any Symfony commands will be run using production configuration. This is useful as we cannot pass in a flag such as --env=prod, as composer would try to interpret that flag and get confused. If we don't pass SYMFONY_ENV=prod in this case, a bunch of development dependencies would be expected to be found (SensioGeneratorBundle for example), which aren't there as we explicitly said --no-dev to exclude them.

In this example we will still get some database errors. This is understandable as we do not have a local development environment, nor local database server to connect too. You can safely ignore these errors for the purposes of this example.

Is rsync Risky?

Earlier I mentioned that I consider this use of rsync to be risky.

For clarity I do not mean rsync in general is risky, just this specific approach.

Why is it risky?

Well, let's examine the command:

# from your local PC
# substitute this out for your path to your Symfony project directory
cd /home/chris/Development/symfony-deploy-test-site

rsync --exclude '.git' --exclude=var -avzh . root@

# equal to:
rsync --exclude '.git' --exclude=var -avzh /home/chris/Development/symfony-deploy-test-site root@

# or over multiple lines
rsync --exclude '.git' --exclude=var -avzh \
  /home/chris/Development/symfony-deploy-test-site \

Breaking this down...

We run this from our local PC (or Mac, of course). This PC is where we have our Symfony code.

We need to run this command from inside our Symfony project's root directory. In my case this directory is:


The period / dot (.) in the command indicates to copy everything from the current directory.

The flags (-avzh) are not super important, but here we go all the same:

  • a - archive mode (a bunch of other flags combined)
  • v - verbose, because it's nice to see what's being copied
  • z - compress, (-c is used taken for checksum :))
  • h - human readable output figures, because I am not a robot

In short rsync does a great job of quickly and reliably transferring the local folder contents up to the server.

You may be wondering why rsync, and not scp (secure copy).

rsync has the benefit of only transferring up what's changed, which makes the process of synchronising data really easy.

scp would copy everything from my local to the remote, every time.

With rsync, the files are copied using a particular user (root), on a particular IP (, and into a directory (:/var/www/ - note the leading colon, very important).

Why this is risky is it is copying directly from your desktop / laptop to your live / production website directory.

We will implement an improvement on this process in the next video.

As it stands, this process is not safe, and shouldn't be considered a viable deployment strategy for a working, production website. It does introduce us to moving data from our development to a production environment, so is a valid starting point.

We will get on to more robust solutions later in this series, some of which mirror this process in a more automated fashion.

Once this process completes then your project files are up, on your server.

# back on the server
cd /var/www/

ls -la

total 136
drwxr-xr-x  9 1000 1000  4096 Oct 14 19:02 .
drwxr-xr-x  4 root root  4096 Oct 14 19:17 ..
-rw-r--r--  1 1000 1000   265 Oct  6 00:10 .gitignore
-rw-rw-rw-  1 1000 1000   107 Oct 14 19:02
drwxr-xr-x  4 1000 1000  4096 Oct 14 19:02 app
drwxr-xr-x  2 1000 1000  4096 Oct 14 19:03 bin
-rw-rw-rw-  1 1000 1000  2277 Oct 14 19:02 composer.json
-rw-rw-rw-  1 1000 1000 85612 Oct 14 19:03 composer.lock
-rw-r--r--  1 1000 1000   988 Oct  6 00:10 phpunit.xml.dist
drwxr-xr-x  3 1000 1000  4096 Oct 14 19:02 src
drwxr-xr-x  3 1000 1000  4096 Oct 14 19:02 tests
drwxr-xr-x  5 1000 1000  4096 Oct 14 19:03 var
drwxr-xr-x 16 1000 1000  4096 Oct 14 19:03 vendor
drwxr-xr-x  3 1000 1000  4096 Oct 14 19:03 web

Notice how all the files and folders are owned by user / group 1000?

That won't work.

Our files and folders need to be owned by the user, and group of www-data.

Let's fix that.

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

This command recursively changes the owning user and group to both be www-data.

In order for our application to function properly, we must follow the commands described in the deploy process for the Symfony Demo app:

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

The last one will prompt you, so type y to accept.

Note that it is at this point that you would do your own custom setup routine for a real site.

The Symfony docs list some examples.

Finally, let's clear, and then warm-up the Symfony cache for the production environment:

cd /var/www/

php bin/console cache:clear --env=prod --no-debug --no-warmup
php bin/console cache:warmup --env=prod

Now browse to your site:

And et voila, a working Symfony website.

As part of this process we have validated our PHP build is working, as is our web server and database.

Cleaning Up

Don't forget to destroy your Digital Ocean Droplet.

Simply powering off is not enough. If you leave your Droplet available, but powered off, you will still be billed as though the Droplet were online. Be careful, I have made this mistake myself!

Wrapping Up

This was the most basic deploy I could think to show.

We covered a lot of ground, but not in very much depth.

Both Apache and PHP have been installed and left in their default setups. In reality you would likely want to tweak these setups considerably for production. We will cover more on this later in this series.

The crazy thing, however, is that this is likely good enough if this is your very first attempt at getting a Symfony site online. This will quite happily serve 100's of users per day without flinching.

Sure, once your site starts to become a little more frequently visited you will need to change some settings, but don't fall into the trap of thinking you need some crazy mega deploy process.

I guess what I'm trying to say, in a very roundabout way is:

Don't over complicate it.