Semaphore v2.19.17 Docker Compose 部署指南

Semaphore v2.19.17 官方 semaphoreui/semaphore 镜像 Docker Compose 部署实录:MySQL 主线从 0 到 1 部署验证、SQLite/PostgreSQL 方言对比、数据持久化、config.json 生成与挂载、requirements.txt 自动装依赖、排障实录与运维命令速查。

SemaphoreAnsibleDevOps自动化自托管自动化部署2026/10/24

Semaphore v2.19.17 Docker Compose 部署指南

目标:用官方 semaphoreui/semaphore 镜像 + Docker Compose 从 0 到 1 部署一套可用的 Semaphore v2.19.17(Ansible Web 控制台),覆盖 MySQL 主线部署、SQLite/PostgreSQL 方言切换、数据持久化验证、config.json 配置文件挂载、Python 依赖注入与常见故障排查。本文全部命令与回显均在 Ubuntu 22.04(x86_64,内核 5.15)+ Docker Compose v2 环境实测通过,部署形态为 Semaphore Server 内置 Runner 单机版。

一、环境信息

本节列出实测环境的软硬件信息与部署后各服务监听情况,便于对照复现。

  • 操作系统:Ubuntu 22.04(x86_64)
  • Docker:Docker Engine 24+,Docker Compose v2(docker compose 子命令形式)
  • Semaphore 版本:v2.19.17(镜像 semaphoreui/semaphore:v2.19.17,启动后自报版本 v2.19.17-f8cefe6-1792391878)
  • 数据库:MySQL 8.0(主线);SQLite / PostgreSQL 14(方言对比实测)
  • 宿主端口:8151(映射到 Semaphore 的 3000 端口,避开宿主机已占用端口)
  • 部署目录:/opt/semaphore/(下文所有 docker-compose.yml、数据目录均放此处)
  • 管理账号:admin / q1w2e3r4(部署时通过环境变量初始化)

二、镜像选型说明

Semaphore 官方在 Docker Hub 发布两个镜像,选型前先分清用途:

镜像用途适用场景
semaphoreui/semaphoreSemaphore Server(Web UI + API + 调度),内置本地 Runner单机一体化部署,绝大多数场景
semaphoreui/runner独立任务执行器(Runner),需注册到已有 Server任务量大、需要把执行节点与 Web 节点分离时

版本 tag 如何选:生产部署必须锁定具体版本 tag(如 v2.19.17),不要用 latest——latest 随官方发布漂移,升级时数据库迁移不可逆,回滚困难。本文所有配置均以 v2.19.17 为准。镜像拉取实测(Digest 为 v2.19.17 当前线上摘要,可用于校验):

docker pull semaphoreui/semaphore:v2.19.17
v2.19.17: Pulling from semaphoreui/semaphore
Digest: sha256:008dacd9f1adbd92e8a048f1786ae1dd9bed2a352aca2eb30b9837de5706e7f7
Status: Image is up to date for semaphoreui/semaphore:v2.19.17
docker.io/semaphoreui/semaphore:v2.19.17

💡 说明:Semaphore 的组件模型(Server 与 Runner 分离、社区版功能边界)在 《Semaphore v2.19.17 介绍与选型指南》中有完整展开,本文聚焦部署落地。

三、前置条件

开始部署前确认以下条件全部满足,每一项都给出验证命令与实测回显。

3.1 Docker 与 Compose 可用

docker --version && docker compose version
Docker version 24.0.7, build afdb53d
Docker Compose version v2.24.5

3.2 目标端口未被占用

Semaphore 默认监听 3000,宿主机映射端口可自选。选端口前先用 ss 确认空闲:

ss -ltn | grep -E ':(8151|8133)\b' || echo "ports free"
ports free

⚠️ 排障实录:本次部署最初选的 8133 端口在正式执行时被宿主机上一个 Java 进程抢占,docker compose up 报 failed to bind host port 0.0.0.0:8133/tcp: address already in use(完整排障过程见第十二节 FAQ 1)。部署前务必先 ss -ltn 核对。

3.3 磁盘与内存

Semaphore Server 本身是 Go 单进程,空载内存约 100–200MB;MySQL 8.0 建议预留 1GB 以上内存与 5GB 磁盘(镜像约 1GB + 数据库初始数据)。任务执行时 Ansible playbook 会克隆代码仓库到临时目录,按最大仓库体积预留 /tmp 或自定义 tmp_path 的磁盘空间。

四、部署步骤(MySQL 主线)

本节从空目录开始,完成 docker-compose.yml 编写、启动、初始化验证全流程。MySQL 是官方文档的默认推荐方言,作为本文主线。

4.1 创建部署目录

mkdir -p /opt/semaphore/data/semaphore-config && cd /opt/semaphore

(命令无输出,data/semaphore-config 是 Semaphore 配置目录的挂载点,首次启动时会自动写入 config.json。)

4.2 编写 docker-compose.yml

在 /opt/semaphore/docker-compose.yml 写入以下内容(与官方文档模板一致,仅将端口映射改为 8151、密码改为本文统一示例口令):

