---
title: "Deploy Semaphore UI"
description: "Web UI for running Ansible, Terraform and Bash automation"
category: "Automation"
url: https://railway.com/deploy/semaphore
---

# Deploy Semaphore UI

Web UI for running Ansible, Terraform and Bash automation

**[Deploy Semaphore UI on Railway](https://railway.com/template/semaphore)**

- **Creator:** A3A
- **Category:** Automation

## Template content

### semaphore https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/semaphore.svg

- **Source:** https://github.com/gridalpha/semaphore-ui-railway
- **Health check:** /api/ping
- **Public domain:** Yes

### Postgres https://devicons.railway.app/i/postgresql.svg

- **Image:** ghcr.io/railwayapp-templates/postgres-ssl:18

### runner https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/semaphore.svg

- **Source:** https://github.com/gridalpha/semaphore-ui-railway

## Documentation

# Deploy and Host Semaphore UI on Railway

Semaphore UI is an open-source web interface and REST API for running Ansible playbooks, OpenTofu and Terraform plans, Terragrunt stacks, Bash scripts and PowerShell. Teams self-host Semaphore UI when playbooks have outgrown one engineer's laptop: it keeps inventories, SSH keys and cloud credentials in an encrypted key store, records who ran what and when, streams live output to the browser and exposes every operation over an API.

Deploy Semaphore UI on Railway and you get the production shape, not a single box. Three services come up together: the **semaphore** server, serving the web UI and API on a public domain; a dedicated **runner**, the process that checks out your repository and runs the playbook; and managed **Postgres**, holding projects, templates, history and the encrypted key store. The server never executes automation itself, so a runaway playbook cannot take the UI down. Both application services keep a volume — the server's holds the encryption keys generated on first boot, the runner's the credential it is issued at registration.

![Diagram of the Semaphore, runner and Postgres services on Railway](https://res.cloudinary.com/rroe4rtk/image/upload/v1788316469/semaphore-ui-architecture.png)

## Getting Started with Semaphore UI on Railway

Set `SEMAPHORE_ADMIN` and `SEMAPHORE_ADMIN_PASSWORD` before deploying: they become the first administrator, created once the server finishes its migrations. When the deploy goes green, open the public URL and sign in. Semaphore has no public sign-up: admins create every other account, so the instance is closed from the first request. You land on the New Project screen; the fastest way to prove the stack works is to name a project and press **Create Demo Project**, which seeds a real repository, two inventories and eight task templates. Open **Task Templates**, press the run arrow beside *Ping semaphoreui.com* and confirm: the log should show the task assigned to a runner, cloning the repository, installing the playbook's Galaxy collections and printing a real ping result — one run that exercises the database, the scheduler, the runner and the private network. Then swap in your own repository, credentials under **Key Store** and hosts under **Inventory**.

![Semaphore task templates for Ansible, OpenTofu and Terragrunt](https://res.cloudinary.com/rroe4rtk/image/upload/v1788316472/semaphore-ui-task-templates.png)
![Live Ansible playbook log streaming from a Semaphore runner](https://res.cloudinary.com/rroe4rtk/image/upload/v1788316473/semaphore-ui-ansible-task-log.png)
![Semaphore admin listing an online default global runner](https://res.cloudinary.com/rroe4rtk/image/upload/v1788316474/semaphore-ui-runner-fleet.png)

## About Hosting Semaphore UI

Ansible is excellent at describing work and poor at sharing it: playbooks end up on one machine, with one person's SSH agent, and nobody else can see what ran. Semaphore UI closes that gap without a heavyweight orchestrator — one Go binary with an embedded Vue front end.

- **Multiple tools** — Ansible, OpenTofu, Terraform, Terragrunt, Bash and PowerShell templates in one image
- **Encrypted key store** — SSH keys and cloud credentials encrypted at rest, the key held outside it
- **Schedules and webhooks** — cron schedules plus incoming hooks from GitHub, GitLab or Bitbucket
- **Alerting and SSO** — failure notifications by email, Slack, Teams or Gotify; LDAP, OIDC and TOTP

The **semaphore** service holds the API, web assets, scheduler and task queue; it reaches **Postgres** over the private network and hands each queued task to a registered runner, which clones the repository, installs what the run needs and streams output back.

## Why Deploy Semaphore UI on Railway

Railway removes the parts of self-hosting Semaphore that are not about automation.

- Managed PostgreSQL, wired to the app before first boot
- Private networking between server, runner and database
- HTTPS and a public domain with no reverse proxy to configure
- Volumes, so encryption keys and runner registrations survive a redeploy
- Add another runner service when one worker is not enough
- Upgrades are a redeploy from the source repository

## Common Use Cases

- **Shared infrastructure runbooks** — a button for playbooks that lived on one laptop, with an audit trail
- **Scheduled maintenance** — nightly patching, certificate renewal or backup checks as cron jobs
- **Terraform and OpenTofu without a laptop** — plans and applies from one place, credentials in the key store

## Dependencies for Semaphore UI

- **semaphore** — web UI and API, from `Dockerfile.server` in [gridalpha/semaphore-ui-railway](https://github.com/gridalpha/semaphore-ui-railway) on `docker.io/semaphoreui/semaphore:v2.19.12`. Public, port 3000, volume `/var/lib/semaphore`.
- **runner** — execution tier, from `Dockerfile.runner` on `docker.io/semaphoreui/runner:v2.19.12`. Private, with a volume.
- **Postgres** — managed PostgreSQL: projects, templates, history, users, key store.

Both images are pinned to the same tag: the runner speaks the server's internal API, so the two upgrade together.

### Environment Variables Reference

| Variable | Service | Purpose |
|---|---|---|
| `SEMAPHORE_ADMIN` | semaphore | Username of the first administrator |
| `SEMAPHORE_ADMIN_PASSWORD` | semaphore | Password for that administrator |
| `SEMAPHORE_WEB_ROOT` | semaphore | Public URL; enables secure cookies |
| `SEMAPHORE_RUNNER_REGISTRATION_TOKEN` | both | Token a runner presents when registering |
| `SEMAPHORE_USE_REMOTE_RUNNER` | semaphore | Send every task to the runner |

### Deployment Dependencies

- Source:  · Upstream:  · Docs: 
- Bundled: Ansible 13.5.0, OpenTofu 1.11.0, Terraform 1.11.3, Terragrunt 0.78.0, `git`, `openssh-client`, `sshpass`

## Hardware Requirements for Self-Hosting Semaphore UI

| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 vCPU per service | 2 vCPU server, 2+ vCPU runner |
| RAM | 512 MB server, 512 MB runner | 1 GB server, 2 GB runner |
| Storage | 1 GB per volume | 5 GB per volume, plus database |
| Runtime | Go on Alpine, PostgreSQL 13+ | PostgreSQL 16+ |

The server idles under 256 MB. Sizing matters on the runner, which holds checkouts and provider binaries and forks a process per task — raise its memory before `SEMAPHORE_RUNNER_MAX_PARALLEL_TASKS`.

## Self-Hosting Semaphore UI

The quickest local trial is the embedded database in one container:

```
docker run -d --name semaphore -p 3000:3000 \
  -e SEMAPHORE_DB_DIALECT=bolt \
  -e SEMAPHORE_ADMIN=admin -e SEMAPHORE_ADMIN_PASSWORD=changeme \
  -v semaphore-data:/var/lib/semaphore \
  semaphoreui/semaphore:v2.19.12
```

For anything real, use PostgreSQL and split the runner out, as this Compose fragment shows:

```
services:
  semaphore:
    image: semaphoreui/semaphore:v2.19.12
    environment:
      SEMAPHORE_DB_DIALECT: postgres
      SEMAPHORE_DB_HOST: db
      SEMAPHORE_USE_REMOTE_RUNNER: "true"
      SEMAPHORE_RUNNER_REGISTRATION_TOKEN: shared-token
  runner:
    image: semaphoreui/runner:v2.19.12
    environment:
      SEMAPHORE_WEB_ROOT: http://semaphore:3000
      SEMAPHORE_RUNNER_ENABLED: "true"
      SEMAPHORE_RUNNER_REGISTRATION_TOKEN: shared-token
  db:
    image: postgres:16
```

A runner registered by hand arrives switched off and not marked default, so untagged tasks report `no runners available` until you enable it in **Admin → Runners**. The template does that on first boot.

## How Much Does Semaphore UI Cost to Self-Host?

Semaphore UI is free and open source under the MIT licence — no seat limits, no account required. A paid Pro edition adds clustering, visual workflows and enterprise secret backends; nothing here depends on it. On Railway you pay only for the compute, database and volumes the three services use.

## FAQ

**What is Semaphore UI?**
An open-source web UI and REST API for running Ansible, OpenTofu, Terraform, Terragrunt, Bash and PowerShell automation, with inventories, an encrypted key store, scheduling and teams. Formerly Ansible Semaphore; unrelated to Semaphore CI.

**What does this Railway template deploy?**
The Semaphore server on a public HTTPS domain, one remote runner that executes every task, and managed PostgreSQL. Server and runner each have a volume; only the server is public.

**Why does the template include a separate runner service?**
Splitting execution off the control plane is Semaphore's own production shape: a heavy playbook cannot slow the UI, and the container running your automation holds no admin credentials.

**Why does this template need PostgreSQL?**
Semaphore keeps projects, templates, inventories, history, users and the encrypted key store in a relational database. The embedded database suits a single-user trial; PostgreSQL is what upstream recommends for a shared instance.

**How do I add more runners to self-hosted Semaphore UI?**
Add another service from `Dockerfile.runner` with its own volume, a distinct `SEMAPHORE_RUNNER_NAME` and the same registration token; it registers itself on first boot. Do not raise the replica count on an existing runner — replicas share one volume and one identity.

**Can I trigger Semaphore UI from a Git push or CI?**
Yes. Each task template can expose an incoming webhook under **Integrations**, matched on headers or payload fields, and every operation is available over the REST API with a user token.

**Will my SSH keys and secrets survive a redeploy?**
Yes — they are encrypted in the database with a key kept on the server's volume, generated once on first boot. Keep that volume, and back up the database before major upgrades.


## Similar templates

- [N8N Main + Worker](https://railway.com/deploy/n8n-main-worker) — Deploy and Host N8N with Inactive worker.
- [Evolution API with n8n](https://railway.com/deploy/evolution-api-with-n8n) — Automate WhatsApp workflows with Evolution API, n8n, and Postgres.
- [Postgres Backup](https://railway.com/deploy/postgres-s3-backups) — Cron-based PostgreSQL backup to bucket storage

Open this page in a browser: https://railway.com/deploy/semaphore
