05 · Production · ~10 min
Real-world patterns.
Four sanitized patterns from production deployments. Each one is something you'd actually run, distilled to the parts that teach. Steal these.
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 is the killer pattern: Salt is doing what would normally take a Bash-and-Ansible kludge with sleeps and pings — and doing it idempotently. salt.state applies states to grain-targeted groups; salt.function runs ad-hoc functions; require: chains them. Re-run anytime, only the parts that haven't converged actually 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 event tags to state files. This turns Salt from "I tell it to do things" into "it does things when stuff happens."
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.