name: semaphore-v2.19.17-stack
services:
  mysql:
    restart: unless-stopped
    image: mysql:8.0
    hostname: mysql
    volumes:
      - ./data/mysql:/var/lib/mysql
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
      MYSQL_DATABASE: semaphore
      MYSQL_USER: semaphore
      MYSQL_PASSWORD: q1w2e3r4
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-usemaphore", "-pq1w2e3r4"]
      interval: 5s
      timeout: 5s
      retries: 20
  semaphore:
    restart: unless-stopped
    ports:
      - "8151:3000"
    image: semaphoreui/semaphore:v2.19.17
    volumes:
      - ./data/semaphore-config:/etc/semaphore
    environment:
      SEMAPHORE_DB_USER: semaphore
      SEMAPHORE_DB_PASS: q1w2e3r4
      SEMAPHORE_DB_HOST: mysql
      SEMAPHORE_DB_PORT: 3306
      SEMAPHORE_DB_DIALECT: mysql
      SEMAPHORE_DB: semaphore
      SEMAPHORE_PLAYBOOK_PATH: /tmp/semaphore/
      SEMAPHORE_ADMIN_PASSWORD: q1w2e3r4
      SEMAPHORE_ADMIN_NAME: admin
      SEMAPHORE_ADMIN_EMAIL: admin@localhost
      SEMAPHORE_ADMIN: admin
      SEMAPHORE_ACCESS_KEY_ENCRYPTION: IrPXhBcHhRyjt42Ej+eq7tdyicGzPW38Sh7GzitQ1mk=
      SEMAPHORE_LDAP_ACTIVATED: 'no'
      TZ: Asia/Shanghai
    depends_on:
      mysql:
        condition: service_healthy

networks:
  default:
    name: semaphore-v2.19.17-net

逐项说明:

  • name: semaphore-v2.19.17-stack:Compose 项目名。显式指定可避免多套部署目录同名导致 docker compose down 误停其他实例。
  • mysql 服务:MYSQL_RANDOM_ROOT_PASSWORD 让 root 密码随机生成(业务库用独立的 semaphore 账号,不用 root);./data/mysql 把数据库文件持久化到宿主机,重建服务不丢数据。
  • healthcheck:用 mysqladmin ping 探活,depends_on 的 condition: service_healthy 保证 Semaphore 严格等 MySQL 就绪后才启动——Semaphore 启动时要连库执行迁移,库没就绪会启动失败。
  • semaphore 服务:8151:3000 把 Web 端口映射到宿主机 8151;./data/semaphore-config 挂载到 /etc/semaphore,首次启动自动生成的 config.json 落在宿主机该目录,可审查、可备份。
  • SEMAPHORE_ACCESS_KEY_ENCRYPTION:加密数据库中访问密钥(Key Store)的 32 字节 base64 密钥,用 head -c32 /dev/urandom | base64 生成,每套部署必须独立生成并妥善保管——丢失该密钥,数据库里已存的 Key Store 条目将无法解密。本文值仅为示例。

4.3 启动

docker compose up -d
 Network semaphore-v2.19.17-net Creating
 Network semaphore-v2.19.17-net Created
 Container semaphore-v21917-stack-mysql-1 Creating
 Container semaphore-v21917-stack-mysql-1 Created
 Container semaphore-v2.19.17-web Creating
 Container semaphore-v2.19.17-web Created
 Container semaphore-v21917-stack-mysql-1 Starting
 Container semaphore-v21917-stack-mysql-1 Started
 Container semaphore-v21917-stack-mysql-1 Waiting
 Container semaphore-v21917-stack-mysql-1 Healthy
 Container semaphore-v2.19.17-web Starting
 Container semaphore-v2.19.17-web Started

💡 技术拓展:depends_on 有两种等待语义——短格式(depends_on: [mysql])只保证启动顺序不保证可用性;长格式(condition: service_healthy)配合 healthcheck 才是真正的"依赖就绪"。常见 condition 对比:

condition行为适用场景
service_started依赖容器已启动(默认)依赖无健康检查能力的轻量服务
service_healthy依赖健康检查通过数据库类、需要初始化完成的服务
service_completed_successfully依赖容器成功退出一次性初始化任务(如迁移脚本)

Semaphore 强依赖数据库完成初始化,必须用 service_healthy。

4.4 首次启动过程解读

首次启动时 Semaphore 检测到配置目录没有 config.json,会用 SEMAPHORE_ADMIN_* 等环境变量自动执行无人值守初始化(交互式问答的自动应答版):生成 config.json → 连库执行 268 项数据库迁移 → 创建管理员账号。通过 docker compose logs semaphore 可以看到完整过程(节选关键段落,实测原文):

Pinging database on mysql port 3306...
Generating setup file  ...
Executing semaphore setup

Hello! You will now be guided through a setup to:

1. Set up configuration for a MySQL/MariaDB database
2. Set up a path for your playbooks (auto-created)
3. Run database Migrations
4. Set up initial semaphore user & password

