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
| Feature | Semaphore | AWX | Automation Controller |
|---|---|---|---|
| License | MIT (free) | Apache 2.0 (free) | Red Hat subscription |
| Resource usage | ~50 MB RAM | ~4 GB RAM | ~8 GB RAM |
| Setup time | 5 minutes | 30-60 minutes | Hours (with installer) |
| Web UI | Clean, minimal | Full-featured | Enterprise |
| API | REST API | REST API | REST API |
| RBAC | Basic (admin/user) | Full RBAC | Full RBAC + orgs |
| Workflows | Task templates | Workflow visualizer | Workflow visualizer |
| Scheduling | Cron schedules | Cron schedules | Cron schedules |
| Inventories | Static, file-based | Static + dynamic | Static + dynamic |
| Execution Environments | No (uses system ansible) | Yes | Yes |
| Automation Mesh | No | No | Yes |
| Best for | Small teams, labs | Mid-size teams | Enterprise |
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 Type | Use Case |
|---|---|
| SSH Key | Connect to managed hosts |
| Login with password | SSH password authentication |
| None | Public 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:
| Field | Value |
|---|---|
| Name | Deploy Web Application |
| Playbook | deploy.yml |
| Repository | Infrastructure Playbooks |
| Inventory | Production |
| Environment | Production Vars |
| Vault Password | (from key store) |
Schedule Jobs
Semaphore supports cron-style scheduling:
- Open a Task Template
- Click Schedules → Add Schedule
- Set cron expression:
| Schedule | Cron Expression |
|---|---|
| Every day at 2 AM | 0 2 * * * |
| Every Monday 9 AM | 0 9 * * 1 |
| Every 6 hours | 0 */6 * * * |
| First of month | 0 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
- Use PostgreSQL for production — BoltDB is fine for testing, not for teams
- Put behind HTTPS — use Nginx/Caddy reverse proxy with TLS
- Separate credentials — different SSH keys per environment
- Version your playbooks — Semaphore pulls from Git, so your repo is the source of truth
- Use environments for variables — keep secrets in Vault, use environments for non-sensitive vars
- Schedule compliance scans — daily/weekly runs of hardening playbooks
- Monitor the dashboard — check for failed tasks regularly
Related Articles
- Ansible AWX Installation Guide
- Ansible Automation Platform 2.6 Architecture
- Ansible Vault Guide
- Ansible Inventory Guide
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.