Skip to main content

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

FeatureSemaphoreAWXAnsible CLI
RAM usage~50MB~8GB+N/A
Setup time5 min30+ minN/A
Web UIYes (clean)Yes (complex)No
SchedulingYesYesExternal (cron)
RBACBasicAdvancedN/A
APIRESTRESTN/A
Workflow engineNoYesNo
NotificationsEmail, Telegram, SlackEmail, Slack, webhookN/A
Best forSmall-medium teamsEnterpriseIndividual

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

  1. + New Project
  2. 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

  1. Task Templates → Update All Servers → Run
  2. Watch real-time output in the browser
  3. See success/failure for each host
  4. 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

TaskScheduleCron
OS updatesWeekly Sunday 3 AM0 3 * * 0
Certificate renewalDaily 2 AM0 2 * * *
Log rotationDaily midnight0 0 * * *
Backup verificationWeekly0 4 * * 1
Security scanDaily 5 AM0 5 * * *
Docker pruneWeekly0 3 * * 6

Part 6: Notifications

Email

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

RoleCapabilities
AdminFull access, manage users and projects
ManagerCreate/edit templates, run tasks, manage inventories
Task RunnerRun existing templates only
GuestView 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.

Comments