Introduction

Ansible Semaphore is a lightweight, open-source web UI for running Ansible playbooks. It provides project management, credential storage, job scheduling, and a clean dashboard — without the resource overhead of AWX or Automation Controller. If you need a simple way for your team to run playbooks through a browser, Semaphore is the fastest path to get there.

Semaphore vs AWX vs Automation Controller

FeatureSemaphoreAWXAutomation Controller
LicenseMIT (free)Apache 2.0 (free)Red Hat subscription
Resource usage~50 MB RAM~4 GB RAM~8 GB RAM
Setup time5 minutes30-60 minutesHours (with installer)
Web UIClean, minimalFull-featuredEnterprise
APIREST APIREST APIREST API
RBACBasic (admin/user)Full RBACFull RBAC + orgs
WorkflowsTask templatesWorkflow visualizerWorkflow visualizer
SchedulingCron schedulesCron schedulesCron schedules
InventoriesStatic, file-basedStatic + dynamicStatic + dynamic
Execution EnvironmentsNo (uses system ansible)YesYes
Automation MeshNoNoYes
Best forSmall teams, labsMid-size teamsEnterprise

Install with Docker Compose

The fastest way to get Semaphore running:

# docker-compose.yml
version: '3.8'
services:
  semaphore:
    image: semaphoreui/semaphore:latest
    container_name: semaphore
    ports:
      - "3000:3000"
    environment:
      SEMAPHORE_DB_DIALECT: bolt
      SEMAPHORE_ADMIN_PASSWORD: changeme
      SEMAPHORE_ADMIN_NAME: admin
      SEMAPHORE_ADMIN_EMAIL: admin@example.com
      SEMAPHORE_ADMIN: admin
    volumes:
      - semaphore-data:/var/lib/semaphore
      - /path/to/your/playbooks:/playbooks:ro
    restart: unless-stopped

volumes:
  semaphore-data:
docker compose up -d
# Open http://localhost:3000
# Login: admin / changeme

With PostgreSQL (Production)

# docker-compose.yml
version: '3.8'
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: semaphore
      POSTGRES_USER: semaphore
      POSTGRES_PASSWORD: semaphore_db_pass
    volumes:
      - postgres-data:/var/lib/postgresql/data
    restart: unless-stopped

  semaphore:
    image: semaphoreui/semaphore:latest
    container_name: semaphore
    ports:
      - "3000:3000"
    environment:
      SEMAPHORE_DB_DIALECT: postgres
      SEMAPHORE_DB_HOST: postgres
      SEMAPHORE_DB_PORT: 5432
      SEMAPHORE_DB_NAME: semaphore
      SEMAPHORE_DB_USER: semaphore
      SEMAPHORE_DB_PASS: semaphore_db_pass
      SEMAPHORE_ADMIN_PASSWORD: changeme
      SEMAPHORE_ADMIN_NAME: admin
      SEMAPHORE_ADMIN_EMAIL: admin@example.com
      SEMAPHORE_ADMIN: admin
    depends_on:
      - postgres
    restart: unless-stopped

volumes:
  postgres-data:

Install with Ansible

Deploy Semaphore to a server using Ansible:

---
- name: Install Ansible Semaphore
  hosts: semaphore_server
  become: true
  vars:
    semaphore_version: "2.10.22"
    semaphore_admin_user: admin
    semaphore_admin_pass: "{{ vault_semaphore_admin_pass }}"
    semaphore_db_pass: "{{ vault_semaphore_db_pass }}"
  tasks:
    - name: Install dependencies
      ansible.builtin.apt:
        name:
          - docker.io
          - docker-compose-v2
          - python3-docker
        state: present
        update_cache: true

    - name: Create Semaphore directory
      ansible.builtin.file:
        path: /opt/semaphore
        state: directory
        mode: '0755'

    - name: Deploy docker-compose.yml
      ansible.builtin.template:
        src: semaphore-compose.yml.j2
        dest: /opt/semaphore/docker-compose.yml
        mode: '0640'

    - name: Start Semaphore
      community.docker.docker_compose_v2:
        project_src: /opt/semaphore
        state: present

    - name: Wait for Semaphore to be ready
      ansible.builtin.uri:
        url: http://localhost:3000/api/ping
        status_code: 200
      register: result
      until: result.status == 200
      retries: 30
      delay: 2

