SaltStack on Your VPS Series
Part 4 of 6
LAMP Stack Deployment via Salt States
Full Nginx + PHP-FPM + MariaDB deployment with role separation and Jinja-templated virtual host management.
40 minutes
Architecture Overview
This deployment separates concerns into distinct roles:
- Web server (Nginx + PHP-FPM) — handles HTTP requests and PHP execution
- Database server (MariaDB) — handles data storage, ideally on a separate minion
- Shared baseline (common) — applied to all servers regardless of role
Directory Structure
/srv/salt/
top.sls
common.sls
lamp/
init.sls <- applies full LAMP to one server
webserver.sls <- Nginx + PHP-FPM only
database.sls <- MariaDB only
php.sls <- PHP-FPM and extensions
vhost.sls <- Virtual host management
files/
nginx.conf
php-fpm.conf
www.conf
site.conf.jinjatop.sls — Role Assignment
/srv/salt/top.sls
base:
'*':
- common
'lamp-single':
- lamp
'web-*':
- lamp.webserver
- lamp.php
- lamp.vhost
'db-*':
- lamp.databasecommon.sls — Baseline
/srv/salt/common.sls
essential_packages:
pkg.installed:
- pkgs:
- curl
- wget
- vim
- git
- unzip
- htop
- ufw
- fail2ban
- chrony
chrony_service:
service.running:
- name: chrony
- enable: True
- require:
- pkg: essential_packages
set_timezone:
timezone.system:
- name: UTCNginx Web Server
/srv/salt/lamp/webserver.sls
nginx_package:
pkg.installed:
- name: nginx
nginx_service:
service.running:
- name: nginx
- enable: True
- watch:
- file: nginx_main_config
- require:
- pkg: nginx_package
nginx_main_config:
file.managed:
- name: /etc/nginx/nginx.conf
- source: salt://lamp/files/nginx.conf
- user: root
- group: root
- mode: '0644'
- require:
- pkg: nginx_package
nginx_default_site:
file.absent:
- name: /etc/nginx/sites-enabled/default
allow_http:
cmd.run:
- name: ufw allow 'Nginx Full'
- unless: ufw status | grep -q 'Nginx Full.*ALLOW'PHP-FPM
/srv/salt/lamp/php.sls
php_packages:
pkg.installed:
- pkgs:
- php8.1-fpm
- php8.1-cli
- php8.1-common
- php8.1-mysql
- php8.1-xml
- php8.1-curl
- php8.1-mbstring
- php8.1-zip
- php8.1-bcmath
- php8.1-intl
- php8.1-gd
php_fpm_service:
service.running:
- name: php8.1-fpm
- enable: True
- watch:
- file: php_fpm_pool_config
- require:
- pkg: php_packages
php_fpm_pool_config:
file.managed:
- name: /etc/php/8.1/fpm/pool.d/www.conf
- source: salt://lamp/files/www.conf
- require:
- pkg: php_packagesMariaDB
/srv/salt/lamp/database.sls
mariadb_packages:
pkg.installed:
- pkgs:
- mariadb-server
- mariadb-client
- python3-pymysql
mariadb_service:
service.running:
- name: mariadb
- enable: True
- require:
- pkg: mariadb_packages
mariadb_config:
file.managed:
- name: /etc/mysql/mariadb.conf.d/50-server.cnf
- source: salt://lamp/files/50-server.cnf
- require:
- pkg: mariadb_packages
- watch_in:
- service: mariadb_serviceVirtual Host Management with Jinja
/srv/salt/lamp/vhost.sls
{% set sites = pillar.get('nginx_sites', {}) %}
{% for site_name, site in sites.items() %}
{{ site_name }}_webroot:
file.directory:
- name: {{ site.get('root', '/var/www/' + site_name) }}
- user: www-data
- group: www-data
- mode: '0755'
- makedirs: True
{{ site_name }}_vhost_config:
file.managed:
- name: /etc/nginx/sites-available/{{ site_name }}.conf
- source: salt://lamp/files/site.conf.jinja
- template: jinja
- context:
site_name: {{ site_name }}
server_name: {{ site.get('server_name', site_name) }}
root: {{ site.get('root', '/var/www/' + site_name) }}
php_socket: /run/php/php8.1-fpm.sock
{{ site_name }}_vhost_enabled:
file.symlink:
- name: /etc/nginx/sites-enabled/{{ site_name }}.conf
- target: /etc/nginx/sites-available/{{ site_name }}.conf
- watch_in:
- service: nginx_service
{% endfor %}Supporting Config Files
site.conf.jinja (Nginx vhost template)
server {
listen 80;
server_name {{ server_name }} www.{{ server_name }};
root {{ root }};
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:{{ php_socket }};
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
}Pillar Data
/srv/pillar/lamp.sls
mariadb:
root_password: 'your-strong-root-password'
databases:
- name: myapp_db
user: myapp_user
password: 'app-db-password'
nginx_sites:
myapp:
server_name: example.com
root: /var/www/myapp/publicDeploying the Stack
# Single server — test first
sudo salt 'lamp-single' state.apply lamp test=True
sudo salt 'lamp-single' state.apply lamp
# Separated roles
sudo salt 'db-01' state.apply lamp.database
sudo salt 'web-01' state.apply lamp.webserver,lamp.php,lamp.vhost
# Or let top.sls handle targeting
sudo salt -G 'role:database' state.highstate
sudo salt -G 'role:webserver' state.highstateVerifying the Deployment
sudo salt 'web-*' service.status nginx
sudo salt 'web-*' service.status php8.1-fpm
sudo salt 'db-*' service.status mariadb
sudo salt 'web-01' cmd.run 'curl -s -o /dev/null -w "%{http_code}" http://localhost'What's Next
Your LAMP stack is now fully managed by Salt. In Part 5, we add Docker — letting Salt manage container workloads, deploy Compose files, and manage running containers as state resources.
