Skip to content

About

Custom docker container for Semaphore UI

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

Semaphore with Ansible cache support

A thin derived Docker image on top of the official semaphoreui/semaphore (Ansible Semaphore UI, built on Alpine). It adds Ansible fact/inventory caching backed by Redis, a few controller-side Ansible runtime packages, and predefined environment defaults that pair with the cache.

Localized README: Русский

What it adds

The image layers three concerns on top of the base:

  1. Ansible runtime packages, installed via apk using the Alpine py3-* naming convention. All are controller-side plugins:

    • py3-redis — backs the community.general.redis cache plugin.
    • py3-jmespath — used by the json_query filter and several Ansible lookups (kept explicit even though the base currently ships it).
    • py3-netaddr — enables the ipaddr/ipsubnet/ipmath filters.
    • py3-dnspython — backs the community.general.dig lookup.
    • py3-paramiko — backs the paramiko_ssh/network_cli connection plugins.
  2. Predefined Ansible cache environment variables that configure community.general.redis as the fact/inventory cache backend:

    Variable Default Purpose
    ANSIBLE_INVENTORY_CACHE True Enable inventory caching.
    ANSIBLE_CACHE_PLUGIN community.general.redis Fact cache plugin.
    ANSIBLE_INVENTORY_CACHE_PLUGIN community.general.redis Inventory cache plugin.
    ANSIBLE_CACHE_PLUGIN_CONNECTION localhost:6379:0 Redis connection (host:port:db).

    Override ANSIBLE_CACHE_PLUGIN_CONNECTION at runtime to point at the actual Redis host.

  3. Behavioral environment defaults that pair with the Redis cache:

    Variable Default Purpose
    ANSIBLE_GATHERING smart Reuse cached facts, don't re-gather.
    ANSIBLE_RETRY_FILES_ENABLED False Don't write .retry files.

    Only universally-safe, controller-side defaults are baked. Fleet- or host-dependent knobs (ANSIBLE_FORKS, ANSIBLE_PIPELINING, ANSIBLE_CACHE_PLUGIN_TIMEOUT) are left to the deployment env — ANSIBLE_PIPELINING in particular breaks on targets with requiretty in sudoers.

Usage

Build

docker build -t semaphore-custom .

Smoke-test

Confirm the added packages import from Ansible's own venv interpreter (not just the system python3):

make smoke

Run

Point the cache at a real Redis instance:

docker run -d --name semaphore \
  -p 3000:3000 \
  -e ANSIBLE_CACHE_PLUGIN_CONNECTION=redis-host:6379:0 \
  semaphore-custom

Refer to the official Semaphore documentation for the base image's own configuration (database, admin user, etc.).

Run with Docker Compose

A full example stack — Semaphore plus the Redis cache backend, a PostgreSQL database, and persistent volumes — is provided in docker-compose.yml. Fill in the CHANGE-ME secrets, then:

docker compose up -d

Override the base tag

The base tag defaults to latest but can be overridden at build time without editing the Dockerfile:

docker build --build-arg BASE_TAG=v2.18.12 -t semaphore-custom .

Persistence

The image declares no VOLUMEs — all state lives in the container's writable layer and is lost when the container is removed. Mount these paths explicitly to persist data:

  • /etc/semaphore (SEMAPHORE_CONFIG_PATH) — required. Holds config.json, including the access-key encryption secret. Lose it and the credentials stored in the database can no longer be decrypted.
  • /var/lib/semaphore (SEMAPHORE_DB_PATH) — required for the embedded database (database.boltdb / database.sqlite). Not needed with an external MySQL/Postgres.
  • /tmp/semaphore (SEMAPHORE_TMP_PATH) — cloned repositories and task working directories/output. Optional cache: safe to lose (repos re-clone), worth a volume to avoid re-cloning large repositories.

Self-contained run with an embedded SQLite database and persisted volumes:

