05 · Production · ~10 min
Real-world patterns.
Four sanitized patterns from production deployments. Each one is real code, trimmed to the load-bearing bits.
Pattern 01
Pillar-driven user with sudoers — Linux
Idempotent demo user creation, with sudoers granted via a drop-in. Validates the sudoers fragment with visudo -cf before moving it into place — a malformed line can't break sudo system-wide.
{% set sudo_user = salt['pillar.get']('linux:sudoer:username', 'saltdemo') %}
{% set sudo_shell = salt['pillar.get']('linux:sudoer:shell', '/bin/bash') %}
{% set nopasswd = salt['pillar.get']('linux:sudoer:nopasswd', True) %}
demo_user:
user.present:
- name: {{ sudo_user }}
- shell: {{ sudo_shell }}
- createhome: True
demo_user_sudoers:
file.managed:
- name: /etc/sudoers.d/{{ sudo_user }}
- user: root
- group: root
- mode: '0440'
- contents: |
# Managed by Salt
{{ sudo_user }} ALL=(ALL){% if nopasswd %} NOPASSWD:{% endif %} ALL
- check_cmd: /usr/sbin/visudo -cf
- require:
- user: demo_user
Why this is good: values come from pillar (per-environment override), check_cmd guards against malformed sudoers, require: ensures the user exists before adding rights, and the whole thing is conditional on Linux (in real life with a Jinja {% if grains['kernel'] == 'Linux' %} wrapper).
Pattern 02
Reboot, wait, reconnect — Windows
Reboot a Windows box from Salt and wait for it to come back before continuing the highstate. The "reboot orchestration" pattern that separates production-grade Salt from script-style Salt.
reboot_now:
cmd.run:
- name: shutdown /r /t 2
- shell: cmd
wait_for_winrm:
cmd.run:
- name: |
powershell -ExecutionPolicy Bypass -Command "
$counter = 0
while ($counter -lt 30) {
if (Test-NetConnection -ComputerName $env:COMPUTERNAME `
-Port 5985 -InformationLevel Quiet) { exit 0 }
Start-Sleep -Seconds 10
$counter++
}
exit 1"
- shell: cmd
- require:
- cmd: reboot_now
post_reboot_smoke_test:
cmd.run:
- name: powershell -Command "Get-Service salt-minion"
- shell: cmd
- require:
- cmd: wait_for_winrm
Why this works: WinRM (port 5985) coming back is a reliable signal that the box is genuinely up — services started, network up, ready to take work. The PowerShell loop polls for 5 minutes max, fails fast if the box doesn't come back. Subsequent states require: the wait, so the highstate doesn't try to do work on a half-booted box.
Pattern 03
Multi-host orchestration — RDS-style
One salt-run state.orch command, deploying state to multiple groups of minions in dependency order. This is the pattern for anything that involves "deploy A on these boxes, then B on those, then C on the others." Used here for a Remote Desktop Services build (license server first, then session hosts, then app publishing).
deploy_broker_gateway:
salt.state:
- tgt: 'G@server_role:rds_broker'
- tgt_type: compound
- sls: windows.rds.broker_gateway
deploy_license_server:
salt.state:
- tgt: 'G@server_role:rds_license'
- tgt_type: compound
- sls: windows.rds.license_server
deploy_session_hosts:
salt.state:
- tgt: 'G@server_role:rds_session_host'
- tgt_type: compound
- sls: windows.rds.session_host
- require:
- salt: deploy_broker_gateway
- salt: deploy_license_server
publish_apps:
salt.function:
- tgt: 'G@server_role:rds_broker'
- tgt_type: compound
- name: cmd.run
- arg:
- powershell -File C:\scripts\publish_apps.ps1
- require:
- salt: deploy_session_hosts
Why this works: it replaces the usual Bash-plus-Ansible kludge with sleeps and pings. salt.state applies states to grain-targeted groups; salt.function runs ad-hoc functions; require: chains them. Re-run any time; only unconverged parts do work.
Pattern 04
Reactor — event-driven Salt
Salt has an event bus. Anything happening on the master or minions fires an event. Reactors map those event tags to state files, so the fleet reacts to events automatically rather than waiting for someone to invoke a state.
reactor:
- 'orchestrate/rds/start':
- /srv/salt/reactor/start_rds_build.sls
- 'salt/minion/*/start':
- /srv/salt/reactor/welcome_new_minion.sls
start_rds_build:
runner.state.orchestrate:
- mods: orch.rds_build
Fire the event from anywhere — a CI pipeline, another minion, a webhook handler — and the orchestration kicks off automatically:
salt-call event.send 'orchestrate/rds/start'
Why this matters: blue/green deploys, automated remediation, scheduled builds — they all live here. The install guide's blue/green pattern is essentially this: an event fires, a reactor responds, an orchestration runs.