Hello, I am struggling with deployment of Laravel app to nixos machine. During development I have used php artisan serve
to work with my app. Now I want to deploy it to production, but I am stuck. Nginx nor Apache didn’t work for me. Has anyone experience with Laravel deployment, or deployment of any php app using LAMP or LEMP stack?
Did you want to build and deploy your laravel
application declaratively entirely with nix/nixos … or did you simply want to host your application on nixos and manage the code imperatively?
For begining it would be enough to host and manage it imperatively. To have it entirely managed by nix/nixos would be great bonus, but it is not necessary for now.
I’ll try to keep this simple, descriptive-but-assuming-you-know-some-basics, and clear.
{ config, pkgs, lib, ... }:
let
# declare a custom php package with any php extensions and tweaks we need, like memcached for example
php' = pkgs.php.buildEnv {
extensions = { enabled, all }: with all; enabled ++ [ memcached ];
extraConfig = ''
memory_limit = 256M
'';
};
in
{
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.httpd.enable = true;
services.httpd.adminAddr = "webmaster@example.org";
services.httpd.extraModules = [ "proxy_fcgi" ];
services.httpd.virtualHosts."example.org" = {
documentRoot = "/var/www/example.org/public";
# want ssl + a let's encrypt certificate? add `forceSSL = true;` right here
extraConfig = ''
# https://laravel.com/docs/8.x/deployment#nginx
Header always append X-Frame-Options "SAMEORIGIN"
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Content-Type-Options "nosniff"
<Directory /var/www/example.org/public>
AllowOverride all
DirectoryIndex index.php
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${config.services.phpfpm.pools.example.socket}|fcgi://localhost/"
</If>
</FilesMatch>
</Directory>
'';
};
services.mysql.enable = true;
services.mysql.package = pkgs.mariadb;
# configure a php-fpm pool to run our web application
services.phpfpm.pools.example = {
user = "example";
group = "example";
phpPackage = php';
settings = {
"listen.owner" = config.services.httpd.user;
"listen.group" = config.services.httpd.group;
# you should probably take some time to understand these values, see https://www.php.net/manual/en/install.fpm.configuration.php
"pm" = "dynamic";
"pm.max_children" = 8;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 4;
"pm.max_requests" = 500;
"request_terminate_timeout" = 300;
};
};
# you might need a laravel scheduler service to run
systemd.services.example-laravel-scheduler = {
description = "example laravel scheduler";
startAt = "minutely";
# ensure this service doesn't run unless the app is properly installed
unitConfig = {
ConditionPathExists = "/var/www/example.org/.env";
ConditionDirectoryNotEmpty = "/var/www/example.org/vendor";
};
serviceConfig = {
Type = "oneshot";
User = "example";
Group = "example";
SyslogIdentifier = "example-laravel-scheduler";
WorkingDirectory = "/var/www/example.org";
ExecStart = "${php'}/bin/php artisan schedule:run -v";
};
};
# create the basic directory structure we need
systemd.tmpfiles.rules = [
"d /var/www"
"d /var/www/example.org 0750 example example"
];
# because we probably want free ssl certificates
security.acme.acceptTerms = true;
security.acme.email = "webmaster@example.org";
# create a separate user to run php as for security, isolation, better reproducibility, etc...
users.users.example = {
description = "php-fpm user for our example laravel web application";
isNormalUser = true;
shell = pkgs.bash;
# packages needed to install your laravel application, etc...
packages = [
php'
php'.packages.composer
pkgs.git
pkgs.nodejs
];
openssh.authorizedKeys.keys = [
# maybe a deploy key goes here and hooks into your CI/CD pipeline?
];
};
users.groups.example = {};
users.users.wwwrun.extraGroups = [
"acme"
"example"
];
}
From here you rebuild, hop on your server, and become the “example” user via sudo -iu example
(or whatever):
[example@lamp:~]$ cd /var/www/example.org/
[example@lamp:~]$ git clone git@github.com:adamzivcak/example.git .
[example@lamp:~]$ # hack up your .env file with the details of your install
[example@lamp:~]$ nano .env
[example@lamp:~]$ # prepare your web app for production, see https://laravel.com/docs/8.x/deployment for details
[example@lamp:~]$ composer install --optimize-autoloader --no-dev
[example@lamp:~]$ php artisan config:cache
[example@lamp:~]$ php artisan route:cache
[example@lamp:~]$ php artisan view:cache
I haven’t got into anything more detailed like laravel
queues via redis
or beanstalkd
, but if you want me to feel free to ask about it. A small part of my job involves deploying a large number of web applications written in php
, a non trivial amount of which are laravel
. Almost all of these web apps are deployed on NixOS.
I hope this helps.
ping @stianlagstad because of Tutorial for setting up the LAMP stack on a NixOS server?
It works perfectly. Many thanks.
I have one more issue with php in my app, maybe you can help me.
I need to call some system tools like git
or nixops
from my app. I use exec($command, $result, $resultcode)
, or shell_exec($command)
. But problem is, that php in my app has no idea, where to find theese packages used in $command
, and fails with sh: nixops: command not found
. When I specify full path like /nix/store/vikh.....dfkif-user-environment/bin/nixops
- then it works. But it fails when this package has dependencies / needs to call another package from nix/store/.
Have you any idea how to fix this?
You can create a customized PATH
variable in your php-fpm
pool like so:
services.phpfpm.pools.example = {
user = "example";
group = "example";
phpPackage = php';
phpEnv.PATH = with pkgs; lib.makeBinPath [ git nixops ];
settings = {
"listen.owner" = config.services.httpd.user;
"listen.group" = config.services.httpd.group;
# you should probably take some time to understand these values, see https://www.php.net/manual/en/install.fpm.configuration.php
"pm" = "dynamic";
"pm.max_children" = 8;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 4;
"pm.max_requests" = 500;
"request_terminate_timeout" = 300;
};
};