What database to use:
   1 - MySQL
   2 - BoltDB (NOT SUPPORTED)
   3 - PostgreSQL
   4 - SQLite
 (default 1):
db Hostname (default 127.0.0.1:3306):
db User (default root):
db Password:
db Name (default semaphore):
Playbook path (default /tmp/semaphore):
Public URL (optional, example: https://example.com/semaphore):
Enable email alerts? (yes/no) (default no):
Enable telegram alerts? (yes/no) (default no):
Enable slack alerts? (yes/no) (default no):
Enable Rocket.Chat alerts? (yes/no) (default no):
Enable Microsoft Team Channel alerts? (yes/no) (default no):
Enable LDAP authentication? (yes/no) (default no):
Config output directory (default /home/semaphore):
Running: mkdir -p /etc/semaphore..
Configuration written to /etc/semaphore/config.json..
 Pinging db..
Running db Migrations..
Executing migration v0.0.0 (at 2026-10-24 02:05:54.356595607 +0000 UTC)...
Creating migrations table
 [1/268] [2/127] [3/301] [4/269] [5/290] [6/314] [7/238] [8/593] [9/198] [10/274] [11/238] [12/0]
Executing migration v1.0.0 (at 2026-10-24 02:05:54.466363147 +0000 UTC)...
 [1/59] [2/57] [3/82] [4/87]
...
Executing migration v2.19.14 (at 2026-10-24 02:05:58.127657261 +0000 UTC)...
 [1/0]
Migrations Finished

time="2026-10-24T10:05:58+08:00" level=warning msg="no rows in result set" fields.level=Warn
 > Username:  > Email:  > Your name:  > Password:
 You are all setup admin!
 Re-launch this program pointing to the configuration file

./semaphore server --config /etc/semaphore/config.json

 To run as daemon:

nohup ./semaphore server --config /etc/semaphore/config.json &

 You can login with admin@localhost or admin.
No additional python dependencies to install
Starting semaphore server
MySQL semaphore@mysql:3306 semaphore
Tmp Path (projects home) /tmp/semaphore
Semaphore v2.19.17-f8cefe6-1792391878
Interface
Port :3000
Server is running

关键信息点:Configuration written to /etc/semaphore/config.json 说明配置文件已生成;Migrations Finished 表示从 v0.0.0 到 v2.19.14 的全部迁移执行完毕;末尾 MySQL semaphore@mysql:3306 semaphore + Server is running 确认服务在 MySQL 方言下正常运行(no rows in result set 的 Warn 是首次启动时任务表为空的正常提示,非错误)。

4.5 验证部署结果

① 服务状态:

docker compose ps
NAME                             IMAGE                            COMMAND                   SERVICE     CREATED       STATUS                 PORTS
semaphore-v2.19.17-web           semaphoreui/semaphore:v2.19.17   "/sbin/tini -- /usr/…"   semaphore   2 hours ago   Up 2 hours             0.0.0.0:8151->3000/tcp, [::]:8151->3000/tcp
semaphore-v21917-stack-mysql-1   mysql:8.0                        "docker-entrypoint.s…"   mysql       2 hours ago   Up 2 hours (healthy)   3306/tcp, 33060/tcp

② API 探活:Semaphore 未提供无认证的 ping 端点,用登录接口返回 204 判定服务可用:

curl -s -o /dev/null -w '%{http_code}' -X POST http://localhost:8151/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"auth":"admin","password":"q1w2e3r4"}'
204

③ 身份与会话验证:先登录拿 Cookie,再取当前用户信息:

curl -s -c cookie.txt -X POST http://localhost:8151/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"auth":"admin","password":"q1w2e3r4"}' -o /dev/null
curl -s -b cookie.txt http://localhost:8151/api/user
{"id":1,"created":"2026-10-24T02:05:58Z","username":"admin","name":"admin","email":"admin@localhost","admin":true,"external":false,"alert":false,"pro":false,"can_create_project":true,"has_active_subscription":false}

④ Web UI:浏览器访问 http://<服务器IP>:8151/,用 admin / q1w2e3r4 登录即可进入项目列表页。

四项全过,部署完成。

五、数据持久化说明

本节说明哪些数据落在宿主机、如何验证"重建服务不丢数据"。

持久化清单:

数据宿主机位置挂载点说明
MySQL 数据./data/mysql/var/lib/mysql全部业务数据(项目、模板、任务历史、用户)
Semaphore 配置./data/semaphore-config/etc/semaphoreconfig.json(含三个加密密钥)
临时工作目录(未挂载,容器内 /tmp/semaphore)/tmp/semaphore任务临时文件、仓库克隆缓存,可随时重建,无需持久化

⚠️ 最容易被忽略的持久化项是 config.json 里的三个密钥:cookie_hash、cookie_encryption、access_key_encryption。access_key_encryption 加密着数据库中的 Key Store(SSH 私钥、API Token 等),必须单独备份——只有数据库备份、没有密钥备份,恢复出来的 Key Store 全部无法解密。

重建验证实测:向系统写入一条数据(创建名为 demo 的项目),随后 docker compose down 删除服务与网络(数据目录保留),再 docker compose up -d 重建,验证数据完好:

docker compose down && docker compose up -d
 Container semaphore-v2.19.17-web  Stopping
 Container semaphore-v2.19.17-web  Stopped
 Container semaphore-v21917-stack-mysql-1  Stopping
 Container semaphore-v21917-stack-mysql-1  Stopped
 Container semaphore-v21917-stack-mysql-1  Removing
 Container semaphore-v2.19.17-web  Removing
 Container semaphore-v2.19.17-web  Removed
 Container semaphore-v21917-stack-mysql-1  Removed
 Network semaphore-v2.19.17-net  Removing
 Network semaphore-v2.19.17-net  Removed
 Network semaphore-v2.19.17-net Creating
 Network semaphore-v2.19.17-net Created
 Container semaphore-v21917-stack-mysql-1 Creating
 Container semaphore-v21917-stack-mysql-1 Created
 Container semaphore-v2.19.17-web Creating
 Container semaphore-v2.19.17-web Created
 Container semaphore-v21917-stack-mysql-1 Starting
 Container semaphore-v21917-stack-mysql-1 Started
 Container semaphore-v21917-stack-mysql-1 Waiting
 Container semaphore-v21917-stack-mysql-1 Healthy
 Container semaphore-v2.19.17-web Starting
 Container semaphore-v2.19.17-web Started

重建后重新登录并查询项目列表(/api/user 登录 → /api/projects 查询):

login=204
[{"id":1,"name":"demo","created":"2026-10-24T02:00:15Z","type":""}]

demo 项目仍在,持久化验证通过。注意二次启动日志不再出现迁移过程——config.json 已存在,Semaphore 直接读配置连库启动,docker compose logs semaphore 尾部为:

semaphore-v2.19.17-web  | Interface
semaphore-v2.19.17-web  | Port :3000
semaphore-v2.19.17-web  | Server is running

六、环境变量表

Semaphore 镜像通过环境变量接收配置,首次启动时将其转换为 config.json。部署常用变量如下(均为实测生效项):

环境变量作用示例值 / 说明
SEMAPHORE_DB_DIALECT数据库方言mysql / postgres / sqlite(v2.16+ 支持 sqlite)
SEMAPHORE_DB_HOST数据库主机主机名或 IP;MySQL 形如 mysql:3306 可带端口
SEMAPHORE_DB_PORT数据库端口3306(MySQL)/ 5432(PostgreSQL)
SEMAPHORE_DB_USER数据库用户名semaphore
SEMAPHORE_DB_PASS数据库密码与 MySQL 服务的 MYSQL_PASSWORD 一致
SEMAPHORE_DB数据库名semaphore;SQLite 方言时此变量存数据库文件路径
SEMAPHORE_PLAYBOOK_PATH项目临时目录(旧名)/tmp/semaphore/;等价于配置项 tmp_path
SEMAPHORE_ADMIN初始管理员登录名admin
SEMAPHORE_ADMIN_NAME初始管理员显示名admin
SEMAPHORE_ADMIN_EMAIL初始管理员邮箱admin@localhost(也是登录名之一)
SEMAPHORE_ADMIN_PASSWORD初始管理员密码仅首次初始化生效,之后改密码走 Web UI 或 CLI
SEMAPHORE_ACCESS_KEY_ENCRYPTIONKey Store 加密密钥head -c32 /dev/urandom | base64 生成
SEMAPHORE_LDAP_ACTIVATED是否启用 LDAP(旧式单目录)'no';多目录 LDAP 见 v2.20+ 的 ldap_providers
TZ时区Asia/Shanghai(影响日志时间戳与定时任务,见第七节 schedule.timezone)

关键规则:

  1. 环境变量优先于 config.json。首次启动后若直接改环境变量(如改数据库密码),重启后新值会覆盖 config.json 里的旧值——两处都存在时以环境变量为准。
  2. SEMAPHORE_ADMIN_PASSWORD 只在首次生成 config.json 时消费一次;之后修改该变量不会改密码(防误改),重置密码用 semaphore users change-by-login(见 《Semaphore v2.19.17 常用 CLI 命令与工具详解》)。
  3. 敏感变量支持 _FILE 后缀形态:SEMAPHORE_ADMIN_PASSWORD_FILE: /run/secrets/admin_pw 表示从文件读值,Docker Swarm / secrets 场景用,避免密码明文进 Compose 文件。

七、配置参数说明(config.json)

首次启动生成的 config.json 是 Semaphore 的核心配置文件,挂载在宿主机 ./data/semaphore-config/config.json。MySQL 主线实测生成结果(密钥为实测随机值,已按示例处理):

{
        "mysql": {
                "host": "mysql:3306",
                "user": "semaphore",
                "pass": "q1w2e3r4",
                "name": "semaphore"
        },
        "dialect": "mysql",
        "tmp_path": "/tmp/semaphore",
        "cookie_hash": "f5I+atUJX6MA5xNnpRimZqDKYAM8EzqEjlXn6b5w5zg=",
        "cookie_encryption": "aoSl2CFTBhktRX83U1ivNJ8TXKWW95UAqIpZDylMN8A=",
        "access_key_encryption": "9hm9XNftIltnY05Q50cLHXvr/Vdsj6RmmZiz5M8QEus="
 }

逐项说明:

  • mysql.host/user/pass/name:数据库连接四要素,来自 SEMAPHORE_DB_* 环境变量;host 格式 主机:端口。
  • dialect:数据库引擎,mysql / postgres / sqlite 三选一。首次启动前必须定死,之后更换方言需要自行迁移数据,程序不做自动转换。
  • tmp_path:任务临时目录(playbook 仓库克隆、ansible 运行缓存的根),默认 /tmp/semaphore,可改为大容量分区路径。
  • cookie_hash / cookie_encryption:会话 Cookie 的哈希与加密密钥,首次初始化自动随机生成;cookie_encryption 更换后所有用户会话立即失效(需重新登录)。
  • access_key_encryption:Key Store 加密密钥(来自 SEMAPHORE_ACCESS_KEY_ENCRYPTION),决定数据库中 SSH 密钥、密码等敏感条目的加解密。

三个密钥的完整生成命令:

head -c32 /dev/urandom | base64
IrPXhBcHhRyjt42Ej+eq7tdyicGzPW38Sh7GzitQ1mk=

(每次执行输出都不同,属正常现象。)

config.json 改动如何生效:该文件只在 Semaphore 进程启动时读取一次,改完必须重启服务:

docker compose restart semaphore
 Container semaphore-v2.19.17-web Restarting
 Container semaphore-v2.19.17-web Started

💡 说明:config.json 可配置项远不止初始生成的这六项——定时任务时区(schedule.timezone)、git 客户端与重试(git_client/git_attempts)、任务并发(max_parallel_tasks)、TLS、通知渠道等共 130+ 项,逐项含义与默认值在 《Semaphore v2.19.17 配置参数详解》中按官方 Reference 全量展开,部署阶段先掌握本节六项即可。

八、配置文件挂载与自定义 Python 依赖

本节介绍 config 目录的另一个用途:放入 requirements.txt 让镜像启动时自动安装额外的 Python 依赖(部分 Ansible 模块/集合需要)。

用法:在配置目录(挂载到 /etc/semaphore 的目录)放一个 requirements.txt:

netaddr

重新创建服务(restart 不会重新读取挂载内容变化,需要 up -d 触发重建或直接重启后观察日志):

docker compose up -d --force-recreate semaphore
docker compose logs --tail 10 semaphore

实测日志(netaddr 被自动安装):

Installing additional python dependencies
...Collecting netaddr (from -r /etc/semaphore/requirements.txt (line 1))
Installing collected packages: netaddr
Successfully installed netaddr-1.3.0
[notice] A new release of pip is available: 26.2.1 -> 26.2.2
[notice] To update, run: pip install --upgrade pip

行为要点(官方文档明示 + 实测印证):

  1. 启动时检查 ${SEMAPHORE_CONFIG_PATH}/requirements.txt(默认 /etc/semaphore/requirements.txt),存在则执行 pip3 install --upgrade -r;不存在则打印 No additional python dependencies to install 继续(首次启动日志可见此行)。
  2. 包安装在镜像内置的 Ansible 虚拟环境中(镜像已把该 venv 放在 PATH 首位),任务进程可直接使用。
  3. 每次启动都会重新安装,包不跨容器重建持久化——依赖多、或需要离线启动时,改用自定义镜像预装:
FROM semaphoreui/semaphore:v2.19.17
COPY requirements.txt /tmp/requirements.txt
RUN pip3 install --no-cache-dir -r /tmp/requirements.txt
  1. pip 失败会阻断启动:包名写错、缺编译依赖、断网,都会让服务起不来,排查看 docker compose logs semaphore 里的 pip 输出。

九、多数据库方言部署对比(SQLite / PostgreSQL 实测)

Semaphore 支持三种数据库方言。官方模板主线是 MySQL/PostgreSQL,v2.16 起也支持 SQLite(单文件、零依赖)。本节给出两种替代方言的完整可用配置与实测启动回显,供轻量场景选型。

💡 技术拓展:三种方言如何选——

方言运维成本适用规模特点
MySQL 8.0需维护独立数据库服务中大型、团队共用官方主线推荐,生态成熟,备份工具链完整
PostgreSQL 14需维护独立数据库服务中大型与 MySQL 同级,官方模板一等公民,函数/JSON 能力更强
SQLite零依赖,单文件个人/小团队/演示v2.16+ 支持,一个文件即全部数据,备份=拷文件;并发写弱

本文主线采用 MySQL(团队场景与官方默认一致);个人单机试验推荐 SQLite。

9.1 SQLite 方言(单服务)

单服务即可运行,无需数据库服务。docker-compose-sqlite.yml:

name: semaphore-v2.19.17-stack
services:
  semaphore:
    restart: unless-stopped
    ports:
      - "8151:3000"
    image: semaphoreui/semaphore:v2.19.17
    container_name: semaphore-v2.19.17-web-sqlite
    volumes:
      - ./data/semaphore-config-sqlite:/etc/semaphore
    environment:
      SEMAPHORE_DB_DIALECT: sqlite
      SEMAPHORE_DB_HOST: /etc/semaphore/semaphore.sqlite
      SEMAPHORE_PLAYBOOK_PATH: /tmp/semaphore/
      SEMAPHORE_ADMIN_PASSWORD: q1w2e3r4
      SEMAPHORE_ADMIN_NAME: admin
      SEMAPHORE_ADMIN_EMAIL: admin@localhost
      SEMAPHORE_ADMIN: admin
      SEMAPHORE_ACCESS_KEY_ENCRYPTION: 2iZj7YsSt7gqc+ftF49MCLqwI71F4r2w6+xp9+zwNjc=
      SEMAPHORE_LDAP_ACTIVATED: 'no'
      TZ: Asia/Shanghai

说明:SQLite 方言下 SEMAPHORE_DB_HOST 填数据库文件路径(无需端口与账号),放在挂载的配置目录里即自动持久化到宿主机。启动实测:

docker compose -f docker-compose-sqlite.yml up -d
docker compose -f docker-compose-sqlite.yml ps
 Container semaphore-v2.19.17-web-sqlite Creating
 Container semaphore-v2.19.17-web-sqlite Created
 Container semaphore-v2.19.17-web-sqlite Starting
 Container semaphore-v2.19.17-web-sqlite Started
NAME                            IMAGE                            COMMAND                   SERVICE     CREATED          STATUS          PORTS
semaphore-v2.19.17-web-sqlite   semaphoreui/semaphore:v2.19.17   "/sbin/tini -- /usr/…"   semaphore   35 seconds ago   Up 35 seconds   0.0.0.0:8151->3000/tcp, [::]:8151->3000/tcp

启动日志确认方言(SQLite @... 行),登录验证 204:

No additional python dependencies to install
Starting semaphore server
SQLite @/var/lib/semaphore/database.sqlite /etc/semaphore/semaphore.sqlite
Tmp Path (projects home) /tmp/semaphore
Semaphore v2.19.17-f8cefe6-1792391878
Interface
Port :3000
Server is running
sqlite_login=204

配置目录落地文件实测(semaphore.sqlite 及 WAL 伴生文件):

-rw-r--r-- 1 1001 root    333 10月 24 10:09 config.json
-rw-r--r-- 1 1001 root 606208 10月 24 10:09 semaphore.sqlite
-rw-r--r-- 1 1001 root  32768 10月 24 10:09 semaphore.sqlite-shm
-rw-r--r-- 1 1001 root      0 10月 24 10:09 semaphore.sqlite-wal

9.2 PostgreSQL 方言

把 MySQL 服务替换为 PostgreSQL 即可,docker-compose-postgres.yml:

name: semaphore-v2.19.17-stack
services:
  postgres:
    restart: unless-stopped
    image: postgres:14
    hostname: postgres
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: semaphore
      POSTGRES_PASSWORD: q1w2e3r4
      POSTGRES_DB: semaphore
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U semaphore -d semaphore"]
      interval: 5s
      timeout: 5s
      retries: 20
  semaphore:
    restart: unless-stopped
    ports:
      - "8151:3000"
    image: semaphoreui/semaphore:v2.19.17
    container_name: semaphore-v2.19.17-web-pg
    volumes:
      - ./data/semaphore-config-pg:/etc/semaphore
    environment:
      SEMAPHORE_DB_USER: semaphore
      SEMAPHORE_DB_PASS: q1w2e3r4
      SEMAPHORE_DB_HOST: postgres
      SEMAPHORE_DB_PORT: 5432
      SEMAPHORE_DB_DIALECT: postgres
      SEMAPHORE_DB: semaphore
      SEMAPHORE_PLAYBOOK_PATH: /tmp/semaphore/
      SEMAPHORE_ADMIN_PASSWORD: q1w2e3r4
      SEMAPHORE_ADMIN_NAME: admin
      SEMAPHORE_ADMIN_EMAIL: admin@localhost
      SEMAPHORE_ADMIN: admin
      SEMAPHORE_ACCESS_KEY_ENCRYPTION: 2iZj7YsSt7gqc+ftF49MCLqwI71F4r2w6+xp9+zwNjc=
      SEMAPHORE_LDAP_ACTIVATED: 'no'
      TZ: Asia/Shanghai
    depends_on:
      postgres:
        condition: service_healthy

与 MySQL 版的差异仅三处:数据库服务换成 postgres:14(pg_isready 探活)、Semaphore 侧 SEMAPHORE_DB_DIALECT: postgres + SEMAPHORE_DB_PORT: 5432 + SEMAPHORE_DB_HOST: postgres。启动实测:

docker compose -f docker-compose-postgres.yml up -d
docker compose -f docker-compose-postgres.yml ps
 Container semaphore-v21917-stack-postgres-1 Creating
 Container semaphore-v21917-stack-postgres-1 Created
 Container semaphore-v2.19.17-web-pg Creating
 Container semaphore-v2.19.17-web-pg Created
 Container semaphore-v21917-stack-postgres-1 Starting
 Container semaphore-v21917-stack-postgres-1 Started
 Container semaphore-v21917-stack-postgres-1 Waiting
 Container semaphore-v21917-stack-postgres-1 Healthy
 Container semaphore-v2.19.17-web-pg Starting
 Container semaphore-v2.19.17-web-pg Started
NAME                                IMAGE                            COMMAND                   SERVICE     CREATED          STATUS                    PORTS
semaphore-v2.19.17-web-pg           semaphoreui/semaphore:v2.19.17   "/sbin/tini -- /usr/…"   semaphore   41 seconds ago   Up 35 seconds             0.0.0.0:8151->3000/tcp, [::]:8151->3000/tcp
semaphore-v21917-stack-postgres-1   postgres:14                      "docker-entrypoint.s…"   postgres    41 seconds ago   Up 40 seconds (healthy)   5432/tcp

启动日志确认 Postgres semaphore@postgres:5432 semaphore + Server is running:

No additional python dependencies to install
Starting semaphore server
Postgres semaphore@postgres:5432 semaphore
Tmp Path (projects home) /tmp/semaphore
Semaphore v2.19.17-f8cefe6-1792391878
Interface
Port :3000
Server is running

⚠️ 方言切换注意:三种方言的数据库结构相同,但数据迁移程序不做自动转换。已在 MySQL 上积累数据后想换 PostgreSQL,需要自行导出导入(项目级备份可用 CLI 的 semaphore projects export,见命令详解篇),不要指望改个环境变量就完成切换。

十、等价 docker run 方式

不用 Compose 的场景(脚本化单机部署、临时验证),可用 docker run 直接起。最小可用形态(SQLite、数据全落本地目录):

docker network create semaphore-net
docker run -d --name semaphore-web \
  --network semaphore-net \
  -p 8151:3000 \
  -v /opt/semaphore/data/semaphore-config-run:/etc/semaphore \
  -e SEMAPHORE_DB_DIALECT=sqlite \
  -e SEMAPHORE_DB_HOST=/etc/semaphore/semaphore.sqlite \
  -e SEMAPHORE_PLAYBOOK_PATH=/tmp/semaphore/ \
  -e SEMAPHORE_ADMIN_PASSWORD=q1w2e3r4 \
  -e SEMAPHORE_ADMIN_NAME=admin \
  -e SEMAPHORE_ADMIN_EMAIL=admin@localhost \
  -e SEMAPHORE_ADMIN=admin \
  -e SEMAPHORE_ACCESS_KEY_ENCRYPTION=2iZj7YsSt7gqc+ftF49MCLqwI71F4r2w6+xp9+zwNjc= \
  -e SEMAPHORE_LDAP_ACTIVATED=no \
  -e TZ=Asia/Shanghai \
  --restart unless-stopped \
  semaphoreui/semaphore:v2.19.17

验证:

docker logs --tail 5 semaphore-web
Starting semaphore server
SQLite @/var/lib/semaphore/database.sqlite /etc/semaphore/semaphore.sqlite
Tmp Path (projects home) /tmp/semaphore
Semaphore v2.19.17-f8cefe6-1792391878
Port :3000

MySQL 形态则需要先起数据库、再起 Semaphore 并用 --network 让两者同网互通,SEMAPHORE_DB_HOST 指向数据库容器名——等价关系与 Compose 版一一对应,不再赘述。生产环境仍建议用 Compose(声明式、可版本化、depends_on 健康检查语义完整)。

十一、常用运维命令

本节汇总日常运维高频命令,全部在本文部署形态下实测。

① 启停与状态:

docker compose ps                     # 服务状态与健康检查
docker compose stop semaphore         # 只停 Semaphore(保留 MySQL)
docker compose start semaphore        # 恢复
docker compose restart semaphore      # 改 config.json 后重启生效
docker compose down                   # 停止并删除服务与网络(数据目录保留)
docker compose up -d                  # 重建启动

stop/start 实测回显:

 Container semaphore-v2.19.17-web  Stopping
 Container semaphore-v2.19.17-web  Stopped
 Container semaphore-v2.19.17-web  Starting
 Container semaphore-v2.19.17-web  Started

② 日志:

docker compose logs -f semaphore          # 跟踪实时日志
docker compose logs --tail 20 semaphore   # 最近 20 行

正常稳定态日志尾部(实测):

semaphore-v2.19.17-web  | Starting semaphore server
semaphore-v2.19.17-web  | MySQL semaphore@mysql:3306 semaphore
semaphore-v2.19.17-web  | Tmp Path (projects home) /tmp/semaphore
semaphore-v2.19.17-web  | Semaphore v2.19.17-f8cefe6-1792391878
semaphore-v2.19.17-web  | Interface
semaphore-v2.19.17-web  | Port :3000
semaphore-v2.19.17-web  | Server is running

③ 版本确认(进入服务内部执行 CLI):

docker compose exec semaphore semaphore version
v2.19.17-f8cefe6-1792391878

④ 数据库备份与恢复(MySQL 主线):

# 备份(业务账号即可,无需 root)
docker compose exec mysql sh -c \
  'mysqldump -usemaphore -pq1w2e3r4 semaphore' > /opt/semaphore/backup/semaphore-$(date +%F).sql

实测备份文件头部(确认导出有效):

-- MySQL dump 10.13  Distrib 8.0.46, for Linux (x86_64)
--
-- Host: localhost    Database: semaphore
-- ------------------------------------------------------
-- Server version       8.0.46

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;

恢复用 docker compose exec -T mysql sh -c 'mysql -usemaphore -pq1w2e3r4 semaphore' < 备份文件。务必连同 config.json(三个密钥)一起备份,原因见第五节。

⑤ 升级:改 docker-compose.yml 中的镜像 tag 到目标版本 → docker compose up -d → 观察日志中的自动迁移输出(与首次启动同格式,Migrations Finished 即成功)。升级前先完成第④步备份;跨大版本升级前在测试环境演练回滚路径。

十二、常见问题(FAQ)

以下问题均为本次部署实际踩到并解决的案例,非理论枚举。

Q1:up -d 报 address already in use,服务起不来。

实测报错(docker compose up -d 输出):

 Container semaphore-v2.19.17-web Recreated
 Container semaphore-v21917-stack-mysql-1 Waiting
 Container semaphore-v21917-stack-mysql-1 Healthy
 Container semaphore-v2.19.17-web Starting
Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint semaphore-v2.19.17-web (96a3d0acb6ce8ff3bb4fef4c8929a2db13190f8311ffaad644225a7ca140997d): failed to bind host port 0.0.0.0:8133/tcp: address already in use

定位:宿主机 8133 已被其他进程监听。用 ss -ltn | grep 8133 实测确认:

LISTEN 0      4096               *:8133             *:*    users:(("java",pid=3749258,fd=226))

解决:把 docker-compose.yml 里的端口映射改到空闲端口(本文用 8151),重新 docker compose up -d,服务正常 Started。选端口前先 ss -ltn 核对是唯一可靠做法——写文档时的"常用端口"清单不可信,以实时监听表为准。

Q2:启动即崩溃重启,日志 panic: open /etc/semaphore/config.json: permission denied。

实测日志(docker compose logs semaphore):

Running: mkdir -p /etc/semaphore..
panic: open /etc/semaphore/config.json: permission denied

goroutine 1 [running]:
github.com/semaphoreui/semaphore/cli/setup.SaveConfig({0x48d2a20, 0xe9f7d5af508}, {0x2e26755?, 0xb?}, {0x0?, 0xe9f7d3dd7c0?})
        /go/src/semaphore/cli/setup/setup.go:247 +0x3ea

定位:镜像内 Semaphore 进程以非 root 用户(UID 1001)运行,而挂载的宿主机配置目录属 root 且不可写,首次初始化写 config.json 失败直接 panic。

解决:初始化前把宿主机配置目录属主改为 1001(或放开写权限):

chown -R 1001:0 /opt/semaphore/data/semaphore-config
docker compose up -d

重建后日志出现 Configuration written to /etc/semaphore/config.json..,初始化正常完成。判断依据:挂载目录里生成的文件实测属主为 1001 root(见 9.1 节文件列表)。

Q3:二次启动卡在 Waiting 后一直 Restarting,日志报数据库连接失败。

定位:Semaphore 启动第一步是连数据库执行迁移,MySQL 未就绪时连接被拒。若 depends_on 用了短格式(只保证顺序),MySQL 还在初始化(首启要生成随机 root 密码、建库建用户,需数十秒)时 Semaphore 就会抢跑失败。

解决:改用 condition: service_healthy(见 4.2 节配置),让 Compose 等 healthcheck 通过再启动 Semaphore。实测时序:mysql-1 Started → mysql-1 Waiting → mysql-1 Healthy → web Starting → web Started。

Q4:SEMAPHORE_ADMIN_PASSWORD 改了但登录不上新密码。

原因:该变量只在首次初始化(生成 config.json 时)消费一次,之后修改无效。

解决:改密码走 Web UI 用户设置页,或用 CLI:

docker compose exec semaphore semaphore users change-by-login --login admin --password '新密码' --config /etc/semaphore/config.json

实测回显:

User admin <admin@localhost> changed!

(CLI 子命令全集见 《Semaphore v2.19.17 常用 CLI 命令与工具详解》。)

Q5:首次启动日志有 level=warning msg="no rows in result set",是不是错误?

不是。首次初始化在迁移完成后查询任务/用户表做统计,空库查询无结果属正常,该 Warn 不影响初始化与后续运行(实测该行出现在 Migrations Finished 之后、You are all setup admin! 之前,部署最终验证全部通过)。

十三、参考链接