Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Semaphore Ubuntu
GUIDEInstall Guides

How to Install Semaphore CI on Ubuntu 24.04 — Self-Hosted Ansible Task Runner

23 min read

How to Install Semaphore CI on Ubuntu 24.04 — Self-Hosted Ansible Task Runner

If you manage more than a handful of Linux servers, you already know Ansible. What you probably want next is a clean web UI to organize playbooks, store inventories and SSH keys, schedule jobs, and show non-Ansible teammates a "Run" button instead of a terminal. That is exactly what Semaphore UI provides. This tutorial walks you through installing Semaphore on an Ubuntu 24.04 VPS end to end — from the first SSH connection to a scheduled Ansible playbook running against your fleet, protected by HTTPS.

Prefer a different CI? Compare with Jenkins, Drone CI, Woodpecker CI, or Gitea Actions.

Table of Contents

  • What is Semaphore UI?
  • Why Self-Host an Ansible Task Runner?
  • Prerequisites
  • Step 1: Update the System and Install Ansible
  • Step 2: Install and Configure the Database
  • Step 3: Install Semaphore from the .deb Package
  • Step 4: Run the Semaphore Setup Wizard
  • Step 5: Create the Admin User and Start the Service
  • Step 6: Configure Nginx with Let's Encrypt TLS
  • Step 7: Create Your First Project
  • Step 8: Add Inventory, Environment, and Keys
  • Step 9: Build a Task Template and Run a Playbook
  • Step 10: Schedule a Playbook with Cron
  • Hardening and Backups
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is Semaphore UI?

    Semaphore UI (the open-source project lives at github.com/ansible-semaphore/semaphore) is a modern, browser-based interface for Ansible. Instead of running ansible-playbook from your laptop, you define projects inside Semaphore and let it execute playbooks on demand or on a schedule. Every run is logged, every secret is encrypted at rest, and every user has a role.

    Semaphore supports four task types: Ansible playbooks, Terraform plans and applies, shell scripts (Bash), and tfvars-driven modules. For most sysadmins and DevOps engineers, the Ansible integration is the main draw — it turns your existing playbook repository into a point-and-click automation platform without changing a single line of YAML.

    Under the hood, Semaphore is a single Go binary that talks to a SQL database (BoltDB, MySQL, or PostgreSQL), serves a Vue-based web UI, exposes a REST API, and shells out to ansible-playbook, terraform, or bash for the actual work. It is lightweight, easy to back up, and straightforward to upgrade.

    Why Self-Host an Ansible Task Runner?

    You could run Ansible from a laptop or a shared jump host forever, but a self-hosted task runner quickly pays for itself:

    • Centralized secrets. SSH private keys, vault passwords, and cloud API tokens live in one encrypted store instead of scattered across developer laptops.
    • Audit trail. Every playbook run is logged with the user, the inventory targeted, start/end time, and full stdout. When something breaks at 3 a.m., you know exactly who ran what.
    • Scheduled automation. Cron triggers let you run patching, log rotation, certificate renewal, or backup playbooks without maintaining a separate crontab on a bastion host.
    • Non-sysadmin access. Junior team members, support engineers, and even product managers can trigger safe, pre-approved playbooks from the UI without being handed SSH keys.
    • No vendor lock-in. Unlike Ansible Automation Platform (formerly Ansible Tower, Red Hat subscription), AWX (Kubernetes-heavy), or cloud services like Ansible-as-a-service, Semaphore runs on a single Ubuntu box and costs nothing beyond the VPS.
    • Data sovereignty. Inventories, playbooks, secrets, and run history never leave your infrastructure. This matters for GDPR, HIPAA, SOC 2, and any internal policy that treats credentials as sensitive.
    • Fits small and medium fleets. Semaphore is happy managing 5 servers or 500. It does not require Kubernetes, Redis, or an object store.
    For a 1-5 person team managing up to a few hundred hosts, Semaphore is often the sweet spot between "crontab on a jump box" and a full enterprise automation platform.

    Prerequisites

    Before you start, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access
    • SSH access from your workstation
    • A domain name (for example, semaphore.example.com) with an A record pointing to your VPS IP
    • Open firewall ports 22 (SSH), 80 (HTTP, for Certbot validation), and 443 (HTTPS)
    • At least 2 GB of RAM and 20 GB of disk — Semaphore itself is tiny, but Ansible + a database + the OS comfortably fit this footprint
    Recommended Plan: CloudCore Starter
    >
    Semaphore is lightweight, and our CloudCore Starter plan is a strong fit for a single-team Ansible controller:
    >
    - 4 vCPU cores
    - 8 GB RAM
    - 75 GB NVMe SSD
    - Unmetered bandwidth
    - EUR 7.99/month
    >
    That leaves headroom for the database, concurrent playbook runs, and a few hundred managed hosts. Scale up later only if you start running long-lived Terraform plans or parallel fleet-wide rollouts.

    Connect to your server:

    bash
    ssh root@your-server-ip

    Step 1: Update the System and Install Ansible

    Bring the system up to date and install Ansible plus supporting tools. Semaphore does not bundle Ansible — it calls the ansible-playbook binary that you provide.

    bash
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y ansible git curl gnupg ca-certificates python3-pip sshpass

    Confirm the Ansible version:

    bash
    ansible --version

    Expected output:

    text
    ansible [core 2.16.3]
      config file = /etc/ansible/ansible.cfg
      python version = 3.12.3

    If the kernel was upgraded, reboot and reconnect:

    bash
    sudo reboot

    Step 2: Install and Configure the Database

    Semaphore supports BoltDB (embedded file), MySQL, and PostgreSQL. For anything beyond a single-user test, pick MySQL or PostgreSQL. We show both — use whichever you already operate.

    Option A: MySQL (MariaDB)

    bash
    sudo apt install -y mariadb-server
    sudo systemctl enable --now mariadb
    sudo mysql_secure_installation

    Accept the defaults for socket authentication, set a strong root password, and answer "Y" to the remaining hardening questions.

    Create the Semaphore database and user:

    bash
    sudo mysql -u root -p
    sql
    CREATE DATABASE semaphore CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE USER 'semaphore'@'localhost' IDENTIFIED BY 'ChangeThisStrongPassword!';
    GRANT ALL PRIVILEGES ON semaphore.* TO 'semaphore'@'localhost';
    FLUSH PRIVILEGES;
    EXIT;

    Option B: PostgreSQL

    bash
    sudo apt install -y postgresql postgresql-contrib
    sudo systemctl enable --now postgresql
    bash
    sudo -u postgres psql
    sql
    CREATE DATABASE semaphore;
    CREATE USER semaphore WITH ENCRYPTED PASSWORD 'ChangeThisStrongPassword!';
    GRANT ALL PRIVILEGES ON DATABASE semaphore TO semaphore;
    \c semaphore
    GRANT ALL ON SCHEMA public TO semaphore;
    \q

    Store the credentials in a password manager — you will paste them into the Semaphore setup wizard in a moment.

    Step 3: Install Semaphore from the .deb Package

    The Semaphore project publishes official .deb packages on GitHub Releases. Fetch the latest release URL from github.com/ansible-semaphore/semaphore/releases, then download and install it.

    bash
    cd /tmp
    SEMAPHORE_VERSION="2.10.32"
    curl -LO "https://github.com/ansible-semaphore/semaphore/releases/download/v${SEMAPHORE_VERSION}/semaphore_${SEMAPHORE_VERSION}_linux_amd64.deb"
    sudo dpkg -i "semaphore_${SEMAPHORE_VERSION}_linux_amd64.deb"

    Expected output:

    text
    Selecting previously unselected package semaphore.
    Unpacking semaphore (2.10.32) ...
    Setting up semaphore (2.10.32) ...

    Confirm the binary is on your PATH:

    bash
    semaphore version

    Expected output:

    text
    v2.10.32

    Create a dedicated system user and directory structure so Semaphore does not run as root:

    bash
    sudo useradd --system --home /opt/semaphore --shell /usr/sbin/nologin semaphore
    sudo mkdir -p /etc/semaphore /var/lib/semaphore /var/log/semaphore /tmp/semaphore
    sudo chown -R semaphore:semaphore /etc/semaphore /var/lib/semaphore /var/log/semaphore /tmp/semaphore

    Step 4: Run the Semaphore Setup Wizard

    Semaphore ships with an interactive setup wizard that generates a JSON config file. Run it as the semaphore user so file ownership is correct from the start.

    bash
    sudo -u semaphore semaphore setup

    The wizard asks a series of questions. Sensible answers are shown below — adapt the DB choice and paths to your environment.

    text
    Hello. You will now be guided through a setup to:

  • Set up configuration for a MySQL/MariaDB database
  • Set up a path for your playbooks (auto-created)
  • Run database migrations
  • Set up initial Semaphore user & password
  • What database to use: 1 - MySQL 2 - BoltDB 3 - PostgreSQL (default 1): 1

    db Hostname (default 127.0.0.1:3306): db User (default root): semaphore db Password: ChangeThisStrongPassword! db Name (default semaphore):

    Playbook path (default /tmp/semaphore): /var/lib/semaphore

    Public URL (optional, example: https://<hostname>/semaphore): https://semaphore.example.com

    Enable email alerts? (yes/no) (default no): no Enable telegram alerts? (yes/no) (default no): no Enable slack alerts? (yes/no) (default no): no

    Config output directory (default /home/semaphore): /etc/semaphore

    Running: mkdir -p /etc/semaphore.. Configuration written to /etc/semaphore/config.json.. Pinging db.. Running DB Migrations.. Migrations Finished

    The wizard writes an encrypted config to /etc/semaphore/config.json containing the database credentials, encryption keys, and cookie secrets. Lock it down:

    bash
    sudo chown semaphore:semaphore /etc/semaphore/config.json
    sudo chmod 600 /etc/semaphore/config.json

    Step 5: Create the Admin User and Start the Service

    Create the first admin user non-interactively so you can log into the UI:

    bash
    sudo -u semaphore semaphore user add \
      --admin \
      --login admin \
      --email [email protected] \
      --name "Admin User" \
      --password 'ChangeThisAdminPassword!' \
      --config /etc/semaphore/config.json

    Expected output:

    text
    User admin <[email protected]> added!

    Now create a systemd unit so Semaphore starts at boot and restarts on failure:

    bash
    sudo tee /etc/systemd/system/semaphore.service > /dev/null <<'EOF'
    [Unit]
    Description=Semaphore Ansible UI
    Documentation=https://docs.semaphoreui.com
    After=network.target mariadb.service postgresql.service

    [Service] Type=simple User=semaphore Group=semaphore ExecStart=/usr/bin/semaphore service --config=/etc/semaphore/config.json Restart=on-failure RestartSec=5s WorkingDirectory=/var/lib/semaphore Environment=HOME=/var/lib/semaphore ProtectSystem=full NoNewPrivileges=true PrivateTmp=true

    [Install] WantedBy=multi-user.target EOF

    Enable and start:

    bash
    sudo systemctl daemon-reload
    sudo systemctl enable --now semaphore
    sudo systemctl status semaphore

    Expected output:

    text
    ● semaphore.service - Semaphore Ansible UI
         Loaded: loaded (/etc/systemd/system/semaphore.service; enabled; preset: enabled)
         Active: active (running) since Thu 2026-04-16 12:00:00 UTC; 3s ago
       Main PID: 4321 (semaphore)

    Semaphore listens on port 3000 by default. A quick local check:

    bash
    curl -I http://127.0.0.1:3000

    Expected:

    text
    HTTP/1.1 200 OK

    Do not expose port 3000 to the internet — we will put it behind Nginx with TLS next.

    Step 6: Configure Nginx with Let's Encrypt TLS

    Install Nginx and Certbot:

    bash
    sudo apt install -y nginx certbot python3-certbot-nginx

    Create a reverse proxy site:

    bash
    sudo tee /etc/nginx/sites-available/semaphore > /dev/null <<'EOF'
    server {
        listen 80;
        server_name semaphore.example.com;

    location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $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;

    # WebSocket support for live task output streaming proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; proxy_send_timeout 3600s;

    client_max_body_size 50m; } } EOF

    sudo ln -s /etc/nginx/sites-available/semaphore /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx

    Issue a certificate:

    bash
    sudo certbot --nginx -d semaphore.example.com --redirect --agree-tos -m [email protected] --no-eff-email

    Certbot rewrites the Nginx config to add HTTPS, a redirect from HTTP, and a systemd timer that auto-renews every 60 days. Verify the renewal will work:

    bash
    sudo certbot renew --dry-run

    Now open https://semaphore.example.com in your browser. Log in with the admin account you created in Step 5. You should land on the empty projects page.

    Step 7: Create Your First Project

    Click New Project on the landing page. A project is a logical container for playbooks, inventories, keys, environments, and task templates.

    Fill in:

    • Name: Infrastructure
    • Alerts: off for now
    • Max parallel tasks: 4 (how many playbooks can run simultaneously)
    Click Create. You are dropped into an empty project with a left-hand sidebar: Task Templates, Inventory, Environment, Key Store, Repositories, Schedules, Team, and Activity.

    Add a Git Repository

    Semaphore runs playbooks directly from a Git repo. Go to Repositories → New Repository:

    • Name: infra-playbooks
    • Git URL: [email protected]:yourorg/infra-playbooks.git
    • Branch: main
    • Access Key: (we will add a deploy key in the next step)
    Leave the form open; jump to Key Store, add the key, then return and select it.

    Step 8: Add Inventory, Environment, and Keys

    Key Store

    Semaphore's Key Store holds three types of secrets, all encrypted with the AES key generated during semaphore setup:

    • SSH Key — private keys used to connect to managed hosts
    • Login with password — username/password pairs (for Git HTTPS, for ansible_become_pass, etc.)
    • None — a placeholder when no auth is needed
    Generate a dedicated SSH key pair on the Semaphore server:

    bash
    sudo -u semaphore ssh-keygen -t ed25519 -f /var/lib/semaphore/.ssh/id_ed25519 -N ''
    sudo cat /var/lib/semaphore/.ssh/id_ed25519.pub

    Push the public key to your managed hosts (or bake it into your base image). Then in the UI, go to Key Store → New Key:

    • Name: ansible-ssh
    • Type: SSH Key
    • Private Key: paste the contents of /var/lib/semaphore/.ssh/id_ed25519
    Save. Create a second key for the Git deploy key the same way and go back to Repositories to attach it.

    Inventory

    Go to Inventory → New Inventory:

    • Name: production
    • Type: Static (paste YAML/INI directly) or File (use an inventory file from the repo)
    • User Credentials: ansible-ssh (the key you just added)
    Static inventory example:

    ini
    [webservers]
    web01.example.com
    web02.example.com

    [dbservers] db01.example.com

    [all:vars] ansible_user=deploy ansible_python_interpreter=/usr/bin/python3

    Environment

    Environments are JSON blobs of Ansible extra_vars and shell env vars injected at run time — perfect for per-stage configuration (dev / staging / production).

    Go to Environment → New Environment:

    • Name: production-env
    • Extra Variables (JSON):
    json
    {
      "deploy_env": "production",
      "app_version": "1.4.2",
      "notify_slack": true
    }
    • Environment Variables (JSON):
    json
    {
      "ANSIBLE_HOST_KEY_CHECKING": "False",
      "ANSIBLE_FORCE_COLOR": "True"
    }

    Save. Everything in the first block is passed to Ansible as --extra-vars; everything in the second is exported as a shell environment variable for the run.

    Step 9: Build a Task Template and Run a Playbook

    A Task Template glues together a repository, an inventory, an environment, and a playbook path into a re-runnable job.

    Go to Task Templates → New Template → Ansible Playbook:

    • Name: Deploy Web Tier
    • Playbook Filename: playbooks/web-deploy.yml (relative to the repo root)
    • Inventory: production
    • Repository: infra-playbooks
    • Environment: production-env
    • Vault Password: (optional — reference a Key Store entry if your playbook uses Ansible Vault)
    • Arguments (JSON array): ["--limit", "webservers", "--diff"]
    • Allow CLI args in Survey: checked, if you want users to override args at run time
    • Survey variables: add fields (for example app_version) that users must fill in before launching
    Save. You land back on the Task Templates page with a new card. Click the play icon to launch it.

    The live task view streams ansible-playbook output line by line. When the task completes, you get a green/red badge, duration, and a full log you can download. Every run is stored in the Activity tab, attributed to the user who launched it.

    If you just want to smoke-test before pointing Semaphore at real hosts, create a trivial playbook in your repo:

    yaml
    # playbooks/smoke.yml
    
    • name: Smoke test
    hosts: all gather_facts: false tasks: - name: Ping ansible.builtin.ping: - name: Show hostname ansible.builtin.command: hostname changed_when: false

    Run it against a one-host inventory and verify you see PLAY RECAP with ok=2 at the bottom of the task log.

    Step 10: Schedule a Playbook with Cron

    Semaphore's scheduler uses standard cron syntax to run any task template automatically.

    Go to Schedules → New Schedule:

    • Task Template: Deploy Web Tier
    • Cron Expression: 0 3 * (every day at 03:00 server time)
    • Active: on
    Common cron expressions:
    ExpressionMeaning
    /15 Every 15 minutes
    0 Top of every hour
    0 3 Daily at 03:00
    0 3 0Every Sunday at 03:00
    0 3 1 First of every month at 03:00
    Save. Semaphore's internal scheduler (there is no separate cron daemon involved) will fire the task at the scheduled time using the same inventory, environment, and survey defaults as manual runs. Each scheduled run shows up in Activity just like a manual run, with Triggered by: schedule.

    Typical scheduled playbooks:

    • Nightly OS patching with ansible.builtin.apt: upgrade=dist and a reboot role
    • Weekly TLS certificate audit that reports hosts with certs expiring in less than 30 days
    • Hourly drift detection running in check mode (--check --diff) against your baseline role

    Hardening and Backups

    Firewall

    Lock down everything except SSH, HTTP (for Certbot), and HTTPS:

    bash
    sudo ufw allow OpenSSH
    sudo ufw allow 'Nginx Full'
    sudo ufw enable
    sudo ufw status

    Two-Factor Authentication

    Semaphore supports OIDC (OpenID Connect) for SSO. Point it at Keycloak, Authentik, or Google Workspace so logins flow through your existing identity provider with MFA already enforced. The auth block in /etc/semaphore/config.json is where OIDC providers are declared — see the official OIDC docs for provider-specific blocks.

    Backups

    Three things must be backed up to recover a Semaphore installation:

  • The config file /etc/semaphore/config.json (contains DB credentials and the AES encryption key — without it, stored SSH keys and vault passwords cannot be decrypted).
  • The database (all projects, templates, schedules, users, run history).
  • Any playbook repos that are not already stored on GitHub/Gitea.
  • Example nightly MySQL dump:

    bash
    sudo tee /usr/local/bin/semaphore-backup.sh > /dev/null <<'EOF'
    #!/usr/bin/env bash
    set -euo pipefail
    STAMP=$(date +%F)
    BACKUP_DIR=/var/backups/semaphore
    mkdir -p "$BACKUP_DIR"
    mysqldump --single-transaction --quick --default-character-set=utf8mb4 \
      -u semaphore -p'ChangeThisStrongPassword!' semaphore \
      | gzip > "$BACKUP_DIR/semaphore-$STAMP.sql.gz"
    cp /etc/semaphore/config.json "$BACKUP_DIR/config-$STAMP.json"
    chmod 600 "$BACKUP_DIR"/*
    find "$BACKUP_DIR" -mtime +30 -delete
    EOF
    sudo chmod +x /usr/local/bin/semaphore-backup.sh

    Add a crontab entry:

    bash
    echo "15 2   * root /usr/local/bin/semaphore-backup.sh" | sudo tee /etc/cron.d/semaphore-backup

    Copy backups off-box (S3, Backblaze B2, a second VPS) so a single-server failure does not take your automation with it.

    Upgrades

    Semaphore upgrades are trivial — download the new .deb, install it, restart the service:

    bash
    SEMAPHORE_VERSION="2.10.40"
    cd /tmp
    curl -LO "https://github.com/ansible-semaphore/semaphore/releases/download/v${SEMAPHORE_VERSION}/semaphore_${SEMAPHORE_VERSION}_linux_amd64.deb"
    sudo dpkg -i "semaphore_${SEMAPHORE_VERSION}_linux_amd64.deb"
    sudo systemctl restart semaphore

    DB migrations run automatically on startup. Always back up the database before a major version bump.

    Troubleshooting

    ProblemCauseFix
    dial tcp 127.0.0.1:3306: connect: connection refused in semaphore logsMySQL not running, or DB credentials wrong in config.jsonsudo systemctl status mariadb; re-run semaphore setup or edit the mysql block in /etc/semaphore/config.json
    Web UI loads but login returns 500Config file lost the encryption key between restartsRestore /etc/semaphore/config.json from backup — do not regenerate it, or all encrypted keys in the DB become unreadable
    Permission denied (publickey) when the playbook runsThe SSH key in Key Store is not authorized on the target hostssh-copy-id the corresponding public key, or add it to the target's ~/.ssh/authorized_keys
    ansible-playbook: command not found in task outputAnsible missing on the Semaphore serversudo apt install -y ansible
    Live output hangs / WebSocket errors in browser consoleNginx missing the Upgrade/Connection headersRe-apply the Nginx block from Step 6 and reload
    Scheduled task never firesSchedule exists but its task template was deleted or the schedule is inactiveCheck Schedules list — inactive toggles are gray; recreate if needed
    Error 1045: Access denied for user 'semaphore'@'localhost'DB password changed but config.json not updatedEdit /etc/semaphore/config.json, restart with sudo systemctl restart semaphore
    Tail the logs for live debugging:

    bash
    sudo journalctl -u semaphore -f

    FAQ

    How is Semaphore UI different from Ansible AWX or Ansible Automation Platform?

    AWX and Ansible Automation Platform (Red Hat's commercial product) are powerful but heavy — AWX requires Kubernetes or OpenShift and a PostgreSQL operator, while AAP needs a Red Hat subscription. Semaphore UI is a single Go binary with a SQL database, installs in 15 minutes on any Linux box, and scales fine for teams managing a few hundred hosts. You give up advanced features like workflow DAGs with conditional branching and the Red Hat support contract, but you gain dramatic operational simplicity. For most small-to-medium teams, Semaphore is the pragmatic choice.

    Can Semaphore run Terraform and shell scripts, not just Ansible?

    Yes. When creating a task template, the template type dropdown includes Ansible Playbook, Terraform, Tofu (OpenTofu), and Bash script. Terraform templates let you run plan and apply with state stored in the repo or a remote backend. Bash templates simply execute a script from the connected repository with any environment variables and survey inputs you define. This makes Semaphore a reasonable light alternative to Jenkins for mixed IaC workloads.

    Does Semaphore support LDAP or SSO?

    Yes. Semaphore supports LDAP/AD and OpenID Connect (OIDC). OIDC is the modern path — it works with Keycloak, Authentik, Auth0, Azure AD, Google Workspace, and Okta. Configure the provider block in /etc/semaphore/config.json, restart the service, and the login page gains an "SSO" button. Local username/password accounts can coexist with SSO, which is useful for break-glass admin access.

    How many hosts can a single Semaphore server manage?

    Semaphore itself is the bottleneck only for scheduling and log storage — the real constraint is your Ansible playbooks. A CloudCore Starter (4 vCPU, 8 GB RAM) comfortably runs playbooks against 200-500 managed hosts with forks: 20 and a handful of concurrent task templates. If you routinely run wide gather_facts playbooks across 1000+ hosts or several Terraform plans in parallel, move to a larger plan with more RAM and vCPUs. The database and UI scale easily; the limit is CPU during fact gathering.

    Can I trigger Semaphore tasks from CI/CD or external systems?

    Yes. Semaphore exposes a REST API and every task template has a dedicated webhook URL. From GitHub Actions, GitLab CI, or any CI system, POST to https://semaphore.example.com/api/project/<id>/templates/<id>/run with an API token in the Authorization header, and Semaphore will enqueue the run. This lets you treat Semaphore as the "deployer" stage after your CI does the build/test — very common pattern with Woodpecker CI or Gitea Actions driving Semaphore for the final apply step.

    What happens to my encrypted keys if I lose config.json?

    They become unrecoverable. The AES encryption key for the Key Store lives in /etc/semaphore/config.json as access_key_encryption. If the file is lost and not backed up, every SSH key, vault password, and login stored in Semaphore is permanently undecryptable — you must delete them and re-enter. This is why the backup script in the hardening section copies config.json alongside the database dump. Keep it on encrypted offsite storage.

    Should I use BoltDB, MySQL, or PostgreSQL?

    BoltDB (a single file) is fine for a single user evaluating Semaphore on a laptop. It does not support concurrent access well and has no replication story. MySQL/MariaDB is the most common Semaphore deployment and what the maintainers test most heavily — pick this if you do not already have a PostgreSQL footprint. PostgreSQL works equally well and is the right choice if your team already operates Postgres (shared monitoring, backups, replication). For anything production, pick one of the SQL databases.

    Next Steps

    With Semaphore running, protected by TLS, and executing scheduled playbooks, here is where to go next:

    • Wire up Slack or email alerts in project settings so failed runs page the on-call engineer instead of silently going red in the UI.
    • Add OIDC SSO so team members log in with your existing identity provider — no more shared admin password.
    • Version-control your Ansible roles properly. Split playbooks/ and roles/ into separate repos, pin role versions in requirements.yml, and let Semaphore pull the exact revisions it runs.
    • Pair Semaphore with a build-focused CI. Use Jenkins, Drone CI, Woodpecker CI, or Gitea Actions for compilation and tests, then have them trigger Semaphore via webhook for the deploy stage.
    • Document your task templates. Use the description field on every template to explain what it does, which inventory it targets, and who to call if it breaks at 3 a.m.
    • Read the official docs. The install, configuration, and administration reference lives at semaphoreui.com/install and the full manual at docs.semaphoreui.com.

    Need a VPS sized right for Semaphore?
    >
    Our CloudCore Starter plan ships with 4 vCPU, 8 GB RAM, and 75 GB NVMe — the exact shape recommended above for a single-team Ansible controller managing up to a few hundred hosts. EUR 7.99/month, deployed in under a minute, SSH-ready for this tutorial.
    >
    Launch your Starter VPS and finish this install before your next coffee.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket