How to deploy Laravel app to nixos machine?

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?

4 Likes