Sometimes using Salt in master-minion mode is a overkill. Managing a single server can be done in a more simple way. Salt provides masterless mode in which you dont have to use salt master at all.

I keep the server configuration under version control in a git repository. The main Fabric file is located in the project root. Salt states and pillars are stored in the roots directory.

.
├── fabfile.py
├── roots/
│   ├── pillar/
│   │   ├── common.sls
│   │   └── top.sls
│   └── salt/
│       ├── common/
│       └── top.sls
└── templates/
    └── minion

The minion config template is located under the templates directory. It is a Jinja 2 template and we rerender it everytime we apply the configuration to the server. Remember that every Salt operation is running on the remote server and we use Fabric to connect to it.

file_client: local
file_roots:
  base:
    - {{ salt_root }}
pillar_roots:
  base:
    - {{ pillar_root }}

Deploy phase

  1. Connect to the remote server using SSH protocol with Fabric.
  2. Install Salt minion on the remote server.
  3. Copy Salt states and pillars to the remote server.
  4. Write Salt minion configuration file to the remote server.
  5. Call Salt highstate on the remote server to apply the server configuration.

Here is the stripped down Fabric script which i use to manage my server:

import os

from fabric.api import env, run
from fabric.decorators import task
from fabric.contrib.project import rsync_project
from fabric.contrib.files import upload_template, exists

MINION_CONFIG = '/etc/salt/minion'
MINION_BIN = '/usr/bin/salt-minion'

env.hosts = ['xx.xx.xx.xx']
env.user = 'my_user'
env.port = 22
env.root = '/home/my_user/my_root'
env.salt_roots = os.path.join(env.root, 'roots')


def bootstrap_salt():
    """
    Installs Salt minion if not already installed.
    """
    if not exists(MINION_BIN):
        run('wget -O - http://bootstrap.saltstack.org | sudo sh')


def sync_salt_roots():
    """
    Copies states and pillars using rsync to the remote server.
    """
    if not exists(env.salt_roots):
        run('mkdir {0}'.format(env.salt_roots))

    rsync_project(
        local_dir='roots',
        remote_dir=env.root,
        exclude='.git'
    )


def write_salt_minion_config():
    """
    Writes Salt minion config to the remote server.
    """
    context = {
        'salt_root': os.path.join(env.salt_roots, 'salt'),
        'pillar_root': os.path.join(env.salt_roots, 'pillar'),
    }
    upload_template(
        filename='templates/minion',
        destination=MINION_CONFIG,
        context=context,
        use_jinja=True
    )


def provision():
    """
    Calls Salt hightstate on the remote server.
    """
    run('salt-call --local state.highstate -l debug')


@task
def deploy():
    bootstrap_salt()
    sync_salt_roots()
    write_salt_minion_config()
    provision()

To trigger the deployment just run this command from the project root directory:

fab deploy