How to Self-Host Semaphore: Web UI for Ansible 2026
·OSSAlt Team
semaphoreansibleautomationself-hostingdockerdevops2026
TL;DR
Semaphore (MIT, ~10K GitHub stars, Go) is a web UI for running Ansible playbooks. Instead of SSH-ing into a server and running ansible-playbook commands, click a button in a clean web interface. Schedule playbooks, manage inventories, store credentials securely, and give team members access without sharing SSH keys. AWX (the open source Ansible Tower) requires 8GB+ RAM and complex setup. Semaphore runs in ~50MB RAM.
Key Takeaways
- Semaphore: MIT, ~10K stars, Go — lightweight web UI for Ansible
- Task scheduling: Run playbooks on a cron schedule — automated server maintenance
- Inventory management: Store and manage Ansible inventories in the UI
- Key/credential store: Securely store SSH keys, passwords, and vault passwords
- Team access: Role-based access — give teammates specific project access
- Lightweight: ~50MB RAM vs AWX's 8GB+ — runs on any small server
Semaphore vs AWX vs Ansible CLI
| Feature | Semaphore | AWX | Ansible CLI |
|---|---|---|---|
| RAM usage | ~50MB | ~8GB+ | N/A |
| Setup time | 5 min | 30+ min | N/A |
| Web UI | Yes (clean) | Yes (complex) | No |
| Scheduling | Yes | Yes | External (cron) |
| RBAC | Basic | Advanced | N/A |
| API | REST | REST | N/A |
| Workflow engine | No | Yes | No |
| Notifications | Email, Telegram, Slack | Email, Slack, webhook | N/A |
| Best for | Small-medium teams | Enterprise | Individual |
Part 1: Docker Setup
# docker-compose.yml
services:
semaphore:
image: semaphoreui/semaphore:latest
container_name: semaphore
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- semaphore_data:/var/lib/semaphore
environment:
SEMAPHORE_DB_DIALECT: bolt # Or: postgres, mysql
# For PostgreSQL:
# SEMAPHORE_DB_DIALECT: postgres
# SEMAPHORE_DB_HOST: db
# SEMAPHORE_DB_PORT: 5432
# SEMAPHORE_DB_NAME: semaphore
# SEMAPHORE_DB_USER: semaphore
# SEMAPHORE_DB_PASS: ${DB_PASSWORD}
SEMAPHORE_ADMIN_PASSWORD: "${ADMIN_PASSWORD}"
SEMAPHORE_ADMIN_NAME: admin
SEMAPHORE_ADMIN_EMAIL: admin@yourdomain.com
SEMAPHORE_ADMIN: admin
SEMAPHORE_ACCESS_KEY_ENCRYPTION: "${ENCRYPTION_KEY}" # openssl rand -base64 32
volumes:
semaphore_data:
echo "ADMIN_PASSWORD=$(openssl rand -base64 16)" >> .env
echo "ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env
docker compose up -d
Part 2: HTTPS with Caddy
ansible.yourdomain.com {
reverse_proxy localhost:3000
}
Part 3: Project Setup
Create a project
- + New Project
- Name:
Server Management
Add key store entries
Store credentials securely:
Key Store → + Add Key:
# SSH key for server access:
Name: Server SSH Key
Type: SSH Key
Private Key: (paste your private key)
# Ansible Vault password:
Name: Vault Password
Type: Login with Password
Password: your-vault-password
# Git credentials:
Name: Git Credentials
Type: Login with Password
Username: git-user
Password: git-token
Add inventory
Inventory → + Add:
# Static inventory:
Name: Production Servers
Type: Static
Content:
[webservers]
web1.yourdomain.com
web2.yourdomain.com
[databases]
db1.yourdomain.com
[all:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
Add repository
Repositories → + Add:
Name: Playbooks
URL: https://git.yourdomain.com/infra/ansible-playbooks.git
Branch: main
Access Key: Git Credentials (from key store)
Part 4: Running Playbooks
Create a task template
Task Templates → + Add:
Name: Update All Servers
Playbook: playbooks/update-servers.yml
Inventory: Production Servers
Repository: Playbooks
Environment: (optional extra variables)
Vault Password: Vault Password (from key store)
Run a task
- Task Templates → Update All Servers → Run
- Watch real-time output in the browser
- See success/failure for each host
- View full logs after completion
Extra variables
Pass variables at runtime:
{
"target_version": "2.3.1",
"restart_services": true,
"dry_run": false
}
Part 5: Scheduling
Cron schedules
Task Templates → [template] → Schedules → + Add:
Cron Expression: 0 3 * * * # Daily at 3 AM
# Or: 0 */6 * * * # Every 6 hours
# Or: 0 3 * * 0 # Weekly on Sunday
Common automation schedules
| Task | Schedule | Cron |
|---|---|---|
| OS updates | Weekly Sunday 3 AM | 0 3 * * 0 |
| Certificate renewal | Daily 2 AM | 0 2 * * * |
| Log rotation | Daily midnight | 0 0 * * * |
| Backup verification | Weekly | 0 4 * * 1 |
| Security scan | Daily 5 AM | 0 5 * * * |
| Docker prune | Weekly | 0 3 * * 6 |
Part 6: Notifications
Settings → Notifications → Email:
SMTP Host: mail.yourdomain.com
SMTP Port: 587
From: semaphore@yourdomain.com
Telegram
Settings → Notifications → Telegram:
Bot Token: your-telegram-bot-token
Chat ID: your-chat-id
Slack / Microsoft Teams
Settings → Notifications → Slack:
Webhook URL: https://hooks.slack.com/services/...
Notifications trigger on:
- Task success
- Task failure
- Schedule start
Part 7: Team Access
Add users
Settings → Users → + Add:
Username: alice
Email: alice@yourdomain.com
Role: Manager / Task Runner / Guest
Roles
| Role | Capabilities |
|---|---|
| Admin | Full access, manage users and projects |
| Manager | Create/edit templates, run tasks, manage inventories |
| Task Runner | Run existing templates only |
| Guest | View task history only |
Project-level access
Each project can have different team members with different roles — a developer might have Task Runner access to staging but Guest access to production.
Part 8: REST API
BASE="https://ansible.yourdomain.com/api"
# Login and get token:
TOKEN=$(curl -s -X POST "$BASE/auth/login" \
-H "Content-Type: application/json" \
-d '{"auth": "admin", "password": "your-password"}' \
| jq -r '.token')
# List projects:
curl "$BASE/projects" \
-H "Authorization: Bearer $TOKEN" | jq '.[].name'
# Run a task:
curl -X POST "$BASE/project/1/tasks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"template_id": 1,
"environment": "{\"target_version\": \"2.3.1\"}"
}'
# Get task output:
curl "$BASE/project/1/tasks/42/output" \
-H "Authorization: Bearer $TOKEN"
Maintenance
# Update:
docker compose pull
docker compose up -d
# Backup:
tar -czf semaphore-backup-$(date +%Y%m%d).tar.gz \
$(docker volume inspect semaphore_semaphore_data --format '{{.Mountpoint}}')
# Logs:
docker compose logs -f semaphore
See all open source DevOps tools at OSSAlt.com/categories/devops.