Deploying a Symfony app on Fly.io
So you want to deploy your Symfony app on Fly.io
? Great! But... fly launch will not be able to generate the correct configuration files for you, as there is no
scanner & template yet for Symfony. However, there is a scanner for Laravel and with some changes it can be used for
Symfony as well.
This is not a step-by-step guide. Trivial steps are omitted, but you can find all the modifications that I’ve made in the GitHub repository linked down below.
Generating the basic Fly configuration
As mentioned before, we will modify the Laravel configuration files to make them work for Symfony. First, we need to get those Laravel configuration files. There's two ways to get the basic Fly configuration into your project:
- You can copy the files from the Fly CLI repository.
- The faster approach is to use the Fly CLI to generate the files for you. You just have to trick the CLI into thinking
your project is a Laravel project. You can do this by creating a file called
artisanin the root directory of your project. This file can be empty, but it has to exist. After that you can runfly launchand it will generate the files for you. You can now remove theartisanfile.
touch artisan
fly launch
rm artisan
Dealing with environment variables
There's a couple ways to specify runtime environment variables for your Symfony app in a Fly.io context:
- In
fly.tomlthere is a section called[env]where you can define environment variables. These variables will be available to your application at runtime. They are suitable for environment variables that are not secret, such as yourAPP_DEBUGvalue. - Fly secrets are a secure way to define environment variables. They are
suitable for environment variables that are secret, such as your
APP_KEYandDATABASE_URL. - You can also use
.env/.env.prodfiles for non-secret environment variables.
So, for non-secret environment variables there are two options: fly.toml or .env files. Personally, I prefer to
use fly.toml for my non-secret environment variables. This means I don’t use .env or any of its
variants (.env.prod etc.). To prevent them from getting into the image, I added this to the .dockerignore:
*.env*
Now, if you would try to deploy this, it would not work. This is because, by default, Symfony requires there to be
a .env file. This can be disabled by specifying the disable_dotenv runtime option to be true
in public/index.php:
$_SERVER['APP_RUNTIME_OPTIONS'] = [
'disable_dotenv' => true,
];
Two problems:
- Now also locally you will not be able to use
.envfiles anymore. Probably not desirable. - You also need to specify this in
bin/console. Meh.
To solve both problems, I decided to create a custom runtime that sets the disable_dotenv option to true:
<?php
namespace App;
use Symfony\Component\Runtime\SymfonyRuntime;
class FlySymfonyRuntime extends SymfonyRuntime
{
public function __construct(array $options = [])
{
$options['disable_dotenv'] = true;
parent::__construct($options);
}
}
This file is then copied in my Dockerfile to the src/
directory: cp .fly/FlySymfonyRuntime.php /var/www/html/src/FlySymfonyRuntime.php and enabled by
specifying APP_RUNTIME = '\App\FlySymfonyRuntime' in the fly.toml file.
Composer post-install-cmd event
Generally, Symfony projects have post-install-cmd events defined in the composer.json file that call commands such
as assets:install and cache:clear. Running these in the Dockerfile can be problematic, especially when environment
variables are required. Instead, I relocated these commands to the entrypoint. I did this by disabling scripts in the Dockerfile
(composer install --no-scripts) and then creating a file called .fly/scripts/assets.sh with the following contents:
#!/usr/bin/env bash
/usr/bin/php /var/www/html/bin/console assets:install
As far as I understand, cache:clear only clears system caches that reside in the var/ directory, so it is not necessary
to run this on every deploy: we have a brand-new VM after all.
cache:clear also warms up the cache. Because we are not calling cache:clear, we need to warm up the cache ourselves.
I decided to do this by calling cache:warmup in the entrypoint. This can be done by creating a file called
.fly/scripts/warmup.sh with the following contents:
#!/usr/bin/env bash
/usr/bin/php /var/www/html/bin/console cache:warmup
Dealing with the Fly.io proxy
TLS is terminated at the Fly.io proxy, so requests are coming in as HTTP requests. This can cause some annoying "bugs":
URLs are generated as http:// URLs for example. To solve this, you need to declare the proxy as a trusted proxy, which
can be done by specifying trusted_proxies: '127.0.0.1,REMOTE_ADDR' in the config/packages/framework.yaml file.
More information can be found here: How to Configure Symfony to Work behind a Load Balancer or a Reverse Proxy
Connecting to a database
This is the easy part! You can create a Postgres cluster on Fly using fly postgres create and then attach it
using fly postgres attach <db-app-name> --app <app-name>. This will create a database and the DATABASE_URL secret
for your app.
Migrations can be ran on deploy using a release command. This can be done by adding the following to the fly.toml
file:
[deploy]
command = "bin/console doctrine:migrations:migrate --no-interaction"
Queues
Queue workers could be defined as supervisor processes in your main web Fly VM, but I prefer to create a separate Fly
VM for it. This is because I want to be able to scale the queue workers independently from the web VM. This can be done
by adding the following to the fly.toml file:
[processes]
app = ""
worker = "php bin/console messenger:consume async"
Conclusion
In this post we've seen how to deploy a Symfony app on Fly.io. This is not particularly hard to do because the deployment process for Symfony is so similar to Laravel. The full code can be found here:
https://github.com/dejagersh/symfony-on-fly
I hope this was helpful! If you have any suggestions or questions, please reach out to me on Twitter: @dejagersh.