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
artisan
in the root directory of your project. This file can be empty, but it has to exist. After that you can runfly launch
and it will generate the files for you. You can now remove theartisan
file.
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.toml
there 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_DEBUG
value. - Fly secrets are a secure way to define environment variables. They are
suitable for environment variables that are secret, such as your
APP_KEY
andDATABASE_URL
. - You can also use
.env
/.env.prod
files 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
.env
files 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.