Deploying PHP applications on Azure environments is possible, but makes not much of a sense. Main reason is a number of limitations, especially when you are used to solutions like Heroku.
One of the limitations is the lack of easily achieved background queue workers. Running queue:work
as startup command will crash your deployment slot, Azure WebJobs will not work on Linux instances and Windows instances have only PHP 7.something as stack. Your options are limited and this is one of them, very hacky and workaround-like, but worth testing.
⚠️ This is a very poorly tested solution and the stability of it is currently unknown. Use at your own risk.
⚠️ ALSO: this instruction was created for a very specific case, yours is probably different.
What we will do, is use supervisor
, as mentioned in Laravel Docs. But with Azure it's not as easy as described there. There are few problems:
- Azure environment will not preserve any changes made outside
/home
directory - Laravel Documentation's config example needs to be changed
- Azure supervisor doesn't see environment variables and will fail on DB connection attempt
Let's address those:
First things first, enable queues just like described in the Laravel Documentation and set the proper QUEUE_CONNECTION
in App Service/slot config.
Supervisor seems to ignore environment variables that are set from Azure Dashboard level (in App Service/slot configuration). When running artisan queue:work
the app behaves like no environment variables are set, therefore it tries to operate on mysql
connection that uses localhost
and forge
credentials (or other, see config/database.php
). Obviously this will not work. But what supervisor does NOT ignore is .env
file variables. Therefore you have two options:
- create
.env
file once, with proper data - recreate
.env
file every time container is started
Second option seems to be a bit more time-consuming in terms of preparation, but the advantages are bigger than costs. Mainly: you don't have to change file again, when variables set from dashboard level are changed. You can use this example command I created in a hurry, so don't expect it to be flawlessly written:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class EnvGenerate extends Command
{
protected $signature = 'env:generate {--force}';
protected $description = 'Generates .env file';
private $default_vars = [
'APP_ENV',
'APP_DEBUG',
'APP_KEY',
'DATABASE_URL',
'DB_CONNECTION',
'QUEUE_CONNECTION'
];
public function handle(): int
{
if (!$this->option('force')) {
$this->error('This will reset your .env file. No action will be performed without --force option');
return self::FAILURE;
}
$vars = [];
if (getenv('SUPERVISOR_ENV_VARS')) {
$vars = explode(',', getenv('SUPERVISOR_ENV_VARS'));
}
if (!count($vars)) {
$vars = $this->default_vars;
}
$env_content = '';
foreach ($vars as $v) {
$val = getenv($v);
if (!is_numeric($val)) {
$val = '"' . $val . '"';
}
$env_content .= $v . '=' . $val . PHP_EOL;
}
if (file_put_contents('.env', $env_content) === false) {
return self::FAILURE;
}
return self::SUCCESS;
}
}
Just add the following at the end of your Startup Command:
&& php artisan env:generate --force
Notice that you can set which env variables will be saved in
.env
file by settingSUPERVISOR_ENV_VARS
variable to something likeAPP_ENV,DATABASE_URL,DB_CONNECTION
Add a laravel-worker.conf
to your project in the main directory. Example working content:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/site/wwwroot/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=root
numprocs=8
redirect_stderr=true
stdout_logfile=/home/site/wwwroot/storage/logs/worker.log
stopwaitsecs=3600
Changes made, compared to Laravel Docs Example:
artisan
path incommand
was changed to Azure-specific- command arguments changed by removing
sqs
option (we're not using SQS here) - log file path changed as well
- supervisor execution
username
changed
This one is similar to Laravel Docs, although there are some differences, so long story short - this is what you need to add at the end of your deployment slot's Startup Command (Configuration->General):
&& apt install supervisor -y && cp /home/site/wwwroot/laravel-worker.conf /etc/supervisor/conf.d && service supervisor start && supervisorctl reread && supervisorctl update && supervisorctl start laravel-worker:*
If we don't use Docker images here, we will have to install supervisor with every container startup. Fortunately that doesn't take long.