Overview

Configure the server

  1. config.json — the primary and most flexible source.
  2. Environment variables — every option has one, which is what you want in containers and Kubernetes.
  3. The online configuratorsemaphoreui.com/install generates a starting file in the browser.

The naming rule is mechanical: a config key access_key_encryption is the environment variable SEMAPHORE_ACCESS_KEY_ENCRYPTION; a nested key ha.redis.addr is SEMAPHORE_HA_REDIS_ADDR.

Generate a configuration file#

The interactive setup writes a config.json, runs the database migrations and creates the first admin user:

semaphore setup

For a runner, the equivalent is:

semaphore runner setup

A working example#

{
  "dialect": "mysql",
  "mysql": {
    "host": "127.0.0.1:3306",
    "user": "semaphore",
    "pass": "***",
    "name": "semaphore"
  },
 
  "git_client": "go_git",
 
  "auth": {
    "totp": {
      "enabled": true,
      "allow_recovery": true
    }
  },
 
  "use_remote_runner": true,
  "runner_registration_token": "73fs***",
 
  "tmp_path": "/tmp/semaphore",
  "cookie_hash": "96Nt***",
  "cookie_encryption": "x0bs***",
  "access_key_encryption": "j1ia***",
 
  "max_tasks_per_template": 3,
 
  "schedule": {
    "timezone": "UTC"
  },
 
  "log": {
    "events": {
      "enabled": true,
      "path": "./events.log"
    }
  },
 
  "process": {
    "chroot": "/opt/semaphore/sandbox"
  }
}

Start the server with it:

semaphore server --config ./config.json

Settings worth getting right first#

Setting Environment variable Why it matters
access_key_encryption SEMAPHORE_ACCESS_KEY_ENCRYPTION Encrypts every credential in the Key Store. Generate with head -c32 /dev/urandom | base64. Lose it and you lose your secrets.
cookie_hash, cookie_encryption SEMAPHORE_COOKIE_HASH, SEMAPHORE_COOKIE_ENCRYPTION Protect session cookies. Generated by semaphore setup.
dialect + database block SEMAPHORE_DB_DIALECT, SEMAPHORE_DB_* sqlite, mysql or postgres. SQLite and BoltDB cannot be used for high availability.
port, interface SEMAPHORE_PORT, SEMAPHORE_INTERFACE Where the web UI listens. Default :3000, all interfaces.
web_host SEMAPHORE_WEB_ROOT The public URL of the instance. Set it whenever Semaphore sits behind a reverse proxy or issues OIDC redirects.
tmp_path SEMAPHORE_TMP_PATH Where repositories are cloned and generated files land. Default /tmp/semaphore.
git_client SEMAPHORE_GIT_CLIENT cmd_git (default) or go_git.
max_tasks_per_template SEMAPHORE_MAX_TASKS_PER_TEMPLATE Task log retention per template. Unlimited by default — set it before your database grows unbounded.
schedule.timezone SEMAPHORE_SCHEDULE_TIMEZONE Schedules run in UTC unless you say otherwise. Use an IANA name, e.g. America/New_York.
use_remote_runner, runner_registration_token SEMAPHORE_USE_REMOTE_RUNNER, SEMAPHORE_RUNNER_REGISTRATION_TOKEN Required before any runner can register.
subscription.key SEMAPHORE_SUBSCRIPTION_KEY Activates Pro or Enterprise without touching the UI. See License activation.

The complete option table — several hundred keys — is maintained upstream at semaphoreui.com/docs/admin-guide/configuration.

Keeping secrets out of the environment#

Any sensitive option can be read from a file instead of an environment variable by appending _FILE to the variable name. This is the pattern to use with Docker secrets, Kubernetes secrets and systemd credentials:

SEMAPHORE_ADMIN_PASSWORD_FILE=/run/secrets/semaphore_admin_pw
SEMAPHORE_SUBSCRIPTION_KEY_FILE=/run/secrets/semaphore-license-key

When a value is supplied this way, Semaphore reads the file at startup — so rotating the secret needs a restart.

Application environment for your tools#

The configuration above is Semaphore's own. What Ansible, Terraform or your scripts see at runtime comes from Variable Groups, set per project rather than per server. See Inventory and variables.

Public URL behind a reverse proxy#

If Semaphore runs behind NGINX, Apache or Caddy, set the public URL explicitly so generated links and OIDC redirect URIs point at the proxy rather than at localhost:3000. The proxy itself must forward WebSocket upgrades on /api/ws, or live task logs will not stream — there is a working NGINX block in High availability.

Next steps#