The fastest way to build confidence with a new configuration management tool is to work directly with its command-line interface. This tutorial walks through practical SaltStack (Salt) commands covering ad hoc task execution, software installation and removal, custom repository configuration, and writing custom grains in Python.
Before following along, ensure Salt is installed on at least one master and one minion, and that you have a basic understanding of core Salt concepts: minions (managed nodes), pillars (secure configuration data), and grains (system properties). The examples reference server names walkerrowe.copahost.com and eurovps — substitute these with your own server names.
Start Salt on the Master and Minion
Start the Salt services on the master and minion nodes respectively:
systemctl start salt-master
systemctl start salt-minion
While the examples below work with a single server, setting up at least two servers gives you a more realistic sense of how Salt behaves in production environments.
Verify Minion Connectivity with test.ping
The Salt test.ping command confirms that a minion is reachable and responsive. Note that this is not equivalent to the Linux ping utility — it does not send ICMP packets. Instead, it verifies that the Salt minion process is running and can communicate with the master.
salt '*' test.pingCode language: JavaScript (javascript)
A healthy minion returns True. The '*' glob targets all connected minions.
Run Ad Hoc Commands with cmd.run
Salt can execute arbitrary shell commands across minions without needing a state file. Use cmd.run to check disk usage on all minions:
salt '*' cmd.run 'df -h'Code language: JavaScript (javascript)
The '*' wildcard targets minions by hostname. Salt also supports targeting by grain values, node groups, compound matchers, and other methods — giving you fine-grained control over which nodes receive a command.
Install and Remove Software
Salt manages software installation declaratively through state files. The two key files are top.sls (which maps minions to state files) and individual state files such as webserver.sls.
Configure the Top File
The top file at /srv/salt/top.sls defines which states apply to which minions. A basic example that applies a webserver state to all minions looks like this:
base:
'*':
- webserverCode language: JavaScript (javascript)
Write a State File to Install Apache
Create /srv/salt/webserver.sls to install the Apache HTTP server using Salt’s pkg module. Both files use YAML format with two-space indentation:
apache2:
pkg:
- installed
These state files collectively define highstate — the desired configuration that minions should reach when state.highstate is applied. Apply highstate to a specific minion:
salt 'walkerrowe.copahost.com' state.highstateCode language: JavaScript (javascript)
Salt installs Apache and reports a summary of changes made, resources checked, and whether any failures occurred.
Remove a Package with pkg.purged
To remove Apache and its configuration files, update webserver.sls to use the purged state. The dot notation pkg.purged is shorthand that calls the purged function — it removes both the package binary and its configuration files:
apache2:
pkg.purgedCode language: CSS (css)
Run highstate again to apply the removal:
salt 'walkerrowe.copahost.com' state.highstateCode language: JavaScript (javascript)
Use Jinja Templating for OS-Conditional Logic
Salt state files support Jinja2 templating, which you can use to apply different configurations based on grain values such as the OS distribution. The following example installs Apache on Ubuntu and Nginx on CentOS:
{% if grains['osfinger'] == 'Ubuntu-14.04' %}
apache2:
pkg:
- installed
{% elif grains['osfinger'] == 'CentOS-6' %}
nginx:
pkg:
- installed
{% endif %}Code language: PHP (php)
The grains['osfinger'] value combines the OS name and major version (e.g., Ubuntu-20.04). Always verify the exact grain value on your systems using salt-call grains.item osfinger.
Install a Package from a Custom Repository
Salt can manage package repository definitions through the pkgrepo state module. This example adds the Kubernetes repository on a CentOS minion, then targets by grain (OS property) rather than hostname.
Update /srv/salt/top.sls to match on the os grain:
base:
'os:CentOS':
- match: grain
- kubernetesCode language: JavaScript (javascript)
Create /srv/salt/kubernetes.sls to define the repository using Salt’s pkgrepo.managed state (this example targets yum-based systems):
base:
pkgrepo.managed:
- name: kubernetes
- humanname: kubernetes
- baseurl: http://yum.kubernetes.io/repos/kubernetes-el7-x86_64
- file: /etc/yum.repos.d/kubernetes.repo
- key_url: https://packages.cloud.google.com/yum/doc/yum-key.gpgCode language: JavaScript (javascript)
Run state.highstate and then verify the repository file was created on the minion:
ls /etc/yum.repos.d/kubernetes.repo
/etc/yum.repos.d/kubernetes.repo
For apt-based systems such as Ubuntu or Debian, consult the Salt pkg state documentation for the equivalent configuration using pkgrepo.managed with apt parameters.
Write a Custom Grain
Grains are system properties that Salt collects automatically (OS, CPU architecture, IP addresses, etc.). You can extend grains by writing custom Python modules. This example creates a grain that exposes memory usage statistics from the free command.
Note: This grain module is written for Ubuntu. The output format of free can differ between Linux distributions and versions, so the index positions used to parse the output may need adjustment on other systems.
Create the Grain Module
Create the grains directory on the Salt master and open a new Python file:
mkdir /srv/salt/_grains
vim /srv/salt/_grains/fmem.py
Add the following Python code. The function calls free, splits the output into tokens, and maps specific token positions to grain keys:
import subprocess
def memf():
x = subprocess.check_output(["free"])
y = x.split()
grains = {}
grains['totalmem'] = y[7]
grains['usedmem'] = y[8]
grains['freemem'] = y[9]
return grainsCode language: JavaScript (javascript)
Sync and Verify the Grain
Push the custom grain module to the minion using saltutil.sync_grains. The salt-call command runs Salt locally on the minion you are logged into:
salt-call saltutil.sync_grainsCode language: CSS (css)
Salt should confirm the grain was synced:
local:
- grains.fmemCode language: CSS (css)
You can then retrieve the custom grain values:
salt-call grains.item totalmem usedmem freememCode language: CSS (css)
The output will include the three custom memory grain keys alongside Salt’s standard grain output.
Next Steps
The commands covered here represent a cross-section of what Salt can do: executing ad hoc remote commands, applying declarative state management, managing package repositories by grain targeting, and extending Salt’s inventory system with custom grains. As you grow more comfortable with these primitives, explore Salt’s pillar system for secure data management, Salt orchestration for multi-node workflows, and the Salt event bus for reactive automation.
Teams evaluating Salt should also compare the open-source and enterprise (Salt, formerly SaltStack Enterprise) editions, and benchmark Salt’s approach against alternatives such as Ansible, Chef, and Puppet to determine the best fit for their infrastructure scale and workflow requirements.