docker run -d --name semaphore -p 3000:3000 \
  -e SEMAPHORE_DB_DIALECT=sqlite \
  -e SEMAPHORE_ADMIN=admin -e SEMAPHORE_ADMIN_PASSWORD=changeme \
  -e SEMAPHORE_ADMIN_NAME=Admin -e SEMAPHORE_ADMIN_EMAIL=admin@localhost \
  -e SEMAPHORE_ACCESS_KEY_ENCRYPTION="$(head -c32 /dev/urandom | base64)" \
  -v semaphore-config:/etc/semaphore \
  -v semaphore-db:/var/lib/semaphore \
  -v semaphore-tmp:/tmp/semaphore \
  semaphore-custom

Configuration

These variables belong to the upstream base image, not this layer; the bundled server-wrapper reads them on startup. The most common ones:

Server / web

Variable Purpose
SEMAPHORE_PORT Web UI port (default 3000).
SEMAPHORE_INTERFACE Bind to a specific network interface.
SEMAPHORE_WEB_ROOT Base path when served under a subpath.
SEMAPHORE_TLS_ENABLED Serve HTTPS (with cert/key file vars).

Database

Variable Purpose
SEMAPHORE_DB_DIALECT sqlite, bolt, mysql, or postgres.
SEMAPHORE_DB_HOST DB host, or file path for sqlite/bolt.
SEMAPHORE_DB_NAME Schema name (MySQL/Postgres).
SEMAPHORE_DB_USER / SEMAPHORE_DB_PASS DB credentials.

First-run setup & secrets

Variable Purpose
SEMAPHORE_ADMIN / …_PASSWORD Initial admin account.
SEMAPHORE_ADMIN_NAME / …_EMAIL Initial admin profile.
SEMAPHORE_ACCESS_KEY_ENCRYPTION Encrypts stored access keys.
SEMAPHORE_COOKIE_HASH / …_ENCRYPTION Session cookie secrets.

Notifications (Telegram, Slack, Teams, …), remote runners, LDAP/OIDC/TOTP auth, high availability and structured logging are all configurable via further SEMAPHORE_* variables — see the official environment-variable reference.

A few behaviours of the bundled wrapper worth knowing:

  • Dialect default is mysql in the wrapper (the binary's own default is sqlite). For a self-contained image set SEMAPHORE_DB_DIALECT=sqlite or bolt, otherwise startup waits ~30s for a MySQL ping and then exits.
  • config.json is generated on first run only. Later changes to DB/admin env vars do not regenerate it — edit the file or start from an empty config volume to apply them.
  • *_FILE secrets. SEMAPHORE_DB_USER, SEMAPHORE_DB_PASS, SEMAPHORE_ADMIN, SEMAPHORE_ADMIN_PASSWORD, SEMAPHORE_LDAP_PASSWORD and SEMAPHORE_ACCESS_KEY_ENCRYPTION also accept a …_FILE variant pointing at a file (Docker/Compose secrets).
  • Extra Python deps without rebuilding. A requirements.txt in SEMAPHORE_CONFIG_PATH is pip installed on startup; since Ansible's venv uses --system-site-packages, packages like docker or hvac become importable by playbooks this way.

Notes

  • The base tag is pinned to :latest deliberately. Changing the default base image tag is a meaningful decision, not a routine bump.
  • All package installs live in a single RUN layer with --no-cache to avoid bloating the image with the apk cache.
  • The install step drops to USER root to install packages, then restores USER semaphore so the container does not run as root.
  • The build runs a layered verification (mirrored in make smoke): the added packages import through Ansible's own venv interpreter, the relied-on plugins load via ansible-doc, the baked env defaults applied, and the Semaphore base contract holds — so base drift fails the build instead of breaking silently at runtime.
  • A HEALTHCHECK probes the Semaphore web port (:3000). It is a liveness check: any HTTP response counts as healthy, a refused connection or timeout as unhealthy — so it survives Semaphore API changes across the rolling :latest base.

Links

Upstream Ansible Semaphore project (this image only extends it):

Changelog

See CHANGELOG.md.

About

Custom docker container for Semaphore UI

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages