cdalvaro/docker-salt-master
cdalvaro/docker-salt-master has 159 GitHub stars and 32 forks. GitHot measured 0 stars in 24 hours, 0 in 7 days, and 0 in 15 days. Last checked 2026-11-09 19:53 UTC. Last push 2026-10-30.
🐳🧂 Dockerized Salt Master - Boost your @saltstack master service with the power of @docker
Measured star growth
- in 24h
- 0
- in 7 days
- 0
- in 15 days
- 0
- in 1 month
- Collecting history
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
OriginalDockerized Salt Master
Other languages: 🇪🇸 Español
Dockerfile to build a Salt Project Master image for the Docker open source container platform.
salt-master is installed inside the image using the Salt Project repositories for Ubuntu as documented in the official documentation.
For other methods to install salt-master, please refer to
the Salt install guide.
🐳 Installation
Container Registries
Recommended
Automated builds of the image are available on GitHub Container Registry and is the recommended method of installation.
docker pull ghcr.io/cdalvaro/docker-salt-master:3008.5
You can also pull the latest tag, which is built from the repository HEAD
docker pull ghcr.io/cdalvaro/docker-salt-master:latest
Other Registries
These images are also available from:
- AWS ECR:
public.ecr.aws/cdalvaro/docker-salt-master - Docker Registry:
docker.io/cdalvaro/docker-salt-master - Quay.io:
quay.io/cdalvaro/docker-salt-master
Long Term Support
The current LTS (Long Term Support) Salt version is also available through the explicit version tag.
docker pull ghcr.io/cdalvaro/docker-salt-master:3008.5
There are also specific tags for LTS and STS versions:
ghcr.io/cdalvaro/docker-salt-master:ltsghcr.io/cdalvaro/docker-salt-master:sts
[!NOTE] The
stsimage tracks the latest Short Term Support release of Salt.
Available Tags
latest3008.5,lts3007.17,sts
All versions have their SaltGUI counterparts:
latest-gui3008.5-gui,lts-gui3007.17-gui,sts-gui
Build From Source
Alternatively, you can build the image locally using make command:
make release
🚀 Quick Start
The quickest way to get started is using docker compose.
wget https://raw.githubusercontent.com/cdalvaro/docker-salt-master/master/compose.yml
Start the docker-salt-master container with the compose.yml file by executing:
docker compose up --detach
Alternatively, you can manually launch the docker-salt-master container:
docker run --name salt_master --detach \
--publish 4505:4505 --publish 4506:4506 \
--env 'SALT_LOG_LEVEL=info' \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
ghcr.io/cdalvaro/docker-salt-master:latest
⚙️ Configuration
Custom Configuration
This image uses its own master.yml file to configure salt-master to run properly inside the container. However, you
can still tune other configuration parameters to fit your needs by adding your configuration files inside a config/
directory and mounting it into /home/salt/data/config/.
For example, you can customize the Reactor System by
adding a reactor.conf file to config/:
# config/reactor.conf
reactor: # Master config section "reactor"
- 'salt/minion/*/start': # Match tag "salt/minion/*/start"
- /home/salt/data/config/reactor/start.sls # Things to do when a minion starts
Then, you have to add the start.sls file into your config/reactor/ directory:
# config/reactor/start.sls
highstate_run:
local.state.apply:
- tgt: {{ data['id'] }}
Finally, run your docker-salt-master instance mounting the required directories:
docker run --name salt_master -d \
--publish 4505:4505 --publish 4506:4506 \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
--volume $(pwd)/config/:/home/salt/data/config/ \
ghcr.io/cdalvaro/docker-salt-master:latest
This image provides support for automatically restart salt-master when configuration files change.
This support is disabled by default, but it can be enabled by setting the
SALT_RESTART_MASTER_ON_CONFIG_CHANGE environment variable to True.
Custom States
In order to provide salt with your custom states, you must bind the volume /home/salt/data/srv/ to your roots directory.
Minion Keys
Minion keys can be added automatically on startup to docker-salt-master by mounting the volume /home/salt/data/keys
and copying the minion keys inside keys/minions/ directory.
Note: It is recommended to mount this directory to a named volume or a host directory. That way, you can manage your keys outside the container and avoid losing them when the container is removed.
mkdir -p keys/minions
rsync root@minion1:/etc/salt/pki/minion/minion.pub keys/minions/minion1
docker run --name salt_master -d \
--publish 4505:4505 --publish 4506:4506 \
--env 'SALT_LOG_LEVEL=info' \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
--volume $(pwd)/config/:/home/salt/data/config/ \
ghcr.io/cdalvaro/docker-salt-master:latest
Also, you can set your docker-salt-master instance to auto accept minions that match certain grains. To do that, add
the autosign_grains.conf to your config directory:
# config/autosign_grains.conf
autosign_grains_dir: /home/salt/data/srv/autosign_grains
Then, inside roots/autosign_grains you can place a file named like the grain you want to match and fill it with the
content to match. For example, if you want to auto accept minions that belong to specific domains, you have to add
the domain file with the domains you want to allow:
# roots/autosign_grains/domain
cdalvaro.io
cdalvaro.com
It is possible that you have to configure the minion to send the specific grains to the master in the minion config file:
# minion: /etc/salt/minion.d/autosign_grains.conf
autosign_grains:
- domain
More info at: Salt Project - Auto accept Minions From Grains
Master Signed Keys
It is possible to use signed master keys by establishing the environment variable SALT_MASTER_SIGN_PUBKEY to True.
docker run --name salt_master --detach \
--publish 4505:4505 --publish 4506:4506 \
--env 'SALT_LOG_LEVEL=info' \
--env 'SALT_MASTER_SIGN_PUBKEY=True' \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
ghcr.io/cdalvaro/docker-salt-master:latest
The container will create the master_sign key and its signature. More information about how to configure the minion
service can be
found here
.
Additionally, you can generate new signed keys for your existing master key by executing the following command:
docker run --name salt_master -it --rm \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
ghcr.io/cdalvaro/docker-salt-master:latest \
app:gen-signed-keys
The newly created keys will appear inside keys/generated/master_sign.XXXXX directory.
Where XXXXX is a random code to avoid possible collisions with previous generated keys.
Working with Secrets
[!IMPORTANT] Change introduced in 3008.0 — please read if you use secrets.
Up to Salt 3007, master keys provided via secrets were symlinked into the keys directory, so the private material never left
/run/secretseven if the whole keys volume was bind-mounted.Salt 3008.0 changed how the master loads its keys: the new
localfs_keycache driver rejects symlinked key files. To remain compatible, secret keys are now copied into the keys directory (/home/salt/data/keys) as regular files.Consequence: if you both provide keys via secrets and bind-mount the full keys volume (
/home/salt/data/keys), the master private key will be copied onto that persisted volume and is therefore more exposed than before. To keep the private material off persistent storage, when using secrets it is recommended to NOT bind-mount the whole keys volume. Mount only the minion key sub-directories you actually need to persist — usually just the accepted minions:
/home/salt/data/keys/minions— accepted minion keys (most common)/home/salt/data/keys/minions_rejected— rejected minion keys (optional)/home/salt/data/keys/minions_pre,minions_denied,minions_autosign— optionalThese are bind-mounted directories (not symlinks), so Salt 3008.0 accepts them while the master keys stay on the container's ephemeral layer, recreated from the secrets on every start.
Migration / safety net: on upgrade, any pre-existing symlinked master key is automatically replaced by a copy of the secret. If a master key is already present as a regular file (e.g. you mounted the full keys volume), it is not overwritten: it is validated against the secret and, on mismatch, a
WARNis logged — the on-disk key wins and the secret is ignored.
Master keys can be provided via Docker secrets. To do that, you have to set the following environment variable:
SALT_MASTER_KEY_FILE: The path to the master-key-pair {pem,pub} files without suffixes.
Additionally, you can provide the master-sign key pair as well:
SALT_MASTER_SIGN_KEY_FILE: The path to the master-sign-key-pair {pem,pub} files without suffixes.SALT_MASTER_PUBKEY_SIGNATURE_FILE: The path of the salt-master public key file with the pre-calculated signature.
Here you have a complete compose.yml example
services:
salt-master:
image: ghcr.io/cdalvaro/docker-salt-master:latest
ports:
- "4505:4505"
- "4506:4506"
volumes:
- ./config:/home/salt/data/config
secrets:
- source: salt-master-key
target: master.pem
uid: 1000 # Or $PUID if env variable established
gid: 1000 # Or $PGID if env variable established
mode: 0400
- source: salt-master-pub
target: master.pub
uid: 1000 # Or $PUID if env variable established
gid: 1000 # Or $PGID if env variable established
mode: 0644
- source: salt-master-sign-priv-key
target: master_sign.pem
uid: 1000 # Or $PUID if env variable established
gid: 1000 # Or $PGID if env variable established
mode: 0400
- source: salt-master-sign-pub-key
target: master_sign.pub
uid: 1000 # Or $PUID if env variable established
gid: 1000 # Or $PGID if env variable established
mode: 0644
- source: salt-master-signature
target: master_pubkey_signature
uid: 1000 # Or $PUID if env variable established
gid: 1000 # Or $PGID if env variable established
mode: 0644
environment:
SALT_MASTER_SIGN_PUBKEY: True
SALT_MASTER_KEY_FILE: /run/secrets/master
SALT_MASTER_SIGN_KEY_FILE: /run/secrets/master_sign
SALT_MASTER_PUBKEY_SIGNATURE_FILE: /run/secrets/master_pubkey_signature
secrets:
salt-master-pem-key:
file: keys/master.pem
salt-master-pub-key:
file: keys/master.pub
salt-master-sign-priv-key:
file: keys/master_sign.pem
salt-master-sign-pub-key:
file: keys/master_sign.pub
salt-master-signature:
file: keys/master_pubkey_signature
Note that the example above intentionally does not bind-mount the whole keys volume. To persist accepted minion keys across container recreation while keeping the master private key off persistent storage, mount only the minion sub-directory:
volumes:
- ./config:/home/salt/data/config
- ./keys/minions:/home/salt/data/keys/minions
Salt API
You can enable salt-api service by setting env variable SALT_API_ENABLED to True.
A self-signed SSL certificate will be automatically generated and the following configuration will be added to the master configuration file:
rest_cherrypy:
port: 8000
ssl_crt: /etc/pki/tls/certs/docker-salt-master.crt
ssl_key: /etc/pki/tls/certs/docker-salt-master.key
The container exposes port 8000 by default, although you can map this port to whatever port you like in
your docker run command:
docker run --name salt_master --detach \
--publish 4505:4505 --publish 4506:4506 --publish 8000:8000 \
--env 'SALT_API_ENABLED=True' \
--env 'SALT_API_USER_PASS=4wesome-Pass0rd' \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
--volume $(pwd)/config/:/home/salt/data/config/ \
ghcr.io/cdalvaro/docker-salt-master:latest
If you choose using the compose.yml file to manage your salt-master instance, uncomment salt-api
settings to enable and configure the service.
By default, user salt_api is created, and you can set its password by setting the environment
variable SALT_API_USER_PASS.
You can also change the salt-api username by setting SALT_API_USER. It is possible to disable this user by
explicitly setting this variable to an empty string: SALT_API_USER='' if you are going to use an LDAP server.
As a security measure, if SALT_API_ENABLED is set to True and you don't disable SALT_API_USER, you'll be
required to set SALT_API_USER_PASS. Otherwise, the setup process will fail and your container won't work.
SALT_API_USER_PASS_FILE env variable is available to provide the password via a file. This is useful when using Docker
secretes. More info about how to configure secrets can be found in the subsection
Working with secrets.
With all that set, you'll be able to provide your salt-api custom configuration by creating the salt-api.conf file
inside your conf directory.
External Authentication
Here is an example of giving permission to the salt_api user via pam:
external_auth:
pam:
salt_api:
- .*
- "@runner"
- "@wheel"
- "@jobs"
You can specify different authentication methods, as well as specifying groups (by adding a % to the name). For example to authenticate the admins group via LDAP:
external_auth:
ldap:
admins%:
- .*
- "@runner"
- "@wheel"
- "@jobs"
LDAP Configuration
To authenticate via LDAP you will need to configure salt to access your LDAP server. The following example authenticates API logins against the LDAP server. It then defines group configuration for external_auth searches (looking up the user's group membership via memberOf attributes in their person object):
auth.ldap.uri: ldaps://server.example.com # Your LDAP server
auth.ldap.basedn: "dc=server,dc=example,dc=com" # Search base DN (subtree scope).
auth.ldap.binddn: "uid={{ username }},dc=server,dc=exam,ple,dc=com" # The DN to authenticate as (username is substituted from the API authentication information).
auth.ldap.accountattributename: "uid" # The user account attribute type
auth.ldap.groupou: "" # Must be set to an empty string if not in use
auth.ldap.groupclass: "person" # The object class to look at when checking group membership
auth.ldap.groupattribute: "memberOf" # The attribute in that object to look at when checking group membership
Finally (since v3006) you will need to enable one or more client interfaces:
netapi_enable_clients:
- local
Details of all client interfaces is available at the following link: Netapi Client Interfaces
More information is available in the following link: External Authentication System (eAuth).
Now you have your docker-salt-master Docker image ready to accept external authentications and to connect external tools
such as saltstack/pepper.
Salt Pepper
The pepper CLI script allows users to execute Salt commands from computers that are external to computers running the salt-master or salt-minion daemons as though they were running Salt locally.
Installation
pip3 install salt-pepper
Configuration
Then configure pepper by filling your ~/.pepperrc file with your salt-api credentials:
[main]
SALTAPI_URL=https://your.salt-master.hostname:8000/
SALTAPI_USER=salt_api
SALTAPI_PASS=4wesome-Pass0rd
SALTAPI_EAUTH=pam
Usage
Begin executing salt states with pepper:
pepper '*' test.ping
Salt Minion
This image contains support for running a built-in salt-minion service.
You can enable it by setting the environment variable SALT_MINION_ENABLED to True.
The salt-minion will be automatically accepted by the master. Keys will be automatically configured,
even if SALT_MASTER_SIGN_PUBKEY=True.
However, minion keys can be provided via Docker secrets.
To do that, you have to set the env variable SALT_MINION_KEY_FILE,
pointing to the path inside the container of the minion-key-pair {pem,pub} files without extensions.
Minion's keys will be stored inside the keys/SALT_MINION_ID/ directory.
This minion can be configured in the same way as the master.
You can add your custom configuration files inside a minion_config/ directory
and mount it into /home/salt/data/minion_config/.
The default id of the minion is builtin.minion.
But you can change it by setting the environment variable SALT_MINION_ID.
Log levels are the same as the master,
and you can set them by using the SALT_LOG_LEVEL and SALT_LEVEL_LOGFILE environment variables.
Here you have an example of how to run a salt-master with a built-in salt-minion:
docker run --name salt_master --detach \
--publish 4505:4505 --publish 4506:4506 \
--env 'SALT_MINION_ENABLED=True' \
--env 'SALT_MINION_ID=control-minion' \
--env 'SALT_MASTER_SIGN_PUBKEY=True' \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
--volume $(pwd)/config/:/home/salt/data/config/ \
--volume $(pwd)/minion_config/:/home/salt/data/minion_config/ \
ghcr.io/cdalvaro/docker-salt-master:latest
Salt SSH
This image includes salt-ssh, to run Salt commands
and states on hosts over SSH, without installing salt-minion on them. Target hosts only need an SSH server and
python3 (and passwordless sudo if you use sudo: True).
Add your hosts to a roster file named roster,
inside a salt-ssh/ directory:
# salt-ssh/roster
web1:
host: 192.168.1.10
user: root
db1:
host: db1.example.com
user: deploy
sudo: True
Mount this directory into /home/salt/data/salt-ssh/ (you can change this path with SALT_SSH_DIR):
docker run --name salt_master --detach \
--publish 4505:4505 --publish 4506:4506 \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
--volume $(pwd)/config/:/home/salt/data/config/ \
--volume $(pwd)/salt-ssh/:/home/salt/data/salt-ssh/:ro \
ghcr.io/cdalvaro/docker-salt-master:latest
The first time, deploy the salt-ssh key to each host. It asks for the host password once
(-i accepts the host key):
docker exec -it --user salt salt_master salt-ssh -i --key-deploy --askpass web1 test.ping
This trusts the host key of this first connection without verifying it. To avoid that, add the verified host keys to
known_hosts beforehand (see Host Keys) and run this command without -i, or add
keys/ssh/salt-ssh.rsa.pub to your hosts by other means (salt-ssh creates it the first time it runs).
From then on, salt-ssh authenticates with its key:
docker exec --user salt salt_master salt-ssh '*' test.ping
docker exec --user salt salt_master salt-ssh web1 state.apply
[!IMPORTANT] Always run
salt-sshas thesaltuser (docker exec --user salt), so the files it creates are owned bysalt. Its key (salt-ssh.rsa) and the known host keys (known_hosts) are stored inkeys/ssh/: keep this directory in a volume to reuse them when the container is recreated.
salt-ssh logs are written to logs/salt/ssh.
Salt SSH Configuration
salt-ssh reads the salt-master configuration, so you can configure it with an ssh.conf file inside your config/
directory (see Custom Configuration). For example, to set default values for all the hosts:
# config/ssh.conf
roster_defaults:
user: deploy
sudo: True
All the options are listed in the
master configuration reference.
Don't override roster_file or rosters: set SALT_SSH_DIR to change their location instead.
Host Keys
salt-ssh checks the host keys of the targets against keys/ssh/known_hosts, so the first connection to a host fails
with The host key needs to be accepted. Run salt-ssh once with -i (--ignore-host-keys) to accept and store
its key:
docker exec --user salt salt_master salt-ssh -i web1 test.ping
To verify the host key instead of trusting the first connection, add it to known_hosts beforehand: scan the host
of the roster, and check that its fingerprints match the ones you get from the host through a trusted channel:
ssh-keyscan 192.168.1.10 > web1.keys
ssh-keygen -lf web1.keys # Add the keys only if these fingerprints match
cat web1.keys >> keys/ssh/known_hosts
[!WARNING]
-i,--key-deployand roster entries that only use a password (priv: null) accept new host keys without checking them, and-ialso accepts keys that have changed. Use-ionly for the first connection to a host.
To store known_hosts somewhere else, set UserKnownHostsFile in ssh_options (roster entries with their own
ssh_options replace this list). For example, next to your roster:
# config/ssh.conf
ssh_options:
- UserKnownHostsFile=/home/salt/data/salt-ssh/known_hosts
Private Keys from Docker Secrets
Instead of the generated key, you can set the private key of a host with the priv option of the roster
(or of roster_defaults, for all the hosts). For example, with a Docker secret:
# salt-ssh/roster
web1:
host: 192.168.1.10
user: root
priv: /run/secrets/salt-ssh-key
# compose.yml
services:
salt-master:
image: ghcr.io/cdalvaro/docker-salt-master:latest
secrets:
- source: salt-ssh-key
uid: 1000 # Or $PUID if env variable established
gid: 1000 # Or $PGID if env variable established
mode: 0600
secrets:
salt-ssh-key:
file: ./secrets/salt-ssh-key
Docker Compose mounts the key with the same owner and permissions it has on the host, so it must be owned by the salt
user (uid 1000, or the one set in PUID) and have 600 permissions. Add its public key to the authorized_keys
file of your hosts beforehand.
If you deploy this service with Docker Swarm (docker stack deploy), you can use the long secret syntax to set its uid,
gid, and mode inside the container. With docker compose up and a file-backed secret, those settings are ignored;
set the source file’s ownership and permissions on the host instead.
Using salt-ssh from Salt API
To run salt-ssh through salt-api, enable its ssh client:
# config/salt-api.conf
netapi_enable_clients:
- local
- ssh
Requests use the main roster, or a roster of salt-ssh/roster.d/ set with roster_file.
For example, to run test.ping on every host of roster.d/production:
curl -sSk https://localhost:8000/run \
-H 'Accept: application/x-yaml' \
-d client=ssh \
-d tgt='*' \
-d fun=test.ping \
-d roster_file=production \
-d username=salt_api \
-d password=4wesome-Pass0rd \
-d eauth=pam
The hosts must already be in known_hosts.
[!WARNING]
roster.d/does not restrict which rosters each API user can use, so enable thesshclient only if you trust all your API users.
Targets with Other Python Versions
salt-ssh sends the Python modules of salt-master to the targets, and some of them may not work with older Python
versions. For these targets, set SALT_SSH_PYTHON_VERSIONS with their Python versions (e.g. 3.9, or 3.9.20 for
an exact version). When the container starts, it installs them with uv at
/opt/salt-ssh/python<MAJOR.MINOR>, with the Python modules pinned by the lock files of its Salt version.
Add this option to the docker run command above:
--env 'SALT_SSH_PYTHON_VERSIONS=3.9' \
Then, add an ssh_ext_alternatives
entry to your config/ssh.conf file:
# config/ssh.conf
ssh_ext_alternatives:
python3.9: # Namespace: any name
py-version: [3, 9]
path: /opt/salt-ssh/salt # The salt package of salt-master. It is an alias to the 'onedir' instalation.
auto_detect: True
py_bin: /opt/salt-ssh/python3.9/bin/python-isolated
Keep in mind that:
- You can only set the Python versions supported by the Salt version of this image (the ones in
/opt/salt-ssh/locks), except the Python version ofsalt-master. py_binmust bebin/python-isolated, notbin/python, so it doesn't load the Python modules ofsalt-master.- Run
salt-sshwith--regen-thin(-t) after changingssh_ext_alternatives. - The container needs access to the Internet to install the Python versions each time it is created (restarts reuse them).
- You can ignore the
Could not auto detect file location for module ...errors about legacy modules, likesingledispatchorssl_match_hostname.
Host Mapping
By default, the container is configured to run salt-master as user and group salt with uid and gid 1000. From
the host the mounted data volumes will be shown as owned by user:group 1000:1000. This can be a problem if the host's id is different from 1000 or if files have too restrictive permissions. Specially the keys directory and its contents.
Also, the container processes seem to be executed as the host's user/group 1000. To avoid this, the container can be configured to
map
the uid and gid to match host ids by passing the environment variables PUID and PGID. The following
command maps the ids to the current user and group on the host.
docker run --name salt_master -it --rm \
--publish 4505:4505 --publish 4506:4506 \
--env "PUID=$(id -u)" --env "PGID=$(id -g)" \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
ghcr.io/cdalvaro/docker-salt-master:latest
Git Fileserver
This image uses PyGit2 as gitfs backend to allow Salt to serve files from git repositories.
It can be enabled by adding gitfs to
the fileserver_backend
list (see Available Configuration Parameters), and configuring one or more
repositories
in gitfs_remotes.
[!NOTE] Sometimes, the
salt-masterprocess may restart automatically. If this happens while gitfs is updating repositories, lock files may remain undeleted, preventing gitfs from properly updating the repositories.The simple solution is to restart the container to clear the cache. However, if this happens frequently, and
salt-masteris not operating in multimaster mode with shared cache (for example using Ceph, or nfs), then it may be useful to set these two configuration parameters toFalse:gitfs_global_lock: False git_pillar_global_lock: False
PyGit2
You can create an ssh key for pygit2 with the following command:
ssh-keygen -t ed25519 -C -f gitfs_ssh -C '[email protected]'
Place it wherever you want inside the container and specify its path with the configuration parameters: gitfs_pubkey
and gitfs_privkey in your gitfs.conf file.
For example:
# config/gitfs.conf
gitfs_provider: pygit2
gitfs_privkey: /home/salt/data/keys/gitfs/gitfs_ssh
gitfs_pubkey: /home/salt/data/keys/gitfs/gitfs_ssh.pub
[!WARNING] This image has been tested with an ed25519 ssh key.
Alternately, you may create a new RSA key with SHA2 hashing like so:
ssh-keygen -t rsa-sha2-512 -b 4096 -f gitfs_ssh -C '[email protected]'
GPG Keys for Renderers
Salt can use GPG keys to decrypt pillar data. This image is ready to import your GPG keys from the gpgkeys directory
inside the keys directory.
The private key must be named private.key and the public key pubkey.gpg.
If you want to provide these keys via secrets, you can set SALT_GPG_PRIVATE_KEY_FILE and SALT_GPG_PUBLIC_KEY_FILE
env variables to specify the path to the files inside the container.
For example:
# compose.yml
services:
salt-master:
...
env:
SALT_GPG_PRIVATE_KEY_FILE: /run/secrets/private.key
SALT_GPG_PUBLIC_KEY_FILE: /run/secrets/pubkey.gpg
In this case, keys will be symlinked to the gpgkeys directory.
It is important that the private key doesn't have passphrase in order to be imported by salt.
To generate a GPG key and export the private/public pair you can use the following commands:
# Generate key - REMEMBER: Leave empty the passphrase!
gpg --gen-key
# Check GPG keys
gpg --list-secret-keys
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2024-11-09
/tmp/gpgkeys/pubring.kbx
--------------------
sec rsa3072 2022-11-10 [SC] [expires: 2024-11-09]
CB032BA54F21722945FDDE399CE3DB8AE37D28B7
uid [ultimate] Carlos Alvaro <[email protected]>
ssb rsa3072 2022-11-10 [E] [expires: 2024-11-09]
# Export public and private keys
mkdir -p keys/gpgkeys
[email protected]
gpg --armor --export "${KEY_ID}" > keys/gpgkeys/pubkey.gpg
gpg --export-secret-keys --export-options export-backup -o keys/gpgkeys/private.key "${KEY_ID}"
More information about this feature is available at the official documentation.
How to Encrypt Data
You can encrypt strings using the following example:
echo -n 'Super secret pillar' | gpg --armor --batch --trust-model always --encrypt --recipient "${KEY_ID}"
Or you can encrypt files using the example bellow:
gpg --armor --batch --trust-model always --encrypt --recipient "${KEY_ID}" \
--output /tmp/gpg_id_ed25519 ~/.ssh/id_ed25519
cat /tmp/gpg_id_ed25519
On macOS, you can pipe the output to pbcopy to copy the encrypted data to the clipboard. If you are using Linux, you
can use xclip or xsel.
3rd Party Formulas
You can add third party formulas to your configuration simply by adding them to your gitfs_remotes:
# fileserver.conf
fileserver_backend:
- roots
- gitfs
# gitfs.conf
gitfs_provider: pygit2
gitfs_remotes:
- https://github.com/saltstack-formulas/apache-formula
- https://github.com/aokiji/salt-formula-helm.git
This is the Salt recommended way of doing it, and you can go to the Git Fileserver section on this document if you need help configuring the service.
You can find a great set of formulas on the following GitHub repositories:
Although, as mention in Salt Project documentation , you are encouraged to fork desired formulas to avoid unexpected changes to your infrastructure.
However, sometimes you may need to load some formulas that are not available on a git repository, and you want to have
them separated from your main srv directory.
For that case, you can mount a volume containing all your third party formulas separated in subdirectories
into /home/salt/data/3pfs/, and they will be automatically added to the master configuration when your container
starts.
# 3pfs directory content
3pfs
├── custom-formula
├── golang-formula
└── vim-formula
docker run --name salt_master -it --rm \
--publish 4505:4505 --publish 4506:4506 \
--env "PUID=$(id -u)" --env "PGID=$(id -g)" \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/3pfs/:/home/salt/data/3pfs/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
ghcr.io/cdalvaro/docker-salt-master:latest
If you need to add more third party formulas, you can restart the container, or you can type the following command:
docker exec -it salt_master /sbin/entrypoint.sh app:reload-3rd-formulas
file_roots base configuration file will be updated with current existing formulas and salt-master service will be
restarted to reload the new configuration.
Python Extra Packages
Some formulas may depend on Python packages that are not included in the default Salt installation. You can add these packages by setting the PYTHON_PACKAGES_FILE environment variable with an absolute path pointing to a requirements.txt file inside the container.
docker run --name salt_master --detach \
--publish 4505:4505 --publish 4506:4506 \
--env SALT_LOG_LEVEL="info" \
--env PYTHON_PACKAGES_FILE=/home/salt/data/other/requirements.txt \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
--volume $(pwd)/requirements.txt:/home/salt/data/other/requirements.txt \
ghcr.io/cdalvaro/docker-salt-master:latest
This will install the packages listed in the requirements.txt file into the container
before salt-master starts.
Alternatively, you can set the PYTHON_PACKAGES environment variable with a list of Python packages to be installed.
docker run --name salt_master --detach \
--publish 4505:4505 --publish 4506:4506 \
--env SALT_LOG_LEVEL="info" \
--env PYTHON_PACKAGES="docker==7.0.0 redis" \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
ghcr.io/cdalvaro/docker-salt-master:latest
Although both methods are supported, they are mutually exclusive. If both are set, PYTHON_PACKAGES_FILE will take precedence.
Logs
salt-master output is streamed directly to the container's stdout and stderr. However, they are also written inside /home/salt/data/logs/.
Inside the directory you can find supervisor/ logs and salt/ logs.
You can access all logs by mounting the volume: /home/salt/data/logs/.
docker run --name salt_master --detach \
--publish 4505:4505 --publish 4506:4506 \
--env 'SALT_LOG_LEVEL=info' \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
ghcr.io/cdalvaro/docker-salt-master:latest
Check Available Configuration Parameters section for configuring logrotate.
Healthcheck
This image includes a health check
script: /usr/local/sbin/healthcheck (although it is disabled by default). It is useful to check if the salt-master
service is alive and responding.
If you are running this image under k8s, you can define a liveness command as explained here .
If you use docker-compose as your container orchestrator, you can add the following entries to your compose file:
services:
master:
container_name: salt_master
image: ghcr.io/cdalvaro/docker-salt-master:latest
healthcheck:
test: ["CMD", "/usr/local/sbin/healthcheck"]
interval: 5m
timeout: 30s
retries: 3
start_period: 1m
start_interval: 30s
(More info available at compose file official documentation)
Or, if you launch your container with docker:
docker run --name salt_master --detach \
--publish 4505:4505 --publish 4506:4506 \
--health-cmd='/usr/local/sbin/healthcheck' \
--health-start-period=30s \
--env 'SALT_LOG_LEVEL=info' \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
ghcr.io/cdalvaro/docker-salt-master:latest
Then you can manually check this info by running the following command:
docker inspect --format "{{json .State.Health }}" salt_master | jq
Then, the output will be something similar to this:
{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "2020-05-23T16:47:55.1046568Z",
"End": "2020-05-23T16:48:02.3381442Z",
"ExitCode": 0,
"Output": ""
}
]
}
If the default timeout for the salt-run jobs.active command (20s) is not sufficient for your configuration,
you can change it by setting the environment variable SALT_HEALTHCHECK_TIMEOUT_JOBS_ACTIVE to a value that
best fits your needs.
Autoheal
If you run your docker-salt-master instance with healthcheck enabled, you can use willfarrell/autoheal image to restart your service when healthcheck fails:
docker run -d \
--name autoheal \
--restart=always \
-e AUTOHEAL_CONTAINER_LABEL=all \
-v /var/run/docker.sock:/var/run/docker.sock \
willfarrell/autoheal
This container will watch your containers and restart your failing instances.
SaltGUI
There is a set of dedicated images tagged with -gui that include built-in support for SaltGUI 1.34.0.
These images have salt-api enabled by default. However, it's up to you to define the permissions granted to the salt-api user. There is more information about permissions in the SaltGUI documentation.
If you are running your salt instance behind a reverse proxy, you may want to disable SSL by setting the environment variable SALT_API_DISABLE_SSL to True.
Below is an example of how to run a container with SaltGUI enabled.
Create a Salt API Configuration File
First, create a configuration file for salt-api. You can use the following example as a starting point:
# config/salt_api.conf
netapi_enable_clients:
- local
- local_async
- local_batch
- local_subset
- runner
- runner_async
external_auth:
pam:
saltgui:
- .*
- "@runner"
- "@wheel"
- "@jobs"
Run the Container
Once your configuration is ready, start the container with the following command:
docker run --name salt_master_gui --detach \
--publish 4505:4505 --publish 4506:4506 --publish 8000:8000 \
--env 'SALT_API_USER=saltgui' \
--env 'SALT_API_USER_PASS=4wesome-Pass0rd' \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/config/:/home/salt/data/config/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
ghcr.io/cdalvaro/docker-salt-master:latest-gui
[!NOTE] The username used in the
external_auth.pamsection (saltgui) must match the value of theSALT_API_USERenvironment variable.
If you plan to use an LDAP authentication backend, refer to the External Authentication section.
Access SaltGUI
Once the container is running, you can access the SaltGUI interface at: https://localhost:8000
Available Configuration Parameters
Please refer the docker run command options for the --env-file flag where you can specify all required environment
variables in a single file. This will save you from writing a potentially long docker run command. Alternatively you can
use docker-compose.
Below you can find a list with the available options that can be used to customize your docker-salt-master
installation.
| Parameter | Description |
|---|---|
DEBUG |
Set this to True to enable entrypoint debugging. |
TIMEZONE / TZ |
Set the container timezone. Defaults to UTC. Values are expected to be in Canonical format. Example: Europe/Madrid. See the list of acceptable values. |
PUID |
Sets the uid for user salt to the specified uid. Default: 1000. |
PGID |
Sets the gid for user salt to the specified gid. Default: 1000. |
PYTHON_PACKAGES |
Contains a list of Python packages to be installed. Default: Unset. |
PYTHON_PACKAGES_FILE |
An absolute path inside the container pointing to a requirements.txt file for installing Python extra packages. Takes preference over: PYTHON_PACKAGES. Default: Unset |
SALT_RESTART_MASTER_ON_CONFIG_CHANGE |
Set this to True to restart salt-master service when configuration files change. Default: False. |
SALT_LOG_LEVEL |
The level of messages to send to the console. One of 'garbage', 'trace', 'debug', info', 'warning', 'error', 'critical'. Default: warning. |
SALT_LOG_ROTATE_FREQUENCY |
Logrotate frequency for salt logs. Available options are 'daily', 'weekly', 'monthly', and 'yearly'. Default: weekly. |
SALT_LOG_ROTATE_RETENTION |
Keep x files before deleting old log files. Defaults: 52. |
SALT_LEVEL_LOGFILE |
The level of messages to send to the log file. One of 'garbage', 'trace', 'debug', info', 'warning', 'error', 'critical'. Default: SALT_LOG_LEVEL. |
SALT_MASTER_KEY_FILE |
The path to the master-key-pair {pem,pub} files without suffixes. Keys will be copied into the pki directory. Useful to load the password from secrets. Unset by default. |
SALT_API_ENABLED |
Enable salt-api service. Default: False. |
SALT_API_USER |
Set username for salt-api service. Default: salt_api. |
SALT_API_USER_PASS_FILE |
SALT_API_USER password file path. Use this variable to set the path of a file containing the password for the SALT_API_USER. Useful to load the password from secrets. Has priority over SALT_API_USER_PASS. Unset by default. |
SALT_API_USER_PASS |
SALT_API_USER password. Required if SALT_API_ENABLED is True, SALT_API_USER is not empty and SALT_API_USER_PASS_FILE is unset. Unset by default. |
SALT_API_CERT_CN |
Common name in the request. Default: localhost. |
SALT_API_DISABLE_SSL |
Disable SSL for the Cherrypy server. Warning! When True, your salt authentication credentials will be sent in the clear. Default: False. |
SALT_MINION_ENABLED |
Enable salt-minion service. Default: False. |
SALT_MINION_ID |
Set the id of the salt-minion service. Default: builtin.minion. |
SALT_MASTER_SIGN_PUBKEY |
Sign the master auth-replies with a cryptographic signature of the master's public key. Possible values: True or False. Default: False. |
SALT_MASTER_USE_PUBKEY_SIGNATURE |
Instead of computing the signature for each auth-reply, use a pre-calculated signature. This option requires SALT_MASTER_SIGN_PUBKEY set to True. Possible values: True or False. Default: True. |
SALT_MASTER_SIGN_KEY_NAME |
The customizable name of the signing-key-pair without suffix. Default: master_sign. |
SALT_MASTER_SIGN_KEY_FILE |
The path to the signing-key-pair {pem,pub} without suffixes. The pair will be copied into the pki directory if they don't exists previously. Useful to load the password from secrets. Unset by default. |
SALT_MASTER_PUBKEY_SIGNATURE |
The name of the file in the master's pki directory that holds the pre-calculated signature of the master's public-key. Default: master_pubkey_signature. |
SALT_MASTER_PUBKEY_SIGNATURE_FILE |
The path of the salt-master public key file with the pre-calculated signature. It will be copied inside the pki directory if a file with name SALT_MASTER_PUBKEY_SIGNATURE doesn't exist. Useful to load the password from secrets. Unset by default. |
SALT_GPG_PRIVATE_KEY_FILE |
The path to the GPG private key for GPG renderers. Useful to load the key from secrets. Unset by default. |
SALT_GPG_PUBLIC_KEY_FILE |
The path to the GPG public key for GPG renderers. Useful to load the key from secrets. Unset by default. |
SALT_REACTOR_WORKER_THREADS |
The number of workers for the runner/wheel in the reactor. Default: 10. |
SALT_WORKER_THREADS |
The number of threads to start for receiving commands and replies from minions. Default: 5. |
SALT_BASE_DIR |
The base path in file_roots to look for salt and pillar directories. Default: /home/salt/data/srv. |
SALT_CONFS_DIR |
The master will automatically include all config files from this directory. Default: /home/salt/data/config. When set to something different than the default value, a symlink is created from SALT_CONFS_DIR to /home/salt/data/config. This is done to simplify the configuration files allowing to use the same configuration files in different containers. |
SALT_SSH_DIR |
Directory with the salt-ssh roster file (roster) and the additional roster files used by salt-api (roster.d/). Default: /home/salt/data/salt-ssh. |
SALT_SSH_PYTHON_VERSIONS |
Space-separated list of Python versions (MAJOR.MINOR or MAJOR.MINOR.PATCH) to install with uv for salt-ssh ssh_ext_alternatives. See Targets with Other Python Versions. Default: Unset. |
Any parameter not listed in the above table and available in the
following link,
can be set by creating the directory config and adding into it a .conf file with the desired parameters:
mkdir config
cat > config/ports.conf << EOF
# The tcp port used by the publisher:
publish_port: 3505
# The port used by the communication interface.
ret_port: 3506
EOF
docker run --name salt_master -d \
--publish 3505:3505 --publish 3506:3506 \
--env 'SALT_LOG_LEVEL=info' \
--volume $(pwd)/roots/:/home/salt/data/srv/ \
--volume $(pwd)/keys/:/home/salt/data/keys/ \
--volume $(pwd)/logs/:/home/salt/data/logs/ \
--volume $(pwd)/config/:/home/salt/data/config/ \
ghcr.io/cdalvaro/docker-salt-master:latest
🧑🚀 Usage
To test which salt minions are listening the following command can be executed directly from the host machine:
docker exec -it salt_master salt '*' test.ping
Then, you can apply salt states to your minions:
docker exec -it salt_master salt '*' state.apply [state]
🧰 Shell Access
For debugging and maintenance purposes you may want to access the container's shell. If you are using docker version 1.3.0 or higher you can access a running container shell using docker exec command.
docker exec -it salt_master bash
💀 Restart Services
You can restart containers services by running the following command:
docker exec -it salt_master entrypoint.sh app:restart [salt-service]
Where salt-service is one of: salt-master or salt-api (if SALT_API_ENABLED is set to True).
🔨 Contributing
Everyone is wellcome to contribute to this project, and I really appreciate your support. So don't be shy!
Before you start making changes, read carefully the following notes in order to avoid issues.
[!WARNING] Some tests start and stop a non-isolated
salt-minioninstance. So don't run tests locally. Tests are automatically executed on GitHub when you push commits to your PR.
👏 Credits
Many thanks to:
- The SaltProject team for the excellent salt project.
- JetBrains for their free OpenSource license.
- The Contributors for all the smart code and suggestions merged in the project.
- The Stargazers for showing their support.
📖 References
📃 License
This project is available as open source under the terms of the MIT License.
Explore related projects
codecrafters-io/build-your-own-x
Master programming by recreating your favorite technologies from scratch.
561,394 starspublic-apis/public-apis
A collective list of free APIs
497,674 starsmorluto/rea
Reverse engineer anything with agents, from app behavior down to native binaries.
447,457 starsopenclaw/openclaw
The AI that really does things. Any OS. Any Platform. The lobster way. 🦞
394,261 stars