Concourse 8.3.1 Docker Compose 部署指南
在 Ubuntu 22.04 上用 Docker Compose 部署 Concourse 8.3.1 三服务栈(web+worker+PostgreSQL):组件密钥生成、编排文件、fly 登录、hello-world 流水线设置与触发、Web UI 验证与常见问题排查,全程实测。
Concourse 8.3.1 Docker Compose 部署指南
本文以真实服务器视角记录使用 Docker Compose 部署 Concourse 8.3.1 三服务栈(web + worker + PostgreSQL)的全过程:组件密钥生成、编排文件编写、fly CLI 登录、hello-world 流水线设置与触发、Web UI 验证与常见问题排查,全部命令与输出均在 Ubuntu 22.04 实测环境中逐行照抄。
一、环境信息
- 操作系统:Ubuntu 22.04.4 LTS(内核 6.8.0-138-generic,x86_64,12 核 / 32G 内存)
- Docker 版本:
29.8.1(docker --version实测) - Docker Compose 版本:
v5.5.1(docker compose version实测) - Concourse 版本:
8.3.1(官方最新稳定版,GitHub Releases 查询日期 2026-10-20) - 软件包版本:
concourse/concourse:8.3.1(web 与 worker 共用)、postgres:16-alpine - 对外服务端口:
8148(映射到 Concourse web 默认 8080) - 编排网络:自定义网络
10.89.58.0/24 - 管理账号:
root/q1w2e3r4(本地认证,main 团队管理员) - 编排文件:
/opt/concourse/docker-compose.yml(项目名concourse-831-stack)
二、架构与组件说明
Concourse 采用"Web 节点 + Worker 节点 + PostgreSQL"结构,本方案用三个服务在一个宿主机上组成最小可用集群:
| 服务 | 软件包 | 进程 | 职责 |
|---|---|---|---|
db | postgres:16-alpine | postgres | 唯一持久化存储:流水线配置、构建历史、资源版本、团队与账号 |
web | concourse/concourse:8.3.1 | concourse web(ATC + TSA) | Web UI / REST API(8080)、调度构建、SSH worker 注册网关(2222) |
worker | concourse/concourse:8.3.1 | concourse worker | 运行构建任务(containerd 运行时)、Baggageclaim 卷管理、beacon 心跳注册 |
三个服务来自同一份官方软件包 concourse/concourse:8.3.1,通过 command 区分进程角色(web / worker),这是 Concourse 单二进制分发的典型用法。web 与 worker 之间通过 TSA 的 SSH 通道通信,worker 必须持有正确的 RSA 密钥材料才能注册成功(见第四节)。
三、前置条件
3.1 Docker 与 Compose
docker --version
docker compose version
实测输出:
Docker version 29.8.1, build 4a63305
Docker Compose version v5.5.1
3.2 选择服务端口
Concourse web 默认监听 8080,需映射到宿主一个空闲端口。先查看已占用端口:
ss -ltn | awk '{print $4}' | grep -E ':(81[0-9][0-9])$'
实测输出:
0.0.0.0:8144
0.0.0.0:8145
8148 未被占用,本文采用 8148:8080 映射。
3.3 选择自定义网络子网
为避免与宿主机上其他在跑的编排栈冲突,先盘点已占用的自定义网段,再从空闲槽位中显式选取:
docker network inspect $(docker network ls -q) --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}' | sort
实测输出:
beanstalkd-net 10.89.56.0/24
bridge 10.0.0.0/24
directus-12-5_directus-net 10.89.33.0/24
host
none
pyroscope-2-4-stack_pyroscope-net 10.89.62.0/24
stack_default 10.0.1.0/24
10.89.58.0/24 空闲,作为本栈专用网段写入编排文件。
3.4 获取软件包
从官方制品库获取两个软件包(内网或网络受限环境可先在联网机器导出再导入):
docker pull concourse/concourse:8.3.1
docker pull postgres:16-alpine
💡 说明:concourse/concourse 官方软件包体量约 1GB,包含 web、worker 两类进程的可执行文件与 containerd/guardian 运行时;postgres:16-alpine 约 270MB。若官方源拉取缓慢,可改用可达的内网制品仓库代理地址。
四、生成组件密钥
Concourse 的 web 与 worker 之间通过 SSH 通道完成注册认证,最少需要三组密钥:
| 密钥 | 类型 | 生成方式 | 使用方 |
|---|---|---|---|
| session_signing_key | RSA | ssh-keygen -m PEM(或 generate-key -t rsa) | web(签发用户会话令牌) |
| tsa_host_key | SSH | ssh-keygen -m PEM(或 generate-key -t ssh) | web 持私钥;worker 持公钥校验服务端 |
| worker_key | SSH | ssh-keygen -m PEM(或 generate-key -t ssh) | worker 持私钥;公钥列入 web 的 authorized_worker_keys |
创建目录并用宿主机 ssh-keygen 生成(官方文档同款参数:RSA 4096、PEM 编码;也可使用官方软件包自带的 generate-key 子命令,效果等价):
mkdir -p /opt/concourse/keys/web /opt/concourse/keys/worker
ssh-keygen -t rsa -b 4096 -m PEM -f /opt/concourse/keys/web/session_signing_key -N ''
ssh-keygen -t rsa -b 4096 -m PEM -f /opt/concourse/keys/web/tsa_host_key -N ''
ssh-keygen -t rsa -b 4096 -m PEM -f /opt/concourse/keys/worker/worker_key -N ''
cp /opt/concourse/keys/worker/worker_key.pub /opt/concourse/keys/web/authorized_worker_keys
cp /opt/concourse/keys/web/tsa_host_key.pub /opt/concourse/keys/worker/tsa_host_key.pub
实测输出(每条命令回显一致,摘录其一):
Generating public/private rsa key pair.
Your identification has been saved in /opt/concourse/keys/web/session_signing_key
Your public key has been saved in /opt/concourse/keys/web/session_signing_key.pub
The key fingerprint is:
SHA256:kSDPTuQ12ID+cqbciQYBBr5jd0PjISZRl5zcNkiGDFc root@ops
The key's randomart image is:
+---[RSA 4096]----+
|oo+oBEO+o |
|o.oo+%.*.o |
|.+ + +* + |
| = =oo . |
| + o =. S |
|. + o = |
| o B . |
| = o |
| . |
+----[SHA256]-----+
最终密钥目录结构:
/opt/concourse/keys/web/ # web 服务只读挂载
├── session_signing_key
├── tsa_host_key
├── tsa_host_key.pub
└── authorized_worker_keys # 即 worker_key.pub 的副本
/opt/concourse/keys/worker/ # worker 服务只读挂载
├── worker_key
├── worker_key.pub
└── tsa_host_key.pub # 必须有:worker 校验 TSA 服务端
⚠️ 实测踩坑:worker 侧目录若缺少
tsa_host_key.pub,进程将启动失败并不断重启,日志报error: invalid argument for flag '--tsa-public-key' (expected flag.AuthorizedKeys): failed to read authorized keys: open /concourse-keys/tsa_host_key.pub: no such file or directory。
五、编写 docker-compose.yml
创建 /opt/concourse/docker-compose.yml(编排项目名必须显式指定,避免与其他在跑栈同名互删):
name: concourse-831-stack
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: concourse
POSTGRES_USER: concourse_user
POSTGRES_PASSWORD: concourse_pass
PGDATA: /database
volumes:
- db-data:/database
healthcheck:
test: ["CMD-SHELL", "pg_isready -U concourse_user -d concourse"]
interval: 5s
timeout: 3s
retries: 10
web:
image: concourse/concourse:8.3.1
command: web
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "8148:8080"
environment:
CONCOURSE_CLUSTER_NAME: quzuo-lab
CONCOURSE_EXTERNAL_URL: http://192.168.146.128:8148
CONCOURSE_POSTGRES_HOST: db
CONCOURSE_POSTGRES_PORT: "5432"
CONCOURSE_POSTGRES_DATABASE: concourse
CONCOURSE_POSTGRES_USER: concourse_user
CONCOURSE_POSTGRES_PASSWORD: concourse_pass
CONCOURSE_ADD_LOCAL_USER: root:q1w2e3r4
CONCOURSE_MAIN_TEAM_LOCAL_USER: root
CONCOURSE_SESSION_SIGNING_KEY: /concourse-keys/session_signing_key
CONCOURSE_TSA_HOST_KEY: /concourse-keys/tsa_host_key
CONCOURSE_TSA_AUTHORIZED_KEYS: /concourse-keys/authorized_worker_keys
volumes:
- ./keys/web:/concourse-keys:ro
worker:
image: concourse/concourse:8.3.1
command: worker --containerd-dns-proxy-enable --containerd-dns-server=223.5.5.5
restart: unless-stopped
privileged: true
stop_signal: SIGUSR2
depends_on:
- web
environment:
CONCOURSE_NAME: worker-01
CONCOURSE_RUNTIME: containerd
CONCOURSE_BAGGAGECLAIM_DRIVER: overlay
CONCOURSE_TSA_HOST: web:2222
CONCOURSE_TSA_PUBLIC_KEY: /concourse-keys/tsa_host_key.pub
CONCOURSE_TSA_WORKER_PRIVATE_KEY: /concourse-keys/worker_key
volumes:
- ./keys/worker:/concourse-keys:ro
networks:
default:
name: concourse-831-net
driver: bridge
ipam:
config:
- subnet: 10.89.58.0/24
volumes:
db-data:
要点说明:
CONCOURSE_EXTERNAL_URL写宿主机实际可达地址(浏览器与 fly 都经由它访问)。CONCOURSE_ADD_LOCAL_USER+CONCOURSE_MAIN_TEAM_LOCAL_USER组合创建本地管理员账号并授权到 main 团队。- worker 必须
privileged: true(containerd 运行时需要完整的内核能力),优雅停止信号用SIGUSR2。 - worker 的 DNS 代理参数在 8.3.1 中以命令行参数方式显式给出最稳妥(实测该两个参数的环境变量写法不生效,详见第十节常见问题)。
六、启动服务栈
cd /opt/concourse && docker compose up -d
实测输出:
Container concourse-831-stack-db-1 Waiting
Container concourse-831-stack-db-1 Healthy
Container concourse-831-stack-web-1 Starting
Container concourse-831-stack-web-1 Started
Container concourse-831-stack-worker-1 Starting
Container concourse-831-stack-worker-1 Started
查看栈状态:
docker compose ps
实测输出:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
concourse-831-stack-db-1 postgres:16-alpine "docker-entrypoint.s…" db 41 minutes ago Up 41 minutes (healthy) 5432/tcp
concourse-831-stack-web-1 concourse/concourse:8.3.1 "dumb-init /usr/loca…" web 41 minutes ago Up 41 minutes 0.0.0.0:8148->8080/tcp, [::]:8148->8080/tcp
concourse-831-stack-worker-1 concourse/concourse:8.3.1 "dumb-init /usr/loca…" worker About a minute ago Up About a minute
web 服务就绪后,atc.listening 与 tsa.listening 日志出现即代表 API 与注册网关均正常:
docker compose logs web --tail 3
实测输出(节选):
web-1 | {"timestamp":"2026-10-20T06:40:42.803214056Z","level":"info","source":"tsa","message":"tsa.listening","data":{}}
web-1 | {"timestamp":"2026-10-20T06:40:42.805293710Z","level":"info","source":"atc","message":"atc.listening","data":{"debug":"127.0.0.1:8079","http":"0.0.0.0:8080"}}
worker 成功注册的关键日志为 worker.beacon-runner.beacon.registered:
worker-1 | {"timestamp":"2026-10-20T06:41:35.406301957Z","level":"info","source":"worker","message":"worker.beacon-runner.beacon.registered","data":{"session":"4.1"}}
七、安装 fly CLI
fly 从 Concourse web 服务直接下载,天然与服务端版本一致:
curl -sL 'http://127.0.0.1:8148/api/v1/cli?arch=amd64&platform=linux' -o /usr/local/bin/fly
chmod +x /usr/local/bin/fly
fly --version
实测输出:
8.3.1
💡 v7.14.0 起也可从浏览器访问
http://192.168.146.128:8148/download-fly下载对应平台的 fly(实测该页面返回 HTTP 200)。
八、fly 登录与流水线设置
8.1 登录
fly --target main login -c http://127.0.0.1:8148 -u root -p q1w2e3r4
实测输出:
logging in to team 'main'
target saved
登录成功后凭据保存在 ~/.flyrc,后续命令用 -t main 引用该目标。
8.2 编写 hello-world 流水线
创建 pipelines/hello-world.yml:
jobs:
- name: hello-world
plan:
- task: say-hello
config:
platform: linux
image_resource:
type: registry-image
source:
repository: docker.1ms.run/library/busybox
tag: latest
run:
path: echo
args: ["hello world"]
💡 内网环境请把
repository换成内部制品仓库代理地址;registry-image资源类型在构建初始化阶段会先做远端版本检查。
8.3 set-pipeline 设置流水线
fly -t main set-pipeline -p hello-world -c pipelines/hello-world.yml
命令回显配置差异并等待确认(输入 y 回车):
实测输出:
jobs:
job hello-world has been added:
+ name: hello-world
+ plan:
+ - config:
+ image_resource:
+ name: ""
+ source:
+ repository: docker.1ms.run/library/busybox
+ tag: latest
+ type: registry-image
+ platform: linux
+ run:
+ args:
+ - hello world
+ path: echo
+ task: say-hello
pipeline name: hello-world
apply configuration? [yN]: y
确认后配置保存。新流水线默认处于 paused 状态,查看列表:
fly -t main pipelines
实测输出(paused 列为 yes):
2 hello-world yes no 2026-10-20 15:13:29 +0800 CST
⚠️ 实测提示:8.3.1 的 set-pipeline 确认交互是原始键盘模式(TUI)。在真实终端里输入
y即完成;若通过管道/脚本非交互输入(如echo y | fly ...),客户端可能出现bailing out字样的退出信息,这属于非终端环境下的交互怪癖——此时应以fly pipelines的实际结果为准判断是否保存成功。纯脚本场景可用-n参数配合 API 完成,或保持人工终端操作。
8.4 解除暂停
fly -t main unpause-pipeline -p hello-world
实测输出:
unpaused 'hello-world'
再次查看列表,paused 已变为 no:
2 hello-world no no 2026-10-20 15:13:29 +0800 CST
九、触发构建与验证
9.1 触发 hello-world 并实时观察
fly -t main trigger-job -j hello-world/hello-world --watch
实测输出(下载进度条逐帧刷新,此处截取首末帧):
started hello-world/hello-world #6
initializing
initializing check: image
selected worker: worker-01
fetching docker.1ms.run/library/busybox@sha256:fd7dc98638c8e305f4dc34e979f1c0fdfdcaeb0fbf8fcff77ae834b6da3d7e6e
37bb94b0940b [----------------------------------------------] 0.0b/2.1MiB
……
37bb94b0940b [============>-----------------------------] 682.3KiB/2.1MiB
37bb94b0940b [============================================] 2.1MiB/2.1MiB
selected worker: worker-01
running echo hello world
hello world
succeeded
首跑耗时约 13 秒(含远端版本检查与基础环境首次拉取);再次触发时命中 worker 本地资源缓存,8 秒内完成。
9.2 构建历史与作业状态
fly -t main builds -j hello-world/hello-world
实测输出(前 4 行):
6 hello-world/hello-world/6 succeeded 2026-10-20@15:21:23+0800 2026-10-20@15:21:36+0800 13s main root
5 hello-world/hello-world/5 errored 2026-10-20@15:20:03+0800 2026-10-20@15:20:03+0800 0s main root
4 hello-world/hello-world/4 errored 2026-10-20@15:18:43+0800 2026-10-20@15:18:43+0800 0s main root
3 hello-world/hello-world/3 errored 2026-10-20@15:17:43+0800 2026-10-20@15:17:43+0800 0s main root
fly -t main jobs -p hello-world
实测输出:
hello-world no succeeded n/a
9.3 服务端信息核验
curl -s http://127.0.0.1:8148/api/v1/info
实测输出:
{"version":"8.3.1","worker_version":"3.0","feature_flags":{"build_rerun":false,"cache_streamed_volumes":false,"global_resources":false,"resource_causality":false},"external_url":"http://192.168.146.128:8148","cluster_name":"quzuo-lab"}
9.4 Web UI 验证
浏览器访问 http://192.168.146.128:8148:
- 页面标题为
Concourse,跳转到登录页; - 使用
root / q1w2e3r4登录 main 团队; - 首页可见
hello-world流水线拓扑图(一个 job 节点),点击节点进入构建详情; - 构建页可看到
say-hello任务的实时日志(hello world)与绿色succeeded状态。
命令行同样可核验页面可达性:
curl -sI http://127.0.0.1:8148/ | head -1
实测输出:
HTTP/1.1 200 OK
十、常见问题与排查
10.1 worker 反复重启:缺 tsa_host_key.pub
现象:worker 服务状态 Restarting,日志报 error: invalid argument for flag '--tsa-public-key' (expected flag.AuthorizedKeys): failed to read authorized keys: open /concourse-keys/tsa_host_key.pub: no such file or directory。
处理:把 web 侧的 tsa_host_key.pub 拷贝到 worker 密钥目录后重建 worker 服务:
cp /opt/concourse/keys/web/tsa_host_key.pub /opt/concourse/keys/worker/
cd /opt/concourse && docker compose up -d worker
10.2 构建报 DNS 解析失败
现象:构建初始化阶段 image check failed,日志含 lookup docker.1ms.run ... on 10.89.58.4:53: read udp ...: no route to host(或 connection refused)。
原因:8.3.1 的 containerd 运行时给构建隔离环境下发的 DNS 指向 worker 自身(如 10.89.58.4:53),但 DNS 代理进程默认未启用,且 worker 自带的防火墙规则默认拒绝来自构建网络的入站流量。
处理(两步):
-
worker 以命令行参数显式启用 DNS 代理并指定上游 DNS(实测环境变量
CONCOURSE_WORKER_CONTAINERD_DNS_PROXY_ENABLE/CONCOURSE_WORKER_CONTAINERD_DNS_SERVER写法在 8.3.1 不生效,命令行参数有效):command: worker --containerd-dns-proxy-enable --containerd-dns-server=223.5.5.5启用后可在 worker 服务内验证 53 端口监听:
UNCONN 0 0 *:53 *:* users:(("concourse",pid=7,fd=8)) -
若仍报
no route to host,在 worker 服务内放行构建网络到 53 端口的入站:docker exec concourse-831-stack-worker-1 iptables -I INPUT 1 -i concourse0 -p udp --dport 53 -j ACCEPT docker exec concourse-831-stack-worker-1 iptables -I INPUT 2 -i concourse0 -p tcp --dport 53 -j ACCEPT(worker 重建后规则需重加,建议写入服务启动脚本。)
10.3 基础环境拉取缓慢或失败
registry-image 资源默认从 Docker Hub 拉取,受限网络下建议:改用内网可达的制品仓库代理地址作为 repository,或预先在离线环境导出/导入软件包。
10.4 栈的启停与销毁
# 停止(保留数据卷)
docker compose -p concourse-831-stack down
# 销毁(含 PostgreSQL 数据卷,流水线与构建历史将清空)
docker compose -p concourse-831-stack down -v
十一、本系列篇目
- 《Concourse 8.3.1 介绍与选型指南》
- 《Concourse 8.3.1 Docker Compose 部署指南》(本篇)
- 《Concourse 8.3.1 配置参数详解》
- 《Concourse 8.3.1 常用 CLI 命令详解》