Symfony and Dokku

Recently I've been using Dokku to deploy some of my side-projects, I wanted a simple deployment procedure that didn't rely on complicated build systems, as these were just small apps that I needed to host somewhere easily. I'm using Dokku for that - so here's some instructions for getting Symfony working with it.

I'll preface this post by saying this blog post has sat in my drafts for a while. I had some troubles with getting this working, however it was due to the way I was running things in my Procfile and since working out what was happening thanks to josegonzalez on GitHub - things are running just fine.

There's not a lot to get a Symfony project up and running on Dokku. I've got a repository setup ready for it. The GitHub repository is here and if you'd like to see the app running, the demo site is here

Prerequisites

This is all assuming you have the DOKKU_CLIENT.SHinstalled and setup locally - if not, you'll need to add the appname into some of the configuration.

You'll also need the dokku-mysql plugin installed.

Here's the breakdown on how to get up and running:

Setup your Dokku app

You'll need to make sure you're inside your project folder, and that it's a git repository (if you've already got a project that's a git repository, skip this step)

> cd my-project
> git init
> git add .
> git commit -m 'Initial commit'

Then create a dokku app for your project:

> dokku apps:create symfony

You'll get some output from the command about the app being created, and you can verify that it's there by using:

> git remote -v

You should see a remote named dokku that points to your Dokku server:

> git remote -v
dokku   dokku@dokku.mfyu.co.uk:symfony (fetch)
dokku   dokku@dokku.mfyu.co.uk:symfony (push)

Once the app is created, you'll need to ensure that the correct environment (prod) is setup for Symfony on Dokku, otherwise you'll run into issues where Symfony will think it's in development, when in reality it's in production. This means that various classes can't be found as they've not been installed (because they'll be in your require-dev section in composer.json)

dokku config:set SYMFONY_ENV=prod

Create and configure MySQL

Next, if you need a database (which for my sample repo, you will) you'll need to create that:

> dokku mysql:create symfony-db

Then, you'll need to link that database to your app:

> dokku mysql:link symfony-db

Now, you'll need to configure Doctrine within Symfony to look at that database

app/config.yml

# Doctrine Configuration
doctrine:
    dbal:
        driver: pdo_mysql
        url: '%env(DATABASE_URL)%'

Note the use of the environment variable DATABASE_URL - this is because when you link the MySQL database to your app, it exposes an environment variable that's the dsn to connect to the database in the format: mysql://username:password@host-container:port/db-name

If you're using a version of Symfony before 3.2 that doesn't support using environment variables in config files, you can use environment variables from the Incenteev/ParameterHandler package instead.

Nginx Configuration

You'll need to configure nginx for Symfony's re-write rules:

nginx.conf

location / {
    # try to serve file directly, fallback to rewrite
    try_files $uri @rewriteapp;
}

location @rewriteapp {
    # rewrite all to app.php
    rewrite ^(.*)$ /app.php/$1 last;
}

location ~ ^/app\.php(/|$) {
    try_files @heroku-fcgi @heroku-fcgi;
    internal;
}

And then setup a Procfile to use that nginx config:

web: $(composer config bin-dir)/heroku-php-nginx -C nginx.conf web

Deployment tasks

If you wish to run tasks during the deployment process you can use an app.json file in your project with predeploy and postdeploy tasks, note that as per the documentation: postdeploy changes are NOT committed to the app image. - I forgot to read this and wondered why certain things weren't working on another project, that's why.

We have a predeploy task of removing any files from the web/ directory we don't want, such as app_dev.php, app_test.php and config.php - you could always move this out into a separate script and call that if you find yourself needing to perform more tasks.

Then, we have a postdeploy task that runs database migrations - now, this can cause some downtime if you're running migrations on a large database, but if that's the case, you'll likely have a more complicated deployment setup with atomic deployments, ensuring that your app doesn't have a maintenance window and the app is always available.

For this example, we'll assume a minor bit of downtime is okay.

app.json

{
  "scripts": {
    "dokku": {
      "predeploy": "rm -f web/app_*.php && rm -f web/config.php && php bin/console doctrine:migrations:migrate --allow-no-migration"
    }
  }
}

We also use --allow-no-migration to ensure that doctrine doesn't error when running migrations if there aren't any.

UPDATE: I've moved this to the predeploy task, the reason being, that if a migration fails, we don't want the deployment to continue.

Deploying

Once all that is done, it's just a matter of deploying just as the dokku documentation tells us to:

> git push dokku master

And your app is live! Job done!