Introduction
Semaphore is a responsive web UI for running Ansible playbooks, Terraform/OpenTofu and Pulumi code.

Semaphore is written in pure Go and available for Windows, macOS and Linux (x64, ARM, ARM64). Semaphore is an open-source project with concise and high-quality code.
Semaphore supports the following databases:
- MySQL
- PostgreSQL
- BoltDB – embedded key/value database
With Semaphore you can:
- Build, deploy and rollback
- Group playbooks to projects
- Manage environments, inventories, repositories and access keys
- Run playbooks from the browser. Responsive UI allows the use of Semaphore on mobile devices
- Run playbooks by schedule
- View detailed logs of any playbook runs, at any time
- Delegate other users the running of playbooks
- Get notifications about playbook runs
Development roadmap
We are developing Semaphore according to the roadmap.
Links
-
Source code: https://github.com/semaphoreui/semaphore
-
Issue tracking: https://github.com/semaphoreui/semaphore/issues
-
Contact: [email protected]
-
Docker container configurator:
-
Our responsive community:
-
Every day we add new features, fix bugs, support the community. We need your support:
Installation
You can install Semaphore in 4 ways:
See also:
Installing Additional Python Packages
Some Ansible modules and roles require additional python packages to run. To install additional python packages, create a requirements.txt
file and mount it in the /etc/semaphore
directory on the container. For example, you could add the following lines to your docker-compose.yml
file:
volumes:
- /path/to/requirements.txt:/etc/semaphore/requirements.txt
The packages specified in the requirements file will be installed when the container starts up.
For more information about Python requirements files, see the Pip Requirements File Format reference
Package manager
Look into the manual installation on how to set-up your Python/Ansible/Systemd environment!
Download package file from Releases page.
*.deb
for Debian and Ubuntu, *.rpm
for CentOS and RedHat.
Here are several installation commands, depending on the package manager:
wget https://github.com/semaphoreui/semaphore/releases/\
download/v2.12.4/semaphore_2.12.4_linux_amd64.deb
sudo dpkg -i semaphore_2.12.4_linux_amd64.deb
Setup Semaphore by using the following command:
semaphore setup
Now you can run Semaphore:
semaphore server --config=./config.json
Semaphore will be available via this URL https://localhost:3000.
Docker
Create a docker-compose.yml
file with following content:
services:
# uncomment this section and comment out the mysql section to use postgres instead of mysql
#postgres:
#restart: unless-stopped
#image: postgres:14
#hostname: postgres
#volumes:
# - semaphore-postgres:/var/lib/postgresql/data
#environment:
# POSTGRES_USER: semaphore
# POSTGRES_PASSWORD: semaphore
# POSTGRES_DB: semaphore
# if you wish to use postgres, comment the mysql service section below
mysql:
restart: unless-stopped
image: mysql:8.0
hostname: mysql
volumes:
- semaphore-mysql:/var/lib/mysql
environment:
MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
MYSQL_DATABASE: semaphore
MYSQL_USER: semaphore
MYSQL_PASSWORD: semaphore
semaphore:
restart: unless-stopped
ports:
- 3000:3000
image: semaphoreui/semaphore:latest
environment:
SEMAPHORE_DB_USER: semaphore
SEMAPHORE_DB_PASS: semaphore
SEMAPHORE_DB_HOST: mysql # for postgres, change to: postgres
SEMAPHORE_DB_PORT: 3306 # change to 5432 for postgres
SEMAPHORE_DB_DIALECT: mysql # for postgres, change to: postgres
SEMAPHORE_DB: semaphore
SEMAPHORE_PLAYBOOK_PATH: /tmp/semaphore/
SEMAPHORE_ADMIN_PASSWORD: changeme
SEMAPHORE_ADMIN_NAME: admin
SEMAPHORE_ADMIN_EMAIL: admin@localhost
SEMAPHORE_ADMIN: admin
SEMAPHORE_ACCESS_KEY_ENCRYPTION: gs72mPntFATGJs9qK0pQ0rKtfidlexiMjYCH9gWKhTU=
SEMAPHORE_LDAP_ACTIVATED: 'no' # if you wish to use ldap, set to: 'yes'
SEMAPHORE_LDAP_HOST: dc01.local.example.com
SEMAPHORE_LDAP_PORT: '636'
SEMAPHORE_LDAP_NEEDTLS: 'yes'
SEMAPHORE_LDAP_DN_BIND: 'uid=bind_user,cn=users,cn=accounts,dc=local,dc=shiftsystems,dc=net'
SEMAPHORE_LDAP_PASSWORD: 'ldap_bind_account_password'
SEMAPHORE_LDAP_DN_SEARCH: 'dc=local,dc=example,dc=com'
SEMAPHORE_LDAP_SEARCH_FILTER: "(\u0026(uid=%s)(memberOf=cn=ipausers,cn=groups,cn=accounts,dc=local,dc=example,dc=com))"
TZ: UTC
depends_on:
- mysql # for postgres, change to: postgres
volumes:
semaphore-mysql: # to use postgres, switch to: semaphore-postgres
You must specify following confidential variables:
MYSQL_PASSWORD
andSEMAPHORE_DB_PASS
— password for the MySQL user.SEMAPHORE_ADMIN_PASSWORD
— password for the Semaphore's admin user.SEMAPHORE_ACCESS_KEY_ENCRYPTION
— key for encrypting access keys in database. It must be generated by using the following command:head -c32 /dev/urandom | base64
.
Run the following command to start Semaphore with configured database (MySQL or Postgres):
docker-compose up
Semaphore will be available via the following URL http://localhost:3000.
For more information about the Docker Compose, see the Docker Compose reference.
Binary file
Look into the manual installation on how to set-up your Python/Ansible/Systemd environment!
Download the *.tar.gz
for your platform from Releases page. Unpack it and setup Semaphore using the following commands:
download/v2.12.4/semaphore_2.12.4_linux_amd64.tar.gz
tar xf semaphore_2.12.4_linux_amd64.tar.gz
./semaphore setup
Now you can run Semaphore:
./semaphore server --config=./config.json
Semaphore will be available via the following URL https://localhost:3000.
Run as a service
For more detailed information — look into the extended Systemd service documentation.
If you installed Semaphore via a package manager, or by downloading a binary file, you should create the Semaphore service manually.
Create the systemd service file:
/path/to/semaphore
and /path/to/config.json
to your semaphore and config file path.
sudo cat > /etc/systemd/system/semaphore.service <<EOF
[Unit]
Description=Semaphore Ansible
Documentation=https://github.com/semaphoreui/semaphore
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/path/to/semaphore server --config=/path/to/config.json
SyslogIdentifier=semaphore
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target
EOF
Start the Semaphore service:
sudo systemctl daemon-reload
sudo systemctl start semaphore
Check the Semaphore service status:
sudo systemctl status semaphore
To make the Semaphore service auto start:
sudo systemctl enable semaphore
Snap (deprecated)
To install Semaphore via snap, run following command in terminal:
sudo snap install semaphore
Semaphore will be available by URL https://localhost:3000.
But to log in, you should create an admin user. Use the following commands:
sudo snap stop semaphore
sudo semaphore user add --admin \
--login john \
--name=John \
[email protected] \
--password=12345
sudo snap start semaphore
You can check the status of the Semaphore service using the following command:
sudo snap services semaphore
It should print the following table:
Service Startup Current Notes
semaphore.semaphored enabled active -
After installation, you can set up Semaphore via Snap Configuration. Use the following command to see your Semaphore configuration:
sudo snap get semaphore
List of available options you can find in Configuration options reference.
Manually installing Semaphore
Content:
This documentation goes into the details on how to set-up Semaphore when using these installation methods:
The Semaphore software-package is just a part of the whole system needed to successfully run Ansible with it.
The Python3- and Ansible-Execution-Environment are also very important!
NOTE: There are existing Ansible-Galaxy Roles that handle this setup-logic for you or can be used as a base-template for your own Ansible Role!
Service User
Semaphore does not need to be run as user root
- so you shouldn't.
Benefits of using a service user:
- Has its own user-config
- Has its own environment
- Processes easily identifiable
- Gained system security
You can create a system user either manually by using adduser
or using the ansible.builtin.user module.
In this documentation we will assume:
- the service user creates is named
semaphore
- it has the shell
/bin/bash
set - its home directory is
/home/semaphore
Troubleshooting
If the Ansible execution of Semaphore is failing - you will need to troubleshoot it in the context of the service user.
You have multiple options to do so:
-
Change your whole shell session to be in the user's context:
sudo su --login semaphore
-
Run a single command in the user's context:
sudo --login -u semaphore <command>
Python3
Ansible is build using the Python3 programming language.
So its clean setup is essential for Ansible to work correctly.
First - make sure the packages python3
and python3-pip
are installed on your system!
You have multiple options to install required Python modules:
- Installing them in the service user's context
- Installing them in a service-specific Virtual Environment
Requirements
Either way - it is recommended to use a requirements.txt
file to specify the modules that need to be installed.
We will assume the file /home/semaphore/requirements.txt
is used.
Here is an example of its content:
ansible
# for common jinja-filters
netaddr
jmespath
# for common modules
pywinrm
passlib
requests
docker
NOTE: You should also update those requirements from time to time!
An option for doing this automatically is also shown in the service example below.
Modules in user context
Manually:
sudo --login -u semaphore python3 -m pip install --user --upgrade -r /home/semaphore/requirements.txt
Using Ansible:
- name: Install requirements
ansible.builtin.pip:
requirements: '/home/semaphore/requirements.txt'
extra_args: '--user --upgrade'
become_user: 'semaphore'
Modules in a virtualenv
We will assume the virtualenv is created at /home/semaphore/venv
Make sure the virtual environment is activated inside the Service! This is also shown in the service example below.
Manually:
sudo su --login semaphore
python3 -m pip install --user virtualenv
python3 -m venv /home/semaphore/venv
# activate the context of the virtual environment
source /home/semaphore/venv/bin/activate
# verify we are using python3 from inside the venv
which python3
> /home/semaphore/venv/bin/python3
python3 -m pip install --upgrade -r /home/semaphore/requirements.txt
# disable the context to the virtual environment
deactivate
Using Ansible:
- name: Create virtual environment and install requirements into it
ansible.builtin.pip:
requirements: '/home/semaphore/requirements.txt'
virtualenv: '/home/semaphore/venv'
state: present # or 'latest' to upgrade the requirements
Troubleshooting
If you encounter Python3 issues when using a virtual environment, you will need to change into its context to troubleshoot them:
sudo su --login semaphore
source /home/semaphore/venv/bin/activate
# verify we are using python3 from inside the venv
which python3
> /home/semaphore/venv/bin/python3
# troubleshooting
deactivate
Sometimes a virtual environment also breaks on system upgrades. If this happens you might just remove the existing one and re-create it.
Ansible Collections & Roles
You might want to pre-install Ansible modules and roles, so they don't need to be installed every time a task runs!
Requirements
It is recommended to use a requirements.yml
file to specify the modules that need to be installed.
We will assume the file /home/semaphore/requirements.yml
is used.
Here is an example of its content:
---
collections:
- 'namespace.collection'
# for common collections:
- 'community.general'
- 'ansible.posix'
- 'community.mysql'
- 'community.crypto'
roles:
- src: 'namespace.role'
See also: Installing Collections, Installing Roles
NOTE: You should also update those requirements from time to time!
An option for doing this automatically is also shown in the service example below.
Install in user-context
Manually:
sudo su --login semaphore
ansible-galaxy collection install --upgrade -r /home/semaphore/requirements.yml
ansible-galaxy role install --force -r /home/semaphore/requirements.yml
Install when using a virtualenv
Manually:
sudo su --login semaphore
source /home/semaphore/venv/bin/activate
# verify we are using python3 from inside the venv
which python3
> /home/semaphore/venv/bin/python3
ansible-galaxy collection install --upgrade -r /home/semaphore/requirements.yml
ansible-galaxy role install --force -r /home/semaphore/requirements.yml
deactivate
Reverse Proxy
See: Security - Encrypted connection
Extended Systemd Service
Here is the basic template of the systemd service.
Add additional settings under their [PART]
Base
[Unit]
Description=Semaphore UI
Documentation=https://docs.semaphoreui.com/
Wants=network-online.target
After=network-online.target
ConditionPathExists=/usr/bin/semaphore
ConditionPathExists=/etc/semaphore/config.json
[Service]
ExecStart=/usr/bin/semaphore server --config /etc/semaphore/config.json
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target
Service user
[Service]
User=semaphore
Group=semaphore
Python Modules
In user-context
[Service]
# to auto-upgrade python modules at service startup
ExecStartPre=/bin/bash -c 'python3 -m pip install --upgrade --user -r /home/semaphore/requirements.txt'
# so the executables are found
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/semaphore/.local/bin"
# set the correct python path. You can get the correct path with: python3 -c "import site; print(site.USER_SITE)"
Environment="PYTHONPATH=/home/semaphore/.local/lib/python3.10/site-packages"
In virtualenv
[Service]
# to auto-upgrade python modules at service startup
ExecStartPre=/bin/bash -c 'source /home/semaphore/venv/bin/activate \
&& python3 -m pip install --upgrade -r /home/semaphore/requirements.txt'
# REPLACE THE EXISTING 'ExecStart'
ExecStart=/bin/bash -c 'source /home/semaphore/venv/bin/activate \
&& /usr/bin/semaphore server --config /etc/semaphore/config.json'
Ansible Collections & Roles
If using Python3 in user-context
[Service]
# to auto-upgrade ansible collections and roles at service startup
ExecStartPre=/bin/bash -c 'ansible-galaxy collection install --upgrade -r /home/semaphore/requirements.yml'
ExecStartPre=/bin/bash -c 'ansible-galaxy role install --force -r /home/semaphore/requirements.yml'
If using Python3 in virtualenv
# to auto-upgrade ansible collections and roles at service startup
ExecStartPre=/bin/bash -c 'source /home/semaphore/venv/bin/activate \
&& ansible-galaxy collection install --upgrade -r /home/semaphore/requirements.yml \
&& ansible-galaxy role install --force -r /home/semaphore/requirements.yml'
Other use-cases
Using local MariaDB
[Unit]
Requires=mariadb.service
Using local Nginx
[Unit]
Wants=nginx.service
Sending logs to syslog
[Service]
StandardOutput=journal
StandardError=journal
SyslogIdentifier=semaphore
Full Examples
Python Modules in user-context
[Unit]
Description=Semaphore UI
Documentation=https://docs.semaphoreui.com/
Wants=network-online.target
After=network-online.target
ConditionPathExists=/usr/bin/semaphore
ConditionPathExists=/etc/semaphore/config.json
[Service]
User=semaphore
Group=semaphore
Restart=always
RestartSec=10s
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:~/.local/bin"
ExecStartPre=/bin/bash -c 'ansible-galaxy collection install --upgrade -r /home/semaphore/requirements.yml'
ExecStartPre=/bin/bash -c 'ansible-galaxy role install --force -r /home/semaphore/requirements.yml'
ExecStartPre=/bin/bash -c 'python3 -m pip install --upgrade --user -r /home/semaphore/requirements.txt'
ExecStart=/usr/bin/semaphore server --config /etc/semaphore/config.json
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target
Python Modules in virtualenv
[Unit]
Description=Semaphore UI
Documentation=https://docs.semaphoreui.com/
Wants=network-online.target
After=network-online.target
ConditionPathExists=/usr/bin/semaphore
ConditionPathExists=/etc/semaphore/config.json
[Service]
User=semaphore
Group=semaphore
Restart=always
RestartSec=10s
ExecStartPre=/bin/bash -c 'source /home/semaphore/venv/bin/activate \
&& python3 -m pip install --upgrade -r /home/semaphore/requirements.txt'
ExecStartPre=/bin/bash -c 'source /home/semaphore/venv/bin/activate \
&& ansible-galaxy collection install --upgrade -r /home/semaphore/requirements.yml \
&& ansible-galaxy role install --force -r /home/semaphore/requirements.yml'
ExecStart=/bin/bash -c 'source /home/semaphore/venv/bin/activate \
&& /usr/bin/semaphore server --config /etc/semaphore/config.json'
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target
Fixes
If you have a custom system language set - you might run into problems that can be resoled by updating the associated environmental variables:
[Service]
Environment=LANG="en_US.UTF-8"
Environment=LC_ALL="en_US.UTF-8"
Troubleshooting
If there is a problem while executing a task it might be an environmental issue with your setup - not an issue with Semaphore itself!
Please go through these steps to verify if the issue occurs outside Semaphore:
-
Change into the context of the user:
sudo su --login semaphore
-
Change into the context of the virtualenv if you use one:
source /home/semaphore/venv/bin/activate # verify we are using python3 from inside the venv which python3 > /home/semaphore/venv/bin/python3 # troubleshooting deactivate
-
Run the Ansible Playbook manually
- If it fails => there is an issue with your environment
- If it works:
- Re-check your configuration inside Semaphore
- It might be an issue with Semaphore
Configuration
There are following ways to configure Semaphore:
Configuration options
Full list of available configuration options:
Config file option / Environment variable | Description |
---|---|
bolt.host SEMAPHORE_DB_HOST | Path to the BoltDB database file. |
mysql.host SEMAPHORE_DB_HOST | MySQL database host. |
mysql.name SEMAPHORE_DB_NAME | MySQL database (schema) name. |
mysql.user SEMAPHORE_DB_USER | MySQL user name. |
mysql.pass SEMAPHORE_DB_PASS | MySQL user's password. |
postgres.host SEMAPHORE_DB_HOST | Postgres database host. |
postgres.name SEMAPHORE_DB_NAME | Postgres database (schema) name. |
postgres.user SEMAPHORE_DB_USER | Postgres user name. |
postgres.pass SEMAPHORE_DB_PASS | Postgres user's password. |
dialect SEMAPHORE_DB_DIALECT | Can be mysql , postgres or bolt |
git_client SEMAPHORE_GIT_CLIENT | |
ssh_config_path SEMAPHORE_SSH_PATH | |
port SEMAPHORE_PORT | TCP port on which the web interface will be available. Default: 3000 |
interface SEMAPHORE_INTERFACE | Useful if your server has multiple network interfaces |
tmp_path SEMAPHORE_TMP_PATH | Path to directory where cloned repositories and generated files are stored. Default: /tmp/semaphore |
access_key_encryption SEMAPHORE_ACCESS_KEY_ENCRYPTION | Secret key used for encrypting access keys in database. Read more in Database encryption reference. |
web_host SEMAPHORE_WEB_ROOT | Can be useful if you want to use Semaphore by the subpath, for example: http://yourdomain.com/semaphore. Do not add a trailing / . |
tls.enabled SEMAPHORE_TLS_ENABLED | |
tls.cert_file SEMAPHORE_TLS_CERT_FILE | |
tls.key_file SEMAPHORE_TLS_KEY_FILE | |
email_sender SEMAPHORE_EMAIL_SENDER | |
email_host SEMAPHORE_EMAIL_HOST | |
email_port SEMAPHORE_EMAIL_PORT | |
email_secure SEMAPHORE_EMAIL_SECURE | |
email_username SEMAPHORE_EMAIL_USERNAME | |
email_password SEMAPHORE_EMAIL_PASSWORD | |
email_alert SEMAPHORE_EMAIL_ALERT | |
telegram_alert SEMAPHORE_TELEGRAM_ALERT | |
telegram_chat SEMAPHORE_TELEGRAM_CHAT | |
telegram_token SEMAPHORE_TELEGRAM_TOKEN | |
slack_alert SEMAPHORE_SLACK_ALERT | Set to True to enable pushing alerts to slack. It should be used in combination with slack_url |
slack_url SEMAPHORE_SLACK_URL | The slack webhook url. Semaphore will used it to POST Slack formatted json alerts to the provided url. |
microsoft_teams_alert SEMAPHORE_MICROSOFT_TEAMS_ALERT | Set to True to enable pushing alerts to teams. It should be used in combination with microsoft_teams_url . |
microsoft_teams_url SEMAPHORE_MICROSOFT_TEAMS_URL | The teams webhook url. Semaphore will used it to POST alerts. |
rocketchat_alert SEMAPHORE_ROCKETCHAT_ALERT | Set to True to enable pushing alerts to Rocket.Chat. It should be used in combination with rocketchat_url . Available since v2.9.56. |
rocketchat_url SEMAPHORE_ROCKETCHAT_URL | The rocketchat webhook url. Semaphore will used it to POST Rocket.Chat formatted json alerts to the provided url. Available since v2.9.56. |
ldap_enable SEMAPHORE_LDAP_ENABLE | |
ldap_needtls SEMAPHORE_LDAP_NEEDTLS | |
ldap_binddn SEMAPHORE_LDAP_BIND_DN | |
ldap_bindpassword SEMAPHORE_LDAP_BIND_PASSWORD | |
ldap_server SEMAPHORE_LDAP_SERVER | |
ldap_searchdn SEMAPHORE_LDAP_SEARCH_DN | |
ldap_searchfilter SEMAPHORE_LDAP_SEARCH_FILTER | |
max_parallel_tasks SEMAPHORE_MAX_PARALLEL_TASKS | Max allowed parallel tasks for whole Semaphore instance. |
max_task_duration_sec SEMAPHORE_MAX_TASK_DURATION_SEC | Max allowed parallel tasks for whole Semaphore instance. |
max_tasks_per_template SEMAPHORE_MAX_TASKS_PER_TEMPLATE | Max allowed parallel tasks for whole Semaphore instance. |
oidc_providers | OpenID provider settings. You can provide multiple OpenID providers. More about OpenID configuration read in OpenID. |
password_login_disable SEMAPHORE_PASSWORD_LOGIN_DISABLED | Disable login with using password. Only LDAP and OpenID. |
non_admin_can_create_project SEMAPHORE_NON_ADMIN_CAN_CREATE_PROJECT | |
env_vars SEMAPHORE_ENV_VARS | |
forwarded_env_vars SEMAPHORE_FORWARDED_ENV_VARS | |
apps SEMAPHORE_APPS | |
use_remote_runner SEMAPHORE_USE_REMOTE_RUNNER | |
use_remote_runner SEMAPHORE_USE_REMOTE_RUNNER | |
runner_registration_token SEMAPHORE_RUNNER_REGISTRATION_TOKEN | |
auth.totp.enabled SEMAPHORE_TOTP_ENABLED | |
auth.totp.allow_recovery SEMAPHORE_TOTP_ALLOW_RECOVERY |
Public URL
If you use nginx or other web server before Semaphore, you should provide configuration option web_host
.
For example you configured NGINX on the server which proxies queries to Semaphore.
Server address https://exmaple.com
and you proxies all queries https://exmaple.com/semaphore
to Semaphore.
Your web_host
will be https://exmaple.com/semaphore
.
Configuration file
You can use interactive config file generator:
Semaphore uses a config.json
configuration file with following content:
{
"bolt": {
"host": "/home/ubuntu/semaphore.bolt"
},
"mysql": {
"host": "localhost",
"user": "root",
"pass": "*****",
"name": "semaphore",
"options": {}
},
"postgres": {
"host": "localhost",
"user": "postgres",
"pass": "*****",
"name": "semaphore",
"options": {}
},
"dialect": "postgres",
"port": "",
"interface": "",
"tmp_path": "/tmp/semaphore",
"cookie_hash": "*****",
"cookie_encryption": "*****",
"access_key_encryption": "*****",
"email_sender": "",
"email_host": "",
"email_port": "",
"web_host": "",
"ldap_binddn": "",
"ldap_bindpassword": "",
"ldap_server": "",
"ldap_searchdn": "",
"ldap_searchfilter": "",
"ldap_mappings": {
"dn": "",
"mail": "",
"uid": "",
"cn": ""
},
"telegram_chat": "",
"telegram_token": "",
"concurrency_mode": "",
"max_parallel_tasks": 0,
"email_alert": false,
"telegram_alert": false,
"slack_alert": false,
"slack_url": "",
"microsoft_teams_alert": false,
"microsoft_teams_url": "",
"rocketchat_alert": false,
"rocketchat_url": "",
"ldap_enable": false,
"ldap_needtls": false
}
Usage:
semaphore server --config ./config.json
Environment variables
With using environment variables you can override any available configuration option.
You can use interactive evnvironment variables generator (for Docker):
Interactive setup
Use this option for first time configuration (not working for Semaphore installed via Snap).
semaphore setup
Snap configuration
Snap configurations should be used for when Semaphore was installed via Snap.
To see a list of available options, use the following command:
sudo snap get semaphore
You can change each of these configurations. For example if you want to change Semaphore port, use following command:
sudo snap set semaphore port=4444
Don't forget to restart Semaphore after changing a configuration:
sudo snap restart semaphore
Upgrading
There are 4 ways for upgrading Semaphore:
- Snap
- Package manager
- Docker
- Binary
Snap
Use the following command for upgrading Semaphore to the latest stable version:
sudo snap refresh semaphore
Package manager
Download a package file from Releases page.
*.deb
for Debian and Ubuntu, *.rpm
for CentOS and RedHat.
Install it using the package manager.
wget https://github.com/semaphoreui/semaphore/releases/\
download/v2.12.4/semaphore_2.12.4_linux_amd64.deb
sudo dpkg -i semaphore_2.12.4_linux_amd64.deb
Docker
Binary
Download a *.tar.gz
for your platform from Releases page. Unpack the binary to the directory where your old Semaphore binary is located.
wget https://github.com/semaphoreui/semaphore/releases/\
download/v2.12.4/semaphore_2.12.4_linux_amd64.tar.gz
tar xf semaphore_2.12.4_linux_amd64.tar.gz
Security
Database security
Data encryption
Sensitive data is stored in the database, in an encrypted form. You should set the configuration option access_key_encryption
in configuration file to enable Access Keys encryption. It must be generated by command:
head -c32 /dev/urandom | base64
Network security
For security reasons, Semaphore should not be used over unencrypted HTTP!
Why use encrypted connections? See: Article from Cloudflare.
Options you have:
VPN
You can use a Client-to-Site VPN, that terminates on the Semaphore server, to encrypt & secure the connection.
SSL
Semaphore supports SSL/TLS starting from v2.12.
config.json:
{
...
"tsl": {
"enabled": true,
"cert_file": "/path/to/cert/example.com.cert",
"key_file": "/path/to/key/example.com.key"
}
...
}
Or environment varibles (useful for Docker):
export SEMAPHORE_TLS_ENABLED=True
export SEMAPHORE_TLS_CERT_FILE=/path/to/cert/example.com.cert
export SEMAPHORE_TLS_KEY_FILE=/path/to/key/example.com.key
Alternatively, you can use a reverse proxy in front of Semaphore to handle secure connections. For example:
Self-signed SSL certificate
You can generate your own SSL certificate with using openssl
CLI tool:
openssl req -x509 -newkey rsa:4096 \
-keyout key.pem -out cert.pem \
-sha256 -days 3650 -nodes \
-subj "/C=US/ST=California/L=San Francisco/O=CompanyName/OU=DevOps/CN=example.com"
Let's Encrypt SSL certificate
You can use Certbot to generate and automatically renew a Let's Encrypt SSL certificate.
Example for Apache:
sudo snap install certbot
sudo certbot --apache -n --agree-tos -d example.com -m [email protected]
Others
If you want to use any other reverse proxy - make sure to also forward websocket connections on the /api/ws
route!
Nginx config
Configuration example:
server {
listen 443 ssl;
server_name example.com;
# add Strict-Transport-Security to prevent man in the middle attacks
add_header Strict-Transport-Security "max-age=31536000" always;
# SSL
ssl_certificate /etc/nginx/cert/cert.pem;
ssl_certificate_key /etc/nginx/cert/privkey.pem;
# Recommendations from
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx/
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
# required to avoid HTTP 411: see Issue #1486
# (https://github.com/docker/docker/issues/1486)
chunked_transfer_encoding on;
location / {
proxy_pass http://127.0.0.1:3000/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_request_buffering off;
}
location /api/ws {
proxy_pass http://127.0.0.1:3000/api/ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Origin "";
}
}
Apache config
Make sure you have enabled following Apache modules:
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
Add following virtual host to your Apache configuration:
<VirtualHost *:443>
ServerName example.com
ServerAdmin webmaster@localhost
SSLEngine on
SSLCertificateFile /path/to/example.com.crt
SSLCertificateKeyFile /path/to/example.com.key
ProxyPreserveHost On
<Location />
ProxyPass http://127.0.0.1:3000/
ProxyPassReverse http://127.0.0.1:3000/
</Location>
<Location /api/ws>
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
ProxyPass ws://127.0.0.1:3000/api/ws/
ProxyPassReverse ws://127.0.0.1:3000/api/ws/
</Location>
</VirtualHost>
CLI
Common config options
Option | Description |
---|---|
--config config.json | Path to the configuration file. |
--no-cofnig | Do not use any configuration file. Only environment variable will be used. |
--log-level ERROR | DEBUG , INFO , WARN , ERROR , FATAL , PANIC |
Version
Print current version.
semaphore version
Help
Print list of supported commands.
semaphore help
Database migration
Update database schema to latest version.
semaphore migrate
Interactive setup
Use this option for first time configuration.
semaphore setup
Server mode
Start the server.
semaphore server
Runner mode
Start the runner.
semaphore runner
Users
Using CLI you can add, remove or change user.
semaphore user --help
How to add admin user
semaphore user add \
--admin \
--login newAdmin \
--email [email protected] \
--name "New Admin" \
--password "New$Password"
How to change user password
semaphore user change-by-login \
--login myAdmin \
--password "New$Password"
Vaults
You can reencrypt your secrets in database with using following command:
semaphore vault rekey --old-key <encryption-key-which-used-before>
Your data will be decryped using <encryption-key-which-used-before>
and will be encrypted using option access_key_encryption
from configuration key.
Runners
LDAP configuration
Configuration file contains the following LDAP parameters:
{
"ldap_binddn": "cn=admin,dc=example,dc=org",
"ldap_bindpassword": "admin_password",
"ldap_server": "localhost:389",
"ldap_searchdn": "ou=users,dc=example,dc=org",
"ldap_searchfilter": "(&(objectClass=inetOrgPerson)(uid=%s))",
"ldap_mappings": {
"dn": "",
"mail": "uid",
"uid": "uid",
"cn": "cn"
},
"ldap_enable": true,
"ldap_needtls": false,
}
All SSO provider options:
Parameter | Environment Variables | Description |
---|---|---|
ldap_binddn | SEMAPHORE_LDAP_BIND_DN | |
ldap_bindpassword | SEMAPHORE_LDAP_BIND_PASSWORD | Password of LDAP user which used as Bind DN. |
ldap_server | SEMAPHORE_LDAP_SERVER | LDAP server host including port. For example: localhost:389 . |
ldap_searchdn | SEMAPHORE_LDAP_SEARCH_DN | Scope where users will be searched. For example: ou=users,dc=example,dc=org . |
ldap_searchfilter | SEMAPHORE_LDAP_SEARCH_FILTER | Users search expression. Default: (&(objectClass=inetOrgPerson)(uid=%s)) , where %s will replaced to entered login. |
ldap_mappings.dn | SEMAPHORE_LDAP_MAPPING_DN | |
ldap_mappings.mail | SEMAPHORE_LDAP_MAPPING_MAIL | User email claim expression*. |
ldap_mappings.uid | SEMAPHORE_LDAP_MAPPING_UID | User login claim expression*. |
ldap_mappings.cn | SEMAPHORE_LDAP_MAPPING_CN | User name claim expression*. |
ldap_enable | SEMAPHORE_LDAP_ENABLE | LDAP enabled. |
ldap_needtls | SEMAPHORE_LDAP_NEEDTLS | Connect to LDAP server by SSL. |
*Claim expression
Example of claim expression:
email | {{ .username }}@your-domain.com
Semaphore is attempting to claim the email field first. If it is empty, the expression following it is executed.
"username_claim": "|"
generates a random username
for each user who logs in through the provider.
Troubleshooting
Use ldapwhoami
tool to check if your BindDN works:
ldapwhoami\
-H ldap://ldap.com:389\
-D "CN=your_ldap_binddn_value_in_config"\
-x\
-W
It will ask interactively for the password, and should return code 0 and echo out the DN as specified.
Example: Using OpenLDAP Server
Run the following command to start your own LDAP server with an admin account and an additional user:
docker run -d --name openldap \
-p 1389:1389 \
-p 1636:1636 \
-e LDAP_ADMIN_USERNAME=admin \
-e LDAP_ADMIN_PASSWORD=pwd \
-e LDAP_USERS=user1 \
-e LDAP_PASSWORDS=pwd \
-e LDAP_ROOT=dc=example,dc=org \
-e LDAP_ADMIN_DN=cn=admin,dc=example,dc=org \
bitnami/openldap:latest
Your LDAP configuration for Semaphore UI should be as follows:
{
"ldap_binddn": "cn=admin,dc=example,dc=org",
"ldap_bindpassword": "pwd",
"ldap_server": "ldap-server.com:1389",
"ldap_searchdn": "dc=example,dc=org",
"ldap_searchfilter": "(&(objectClass=inetOrgPerson)(uid=%s))",
"ldap_mappings": {
"mail": "{{ .cn }}@ldap.your-domain.com",
"uid": "|",
"cn": "cn"
},
"ldap_enable": true,
"ldap_needtls": false
}
To run Semaphore in Docker, use the following LDAP configuration:
docker run -d -p 3000:3000 --name semaphore \
-e SEMAPHORE_DB_DIALECT=bolt \
-e SEMAPHORE_ADMIN=admin \
-e SEMAPHORE_ADMIN_PASSWORD=changeme \
-e SEMAPHORE_ADMIN_NAME=Admin \
-e SEMAPHORE_ADMIN_EMAIL=admin@localhost \
-e SEMAPHORE_LDAP_ENABLE=yes \
-e SEMAPHORE_LDAP_SERVER=ldap-server.com:1389 \
-e SEMAPHORE_LDAP_BIND_DN=cn=admin,dc=example,dc=org \
-e SEMAPHORE_LDAP_BIND_PASSWORD=pwd \
-e SEMAPHORE_LDAP_SEARCH_DN=dc=example,dc=org \
-e 'SEMAPHORE_LDAP_SEARCH_FILTER=(&(objectClass=inetOrgPerson)(uid=%s))' \
-e 'SEMAPHORE_LDAP_MAPPING_MAIL={{ .cn }}@ldap.your-domain.com' \
-e 'SEMAPHORE_LDAP_MAPPING_UID=|' \
-e 'SEMAPHORE_LDAP_MAPPING_CN=cn' \
semaphoreui/semaphore:latest
OpenID
Semaphore supports authentication via OpenID Connect (OIDC).
Links:
- GitHub config
- Google config
- GitLab config
- Authelia config
- Authentik config
- Keycloak config
- Okta config
- Azure config
Example of SSO provider configuration:
{
"oidc_providers": {
"mysso": {
"display_name": "Sign in with MySSO",
"color": "orange",
"icon": "login",
"provider_url": "https://mysso-provider.com",
"client_id": "***",
"client_secret": "***",
"redirect_url": "https://your-domain.com/api/auth/oidc/mysso/redirect"
}
}
}
All SSO provider options:
Parameter | Description |
---|---|
display_name | Provider name which displayed on Login screen. |
icon | MDI-icon which displayed before of provider name on Login screen. |
color | Provider name which displayed on Login screen. |
client_id | Provider client ID. |
client_id_file | The path to the file where the provider's client ID is stored. Has less priorty then client_id . |
client_secret | Provider client Secret. |
client_secret_file | The path to the file where the provider's client secret is stored. Has less priorty then client_secret . |
redirect_url | |
provider_url | |
scopes | |
username_claim | Username claim expression*. |
email_claim | Email claim expression*. |
name_claim | Profile Name claim expression*. |
order | Position of the provider button on the Sign in screen. |
endpoint.issuer | |
endpoint.auth | |
endpoint.token | |
endpoint.userinfo | |
endpoint.jwks | |
endpoint.algorithms |
*Claim expression
Example of claim expression:
email | {{ .username }}@your-domain.com
Semaphore is attempting to claim the email field first. If it is empty, the expression following it is executed.
"username_claim": "|"
generates a random username
for each user who logs in through the provider.
Sign in screen
For each of the configured providers, an additional login button is added to the login page:
GitHub config
config.json
:
{
"oidc_providers": {
"github": {
"icon": "github",
"display_name": "Sign in with GitHub",
"client_id": "***",
"client_secret": "***",
"redirect_url": "https://your-domain.com/api/auth/oidc/github/redirect",
"endpoint": {
"auth": "https://github.com/login/oauth/authorize",
"token": "https://github.com/login/oauth/access_token",
"userinfo": "https://api.github.com/user"
},
"scopes": ["read:user", "user:email"],
"username_claim": "|",
"email_claim": "email | {{ .id }}@github.your-domain.com",
"name_claim": "name",
"order": 1
}
}
}
Google config
config.json
:
{
"oidc_providers": {
"google": {
"color": "blue",
"icon": "google",
"display_name": "Sign in with Google",
"provider_url": "https://accounts.google.com",
"client_id": "***.apps.googleusercontent.com",
"client_secret": "GOCSPX-***",
"redirect_url": "https://your-domain.com/api/auth/oidc/google/redirect",
"username_claim": "|",
"name_claim": "name",
"order": 2
}
}
}
GitLab config
config.json
:
{
"oidc_providers": {
"gitlab": {
"display_name": "Sign in with GitLab",
"color": "orange",
"icon": "gitlab",
"provider_url": "https://gitlab.com",
"client_id": "***",
"client_secret": "gloas-***",
"redirect_url": "https://your-domain.com/api/auth/oidc/gitlab/redirect",
"username_claim": "|",
"order": 3
}
}
}
Tutorial in Semaphore UI blog: GitLab authentication in Semaphore UI.
Authelia config
Authelia config.yaml
:
identity_providers:
oidc:
- client_id: semaphore
client_name: Semaphore
client_secret: 'your_secret'
public: false
authorization_policy: two_factor
redirect_uris:
- https://your-semaphore-domain.com/api/auth/oidc/authelia/redirect
scopes:
- openid
- profile
- email
userinfo_signed_response_alg: none
Semaphore config.json
:
"oidc_providers": {
"authelia": {
"display_name": "Authelia",
"provider_url": "https://your-authelia-domain.com",
"client_id": "semaphore",
"client_secret": "your_secret",
"redirect_url": "https://your-semaphore-domain.com/api/auth/oidc/authelia/redirect"
}
},
Authentik config
config.json
:
{
"oidc_providers": {
"authentik": {
"display_name": "Sign in with Authentik",
"provider_url": "https://authentik.example.com/application/o/<slug>/",
"client_id": "<client-id>",
"client_secret": "<client-secret>",
"redirect_url": "https://semaphore.example.com/api/auth/oidc/authentik/redirect/",
"scopes": ["openid", "profile", "email"],
"username_claim": "preferred_username",
"name_claim": "preferred_username"
}
}
}
Discussion on GitHub: #1663.
See also description in authentik docs.
Keycloak config
config.json
:
{
"oidc_providers": {
"keycloak": {
"display_name": "Sign in with keycloak",
"provider_url": "https://keycloak.example.com/realms/master",
"client_id": "***",
"client_secret": "***",
"redirect_url": "https://semaphore.example.com/api/auth/oidc/keycloak/redirect"
}
}
}
Okta config
config.json
:
{
"oidc_providers": {
"okta": {
"display_name":"Sign in with Okta",
"provider_url":"https://trial-776xxxx.okta.com/oauth2/default",
"client_id":"***",
"client_secret":"***",
"redirect_url":"https://semaphore.example.com/api/auth/oidc/okta/redirect/"
}
}
}
Azure config
config.json
:
{
"oidc_providers": {
"azure": {
"color": "blue",
"display_name": "Sign in with Azure (Entra ID)",
"provider_url": "https://login.microsoftonline.com/<Tennant ID>/v2.0",
"client_id": "<ID>",
"client_secret": "<secret>",
"redirect_url": "https://semaphore.test.com/api/auth/oidc/azure/redirect"
}
}
}
API
API documentation
How to use the Semaphore API
Login to Semaphore (password should be escaped, slashy\\pass
instead of slashy\pass
e.g.):
curl -v -c /tmp/semaphore-cookie -XPOST \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{"auth": "YOUR_LOGIN", "password": "YOUR_PASSWORD"}' \
http://localhost:3000/api/auth/login
Get a user tokens:
curl -v -b /tmp/semaphore-cookie \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
http://localhost:3000/api/user/tokens
Generate a new token, and get the new token:
curl -v -b /tmp/semaphore-cookie -XPOST \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
http://localhost:3000/api/user/tokens
curl -v -b /tmp/semaphore-cookie \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
http://localhost:3000/api/user/tokens
The command should return something similar to:
[{"id":"
YOUR_ACCESS_TOKEN
","created":"2017-03-11T13:13:13Z","expired":false,"user_id":1}]
Use this token for launching a task or anything else:
curl -v -XPOST \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{"template_id": 1}' \
http://localhost:3000/api/project/1/tasks
Expire a token:
curl -v -XDELETE \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
http://localhost:3000/api/user/tokens/YOUR_ACCESS_TOKEN
Pipelines
Semaphore supports simple pipelins with using build
and deploy
tasks.
Semaphore passes semaphore_vars
variable to each Ansible playbook which it runs.
You can use it in your Ansible tasks to get what type of task was run, which version should be build or deployed, who ran the task, etc.
Example of semaphore_vars
for build
tasks:
semaphore_vars:
task_details:
type: build
username: user123
message: New version of some feature
target_version: 1.5.33
Example of semaphore_vars
for deploy
tasks:
semaphore_vars:
task_details:
type: deploy
username: user123
message: Deploy new feature to servers
incoming_version: 1.5.33
Build
This type of task is used to create artifacts. Each build task has autogenerated version. You should use variable semaphore_vars.task_details.target_version
in your Ansible playbook to get what version of the artifact should be created. After the artifact is created, it can be used for deployment.
Example of build
Ansible role:
- Get app source code from GitHub
- Compile source code
- Pack created binary to a tarball with name
app-{{semaphore_vars.task_details.target_version}}.tar.gz
- Send
app-{{semaphore_vars.task_details.target_version}}.tar.gz
to an S3 bucket
Deploy
This type of task is used to deploy artifacts to destination servers. Each deployment task is associated with the build task. You should use variable semaphore_vars.task_details.incoming_version
in your Ansible playbook to get what version of the artifact should be deployed.
Example of deploy
Ansible role:
- Download
app-{{semaphore_vars.task_details.incoming_version}}.tar.gz
from an S3 bucket to destination servers - Unpack
app-{{semaphore_vars.task_details.incoming_version}}.tar.gz
to destination directory - Create or update configuration files
- Restart app service
Runners
Runners enable running tasks on a separate server from Semaphore UI.
Semaphore runners operate on the same principle as GitLab or GitHub Actions runners:
- You launch a runner on a separate server, specifying the Semaphore server's address and an authentication token.
- The runner connects to Semaphore and signals its readiness to accept tasks.
- When a new task appears, Semaphore provides all the necessary information to the runner, which, in turn, clones the repository and runs Ansible, Terraform, PowerShell, etc.
- The runner sends the task execution results back to Semaphore.
For end users, working with Semaphore with or without runners appears the same.
Using runners offers the following advantages:
- Executing tasks more securely. For instance, a runner can be located within a closed subnet or isolated docker container.
- Distributing the workload across multiple servers. You can start multiple runners, and tasks will be randomly distributed among them.
Set up
To set up the server for working with running you should add following option to your Semaphore server configuration:
{
"use_remote_runner": true,
"runner_registration_token": "long string of random characters"
}
To set up the runner, use the following command:
semaphore runner setup --config /path/to/your/config/file.json
This command will create a configuration file at /path/to/your/config/file.json
.
But before using this command, you need to understand how runners are registered on the server.
Registering the Runner on the Server
There are two ways to register a runner on the Semaphore server:
- Add it via the web interface or API.
- Use the command line with the
semaphore runner register
command.
Adding the Runner via the Web Interface
Registering via CLI
To register a runner this way, you need to add the runner_registration_token
option to your Semaphore server's configuration file. This option should be set to an arbitrary string. Choose a sufficiently complex string to avoid security issues.
When the semaphore runner setup
command asks if you have a Runner token, answer No. Then use the following command to register the runner:
semaphore runner register --config /path/to/your/config/file.json
or
echo REGISTRATION_TOKEN | semaphore runner register --stdin-registration-token --config /path/to/your/config/file.json
Configuration File
As a result of running the semaphore runner setup
command, a configuration file like the following will be created:
{
"tmp_path": "/tmp/semaphore",
"web_host": "https://semaphore_server_host",
// Here you can provide other settings, for example: git_client, ssh_config_path, etc.
// ...
// Runner specific options
"runner": {
"token": "your runner's token",
// or
"token_file": "path/to/the/file/where/runner/saves/token"
// Here you can provide other runner-specific options,
// which will be used for runner registration, for example:
// max_parallel_tasks, webhook, one_off, etc.
// ...
}
}
You can manually edit this file without needing to call semaphore runner setup
again.
To re-register the runner, you can use the semaphore runner register
command. This will overwrite the token in the file specified in the configuration.
Running the Runner
Now you can start the runner with the command:
semaphore runner start --config /path/to/your/config/file.json
Your runner is ready to execute tasks ;)
Runner unregistaration
You can remove runner using the web interfance.
Or unregister runner via CLI:
semaphore runner unregister --config /path/to/your/config/file.json
Security
Data transfer security is ensured by using asymmetric encryption: the server encrypts data using a public key, the runner decrypts it using a private key.
Public and private keys are generated automatically when the runner registers on the server.
Projects
A project is a place to separate management activity.
All Semaphore activities occur within the context of a project.
Projects are independent from one another, so you can use them to organize unrelated systems within a single Semaphore installation.
This can be useful for managing different teams, infrastructures, environments or applications.
Task Templates
Templates define how to run an Ansible Playbook. The template allows you to specify the following parameters:
- Playbook repository
- Playbook filename
- Inventory
- Environment
- Vault password file
- Extra CLI arguments
- and much more
The task template can be one of the following types:
Task
Just runs specified playbooks with specified parameters.
Build
This type of template should be used to create artifacts. The start version of the artifact can be specified in a template parameter. Each run increments the artifact version.
Semaphore doesn't support artifacts out-of-box, it only provides task versioning. You should implement the artifact creation yourself. Read the article CI/CD to know how to do this.
Deploy
This type of template should be used to deploy artifacts to the destination servers. Each deploy
template is associated with a build
template.
This allows you to deploy a specific version of the artifact to the servers.
Schedule
You can set up task scheduling by specifying a cron schedule in the template settings. Cron expression format you can find in documentation.
Run a task when a new commit is added to the repository
You can use cron to periodically check for new commits in the repository and trigger a task upon their arrival.
For example you have source code of the app in the git repository. You can add it to Repositories and trigger the Build task for new commits.
Ansible
Using Semaphore UI you can run Terraform code. To do this, you need to create a Terraform Code Template.
- Go go Task Templates section and click the New Template button.
- Set up the template and click the Create button.
- You can now run your Terraform code.
Terraform/OpenTofu
Using Semaphore UI you can run Terraform code. To do this, you need to create a Terraform Code Template.
- Go go Task Templates section and click the New Template button.
- Set up the template and click the Create button.
- You can now run your Terraform code.
Workspaces
States 🅿
Shell/Bash scripts
Using Semaphore UI you can run Bash scripts. To do this, you need to create a Bash Script Template.
- Go go Task Templates section and click the New Template button.
- Set up the template and click the Create button.
- You can now run your Bash script.
PowerShell
Python
Tasks
A task is an instance of launching an Ansible playbook. You can create the task from Task Template by clicking the button Run/Build/Deploy for the required template.
The Deploy task type allows you to specify a version of the build associated with the task. By default, it is the latest build version.
When the task is running, or it has finished, you can see the task status and the running log.
Tasks log retention
You'll notice that logs of previous runs of your tasks are available in the tasks template or in the dashboard.
However, by default, log retention is infinite.
You can configure this by using the max_tasks_per_template
parameter in config.json
or the SEMAPHORE_MAX_TASKS_PER_TEMPLATE
environment variable.
Schedules
Key Store
The Key Store in Semaphore is used to store credentials for accessing remote Repositories, accessing remote hosts, sudo credentials, and Ansible vault passwords.
It is helpful to have configured all required access keys before setting up other resources like Inventories, Repositories, and tasks templates so you do not have to edit them later.
Types
SSH
SSH Keys are used to access remote servers as well as remote Repositories.
If you need assistance quickly generating a key and placing it on your host, here is a quick guide.
For Git Repositories that use SSH authentication, the Git Repository you are trying to clone from needs to have your public key associated to the private key.
Below are links to the docs for some common Git Repositories:
Login With Password
Login With Password is a username and password/access token combination that can be used to do the following:
- Authenticate to remote hosts (although this is less secure than using SSH keys)
- Sudo credentials on remote hosts
- Authenticate to remote Git Repositories over HTTPS (although SSH is more secure)
- Unlock Ansible vaults
None
This is used as a filler for Repos that do not require authentication, like an Open-Source Repository on GitLab.
Inventory
An Inventory is a file that contains a list of hosts Ansible will run plays against. An Inventory also stores variables that can be used by playbooks. An Inventory can be stored in YAML, JSON, or TOML. More information about Inventories can be found in the Ansible Documentation.
Semaphore UI can either read an Inventory from a file on the server that the Semaphore user has read access to, or a static Inventory that is edited via the web GUI. Each Inventory also has at least one credential tied to it. The user credential is required, and is what Ansible uses to log into hosts for that Inventory. Sudo credentials are used for escalating privileges on that host. It is required to have a user credential that is either a username with a login, or SSH configured in the Key Store to create an Inventory. Information about credentials can be found in the Key Store section of this site.
Creating an Inventory
- Click on the Key Store tab and confirm you have a key that is a login_password or ssh type
- Click on the Inventory tab and click New Inventory
- Name the Inventory and select the correct user credential from the dropdown. Select the correct sudo credential, if needed
- Select the Inventory type
- If you select file, use the absolute path to the file. If this file is located in your git repo, then use relative path. Ex.
inventory/linux-hosts.yaml
- If you select static, paste in or type your Inventory into the form
- Click Create.
Updating an Inventory
- Click on the Inventory tab
- Click the Pencil Icon next to the Inventory you want to edit
- Make your changes
- Click Save
Deleting an Inventory
Before you remove an Inventory, you must remove all resources tied to it. If you are not sure which resources are being used in an environment, follow steps 1 and 2 below. It will show you which resources are being used, with links to those resources.
- Click on the Inventory tab
- Click the trash can icon next to the Inventory
- Click Yes if you are sure you want to remove the Inventory
Variable Groups
The Variable Groups section of Semaphore is a place to store additional variables for an inventory and must be stored in JSON format.
All task templates require an variable group to be defined even if it is empty.
Create an variable group
- Click on the Variable Group tab.
- Click on the New Variable Group button.
- Name the Variable Group and type or paste in valid JSON variables. If you just need an empty Variable Group type in
{}
.
Updating an variable group
- Click on the Variable Groups tab.
- Click the pencil icon.
- Make changes and click save.
Deleting the variable group
Before you remove an variable proup, you must remove all resources tied to it. If you are not sure which resources are being used in an variable group, follow steps 1 and 2 below. It will show you which resources are being used, with links to those resources.
- Click on the Variable Group.
- Click the trash can icon next to the Variable Group.
- Click Yes if you are sure you want to remove the variable group.
Using Variable Groups - Terraform
When you want utilize a stored variable group variable or secret in your terraform template you must prefix the name with TF_VAR_
for the terraform script to use it.
Example Passing Hetzner Cloud API key to OpenTofu/Terraform playbook.
- Click on Variable Group
- Click
New Group
- Click on
Secrets
tab - Add
TV_VAR_hcloud_token
and add yousecret
in the hidden field - Click Save
We will call our secret TV_VAR_hcloud_token
as var.hcloud_token
in
hetzner.tf
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45"
}
}
}
# Declare the variable
variable "hcloud_token" {
type = string
description = "Hetzner Cloud API token"
sensitive = true # This prevents the token from being displayed in logs
}
provider "hcloud" {
token = var.hcloud_token
}
# Create a new server running debian
resource "hcloud_server" "webserver" {
name = "webserver"
image = "ubuntu-24.04"
server_type = "cpx11"
location = "ash"
ssh_keys = [ "mysshkey" ]
public_net {
ipv4_enabled = true
ipv6_enabled = true
}
}
Repositories
A Repository is a place to store and manage Ansible content like playbooks and roles.
Semaphore understands Repositories that are:
- a local file system (
/path/to/the/repo
) - a local Git repository (
file://
) - a remote Git Repository that is accessed over HTTPS (
https://
), SSH(ssh://
) git://
protocol supported, but it is not recommended for security reasons.
All Task Templates require a Repository in order to run.
Authentication
If you are using a remote Repository that requires authentication, you will need to configure a key in the Key Store section of Semaphore.
For remote Repositories that use SSH, you will need to use your SSH key in the Key Store.
For Remote Repositories that do not have authentication, you can create a Key with the type of None
.
Creating a New Repository
- Make sure you have configured the key for the Repository you are about to add in the key store section.
- Go to the Repositories section of Semaphore, click the New Repository button in the upper right hand corner.
- Configure the Repository:
- Name Repository
- Add the URL. The URL must start with the following:
/path/to/the/repo
for a local folder on the file systemhttps://
for a remote Git Repository accessed over HTTPSssh://
for a remote Git Repository accessed over SSHfile://
for a local Git Repositorygit://
for a remote Git Repository accessed over Git protocol
- Set the branch of the Repository, if you are not sure what it should be, it is probably master or main
- Select the Access Key you configured prior to setting up this Repository.
- Click Save once everything is configured.
Editing an Existing Repository
- Go to the Repositories section of Semaphore.
- Click on the pencil icon next to the Repository you wish to change, then you will be presented with the Repository configuration.
Deleting a Repository
Make sure the Repository that is about to be delete is not in use by any Task Templates. A Repository cannot be deleted if it is used in any Task Templates:
- Go to the Repositories section of Semaphore.
- Click on the trash can icon on of the Repository you wish to delete.
- Click Yes on the confirmation pop-up if you are sure you want this Repository to be deleted.
You can use a Bitbucket Access Token in Semaphore to access repositories from Bitbucket.
First, you need to create an Access Token for your Bitbucket repository with read access permissions.
After creation, you will see the access token. Copy it to your clipboard as it will be required for creating an Access Key in Semaphore.
-
Go to to the Key Store section in Semaphore and click the New Key button.
-
Choose
Login with password
as the type of key. -
Enter
x-token-auth
as Login and paste the previously copied key into the Password field. Save the key. -
Go to the Repositories section and click the New Repository button.
-
Enter HTTPS URL of the repository (
https://bitbucket.org/path/to/repo
), enter correct branch and select previously created Access Key.
Integrations
Integrations allow establishing interaction between Semaphore and external services, such as GitHub and GitLab.
Using integration, you can trigger a specific template by calling a special endpoint (alias), for which you can configure one of the following authentication methods:
- GitHub Webhooks
- Token
- HMAC
- No authentication
The alias represents a URL in the following format: /api/integrations/<random_string>
. Supports GET
and POST
requests.
Matchers
With matchers, you can define parameters of the incoming request. When these parameters match, the template will be invoked.
Value Extractors
With an extractor, you can extract the necessary data from the incoming request and pass it to the task as environment variables. For the extracted variables to be passed to the task, you must create an environment with the corresponding keys. Ensure that the environment keys match the variables defined in the extractor, as this allows the task to receive and use the correct environment variables.
Admin
Subscription 🅿
Troubleshooting
Renner prints error 404
How to fix
Getting 401 error code from Runner
Gathering Facts issue for localhost
The issue can occur on Semaphore UI installed via Snap or Docker.
4:10:16 PM
TASK [Gathering Facts] *********************************************************
4:10:17 PM
fatal: [localhost]: FAILED! => changed=false
Why this happens
For more information about localhost use in Ansible, read this article Implicit 'localhost'.
Ansible tries to gather facts locally, but Ansible is located in a limited isolated container which doesn't allow this.
How to fix this
There are two ways:
- Disable facts gathering:
- hosts: localhost
gather_facts: False
roles:
- ...
- Explicitly set the connection type to ssh:
[localhost]
127.0.0.1 ansible_connection=ssh ansible_ssh_user=your_localhost_user
panic: pq: SSL is not enabled on the server
This means that your Postgres doesn't work by SSL.
How to fix this
Add option sslmode=disable
to the configuration file:
"postgres": {
"host": "localhost",
"user": "pastgres",
"pass": "pwd",
"name": "semaphore",
"options": {
"sslmode": "disable"
}
},
fatal: bad numeric config value '0' for 'GIT_TERMINAL_PROMPT': invalid unit
This means that you are trying to access a repository over HTTPS that requires authentication.
How to fix this
- Go to Key Store screen.
- Create a new key
Login with password
type. - Specify your login for GitHub/BitBucket/etc.
- Specify the password. You can't use your account password for GitHub/BitBucket, you should use a Personal Access Token (PAT) instead of it. Read more here.
- After creating the key, go to the Repositories screen, find your repository and specify the key.
unable to read LDAP response packet: unexpected EOF
Most likely, you are trying to connect to the LDAP server using an insecure method, although it expects a secure connection (via TLS).
How to fix this
Enable TLS in your config.json
file:
...
"ldap_needtls": true
...
LDAP Result Code 49 "Invalid Credentials"
You have the wrong password or binddn
.
How to fix this
Use ldapwhoami
tool and check if your binddn works:
ldapwhoami\
-H ldap://ldap.com:389\
-D "CN=/your/ldap_binddn/value/in/config/file"\
-x\
-W
It will ask interactively for the password and should return code 0 and echo out the DN as specified.
You also can read the following articles:
LDAP Result Code 32 "No Such Object"
Coming soon.