Install Binary (No Docker)

# Download latest release
curl -L https://github.com/semaphoreui/semaphore/releases/latest/download/semaphore_linux_amd64.tar.gz \
  | tar xz -C /usr/local/bin/

# Initialize configuration
semaphore setup

# Start
semaphore server --config /etc/semaphore/config.json

systemd Service

# /etc/systemd/system/semaphore.service
[Unit]
Description=Ansible Semaphore
After=network.target

[Service]
Type=simple
User=semaphore
ExecStart=/usr/local/bin/semaphore server --config /etc/semaphore/config.json
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Configure Semaphore

1. Add Key Store (Credentials)

Navigate to Key Store → New Key:

Key TypeUse Case
SSH KeyConnect to managed hosts
Login with passwordSSH password authentication
NonePublic repos, no auth needed

2. Add Repository

Navigate to Repositories → New Repository:

Name: Infrastructure Playbooks
URL: git@github.com:myorg/ansible-playbooks.git
Branch: main
SSH Key: (select from key store)

3. Add Inventory

Navigate to Inventory → New Inventory:

Static inventory:

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

[databases]
db01.example.com

[all:vars]
ansible_user=ansible
ansible_become=true

File-based — point to an inventory file in your repository.

4. Add Environment

Navigate to Environment → New Environment:

{
  "env": "production",
  "deploy_version": "latest",
  "notify_slack": true
}

5. Create Task Template

Navigate to Task Templates → New Template:

FieldValue
NameDeploy Web Application
Playbookdeploy.yml
RepositoryInfrastructure Playbooks
InventoryProduction
EnvironmentProduction Vars
Vault Password(from key store)

Schedule Jobs

Semaphore supports cron-style scheduling:

  1. Open a Task Template
  2. Click Schedules → Add Schedule
  3. Set cron expression:
ScheduleCron Expression
Every day at 2 AM0 2 * * *
Every Monday 9 AM0 9 * * 1
Every 6 hours0 */6 * * *
First of month0 0 1 * *

Semaphore API

# Authenticate
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"auth": "admin", "password": "changeme"}' | jq -r '.token')

# List projects
curl -s http://localhost:3000/api/projects \
  -H "Authorization: Bearer $TOKEN"

# Run a task template
curl -s -X POST http://localhost:3000/api/project/1/tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"template_id": 1}'

# Get task status
curl -s http://localhost:3000/api/project/1/tasks/1 \
  -H "Authorization: Bearer $TOKEN"

Reverse Proxy with Nginx

# /etc/nginx/sites-available/semaphore
server {
    listen 443 ssl http2;
    server_name semaphore.example.com;

    ssl_certificate /etc/letsencrypt/live/semaphore.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/semaphore.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }

    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";
    }
}

Troubleshooting

Playbook Fails with "ansible not found"

Semaphore runs playbooks using the system's ansible-playbook. Install it inside the container or on the host:

# If using Docker, extend the image
FROM semaphoreui/semaphore:latest
RUN apk add --no-cache ansible

SSH Connection Refused

  • Verify the SSH key in Key Store matches the target host's authorized_keys
  • Check the inventory uses the correct ansible_user
  • Test manually: ssh -i /path/to/key user@host

Git Clone Fails

  • Verify the repository URL and branch
  • For private repos, ensure SSH key has read access
  • For HTTPS repos, use a personal access token as password

Best Practices

  1. Use PostgreSQL for production — BoltDB is fine for testing, not for teams
  2. Put behind HTTPS — use Nginx/Caddy reverse proxy with TLS
  3. Separate credentials — different SSH keys per environment
  4. Version your playbooks — Semaphore pulls from Git, so your repo is the source of truth
  5. Use environments for variables — keep secrets in Vault, use environments for non-sensitive vars
  6. Schedule compliance scans — daily/weekly runs of hardening playbooks
  7. Monitor the dashboard — check for failed tasks regularly

Conclusion

Ansible Semaphore is the fastest way to give your team a web UI for running Ansible playbooks. Docker Compose gets you running in 5 minutes with ~50 MB RAM. It handles credentials, Git repo sync, scheduling, and basic user management. For small teams and labs, it's the right tool. For enterprise RBAC, workflows, and Execution Environments, look at AWX or Automation Controller.