How I deploy Laravel apps
Published by Matthew Daly at 29th January 2018 10:00 pm
A while back I provided details of the web server setup I used for Django applications. Nowadays I tend to use Laravel most of the time, so I thought I'd share an example of the sort of setup I use to deploy that.
Server OS
As before I generally prefer Debian Stable where possible. If that's not possible for any reason then the current Ubuntu LTS is an acceptable substitute.
Web server
My usual web server these days is Nginx with PHP 7 or better via FPM. I generally use HTTP2 where possible, with SSL via Let's Encrypt.
Here's my typical Nginx config:
1fastcgi_cache_path /etc/nginx/cache levels=1:2 keys_zone=my-app:100m inactive=60m;2fastcgi_cache_key "$scheme$request_method$host$request_uri";3add_header Content-Security-Policy "default-src 'self'; script-src 'self'; img-src 'self' https://placehold.it; style-src 'self' https://fonts.googleapis.com ; font-src 'self' https://themes.googleusercontent.com; frame-src 'none'; object-src 'none'";4server_tokens off;56server {7 listen 80;8 listen [::]:80;9 server_name my-app.domain;10 return 301 https://$server_name$request_uri;11}1213server {14 listen 443 ssl http2;15 listen [::]:443 ssl http2;16 include snippets/ssl-my-app.domain.conf;17 include snippets/ssl-params.conf;18 client_max_body_size 50M;19 fastcgi_param HTTP_PROXY "";2021 access_log /var/log/nginx/access.log;22 error_log /var/log/nginx/error.log;2324 root /var/www/my-app.domain/current/public;25 index index.php index.html index.htm;2627 server_name my-app.domain;2829 location / {30 try_files $uri $uri/ /index.php?$query_string;31 }3233 location ~ \.php$ {34 try_files $uri /index.php =404;35 fastcgi_split_path_info ^(.+\.php)(/.+)$;36 fastcgi_pass unix:/var/run/php/php7.0-fpm-my-app.sock;37 fastcgi_index index.php;38 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;39 include fastcgi_params;40 fastcgi_cache my-app;41 fastcgi_cache_valid 200 60m;42 }4344 location ~ /.well-known {45 allow all;46 }4748 location ~* \.(?:manifest|appcache|html?|xml|json)$ {49 expires -1;50 gzip on;51 gzip_vary on;52 gzip_types application/json text/xml application/xml;53 }5455 location ~* \.(?:rss|atom)$ {56 expires 1h;57 add_header Cache-Control "public";58 gzip on;59 gzip_vary on;60 gzip_types application/xml+rss;61 }6263 location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {64 expires 1M;65 access_log off;66 add_header Cache-Control "public";67 }6869 location ~* \.(?:css|js)$ {70 expires 1y;71 access_log off;72 add_header Cache-Control "public";73 gzip on;74 gzip_vary on;75 gzip_types text/css application/javascript text/javascript;76 }77}
The times for FastCGI caching tend to vary in practice - sometimes it's not appropriate to use it all, while for others it can be cached for some time.
It's generally fairly safe to cache CSS and JS for a long time with a Laravel app if you're using Mix to version those assets, so I feel comfortable caching them for a year. Images are a bit dicier, but still don't change often so a month seems good enough.
I'll typically give each application its own pool, which means copying the file at /etc/php/7.0/fpm/pool.d/www.conf
to another file in the same directory, amending the pool name and path to set a new location for the socket, and then restarting Nginx and PHP-FPM. Here are the fields that should be changed:
1; Start a new pool named 'www'.2; the variable $pool can be used in any directive and will be replaced by the3; pool name ('www' here)4[my-app.domain]5...6listen = /var/run/php/php7.0-fpm-my-app.sock
Database
I'm a fan of PostgreSQL - it's stricter than MySQL/MariaDB, and has some very useful additional field types, so where possible I prefer to use it over MySQL or MariaDB.
Cache and session backend
Redis is my usual choice here - I make heavy use of cache tags so I need a backend for the cache that supports them, and Memcached doesn't seem to have as much inertia as Redis these days. Neither needs much in the way of configuration, but you can get a slight speed boost by using phpiredis.
Queue
I sometimes use Redis for this too, but it can be problematic if you're using Redis as the queue and broadcast backend, so these days I'm more likely to use Beanstalk and keep Redis for other stuff. I use Supervisor for running the queue worker, and this is an example of the sort of configuration I would use:
1[program:laravel-worker]2process_name=%(program_name)s_%(process_num)02d3command=php /var/www/artisan queue:work --sleep=3 --tries=34autostart=true5autorestart=true6user=www-data7numprocs=88redirect_stderr=true9stdout_logfile=/var/log/worker.log
This is fairly standard for Laravel applications.
Scheduler
I often make use of the Laravel scheduled tasks system. Here's the typical cron job that would be used for that:
* * * * * php /var/www/artisan schedule:run >> /dev/null 2>&1
Again, this is standard for Laravel applications. It runs the scheduler every minute, and the scheduler then determines if it needs to do something.
Provisioning
To set all this up, I'll generally use Ansible. In addition to this, I'll generally also set up fail2ban to block various attacks via both HTTP and SSH.