1. 获取GITHUB仓库设置
- 首先我们创建一个github仓库,然后点击这里
-
创建一个自己主持的runner
-
点击后会进入到一个有相关教程的网页,我们需要选择对应的平台
-
获取身份和仓库信息
-
编写入口点脚本文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 调整 Docker 套接字权限
if [ -e /var/run/docker.sock ]; then
echo "Adjusting permissions for /var/run/docker.sock..."
sudo chmod 666 /var/run/docker.sock
else
echo "Docker socket (/var/run/docker.sock) not found!"
exit 1
fi
# 检查必要的环境变量
if [ -z "$GITHUB_REPOSITORY_URL" ] || [ -z "$GITHUB_RUNNER_TOKEN" ]; then
echo "Error: GITHUB_REPOSITORY_URL and GITHUB_RUNNER_TOKEN environment variables are required."
exit 1
fi
# 配置 runner
./config.sh --url "$GITHUB_REPOSITORY_URL" --token "$GITHUB_RUNNER_TOKEN"
# 启动 runner
./run.sh为了实现DID(Docker in Docker) 我们需要为之后将要挂载的docker.sock提供足够的权限,这样在容器内部也可以操纵外部的docker
环境变量则是会从环境变量读取并替换为上一步获取到的仓库URL和仓库TOKEN
2. 构建GITHUB-Runner镜像
官方镜像网址:https://github.com/actions/runner-images
官方镜像的环境较为齐全,但是体积较大,所以我选择自制镜像
1 | # 使用 Ubuntu 20.04 作为基础镜像 |
以下内容是对于上述部分指令的解释
-
安装docker依赖
1
apt-get install -y docker-ce-cli
其中安装必要的依赖和Docker-Cli是因为DID(Docker in Docker)需要容器内有相关Docker依赖可以操作宿主机的docker
-
切换用户
1
2
3
4# 切换到非 root 用户
USER runner
# 设置工作目录为 runner 用户的家目录
WORKDIR /home/runner切换到非root用户是因为
actions-runner-linux-x64-2.322.0.tar.gz
中的脚本因为安全原因不允许使用sudo指令执行脚本,所以不能使用root用户 -
下载runner压缩包
1
2
3
4
5
6# 从本地复制下载好的压缩包到容器内
COPY --chown=runner:runner actions-runner-linux-x64-2.322.0.tar.gz .
# 解压并删除压缩包
RUN tar xzf actions-runner-linux-x64-2.322.0.tar.gz \
&& rm actions-runner-linux-x64-2.322.0.tar.gz这一部分的压缩包可以使用如下指令进行下载
1
curl -L -o actions-runner-linux-x64-2.322.0.tar.gz https://github.com/actions/runner/releases/download/v2.322.0/actions-runner-linux-x64-2.322.0.tar.gz
3. 启动镜像
1 | docker run -d \ |
启动后输入docker logs YOUR CONTAINER_NAME
可以看到如下内容

看到Runner successfully added
则为启动成功,我们可以去github仓库验证一下
再次回到Runner页面我们可以看到一个空闲的Runner
4. 项目设置
接下来我将创建一个GO项目并推送来验证runner
-
目录结构
.github/workflows
中的build.yml
是Runner会执行的操作Dockerfile
使用本地alpine:latest
镜像可以构建出轻量级的镜像
-
工作流文件如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60name: TestPipeline
on:
push:
branches:
- main
jobs:
build:
runs-on: self-hosted
env:
IMAGE_NAME: myapp
CONTAINER_NAME: myapp
steps:
# 用于拉取代码
- name: Check out repository code
uses: actions/checkout@v4
# 设置GO语言环境
- name: Set up Go 1.24
uses: actions/setup-go@v5
with:
go-version: '1.24'
# 自建Runner需要关闭缓存,否在会报错
# 在issue https://github.com/actions/setup-go/issues/403 提及
cache: false
- name: Update Go dependencies
run: |
export GOPROXY=https://goproxy.cn,direct
go get -u
- name: Build Go application
run: CGO_ENABLED=0 GOOS=linux go build -p 2 -ldflags "-w -s" -trimpath -gcflags "all=-l" -o myapp
- name: Check for existing Docker container
run: |
# 检查名为${{ env.CONTAINER_NAME }}的容器是否正在运行
if docker ps --filter name=${{ env.CONTAINER_NAME }} --format "{{.Names}}" | grep -q${{ env.CONTAINER_NAME ]]; then
# 如果容器正在运行,则停止并移除它
docker stop ${{ env.CONTAINER_NAME }}
docker rm ${{ env.CONTAINER_NAME }}
fi
- name: Check for existing Docker image
run: |
# 检查名为${{ env.IMAGE_NAME }}的镜像是否存在
if docker images --format "{{.Repository}}:{{.Tag}}" | grep -q "${{ env.IMAGE_NAME }}:latest"; then
# 如果镜像存在,则移除它
docker rmi ${{ env.IMAGE_NAME }}:latest
fi
- name: Build Docker image
run: |
# 使用 Dockerfile 构建镜像,并指定镜像名称为${{ env.IMAGE_NAME }}
docker build -t ${{ env.IMAGE_NAME }}:latest .
- name: Run Docker container
run: |
# 启动新镜像的容器,指定容器名称为${{ env.CONTAINER_NAME }},使用 --network host 模式并后台运行
docker run -d --network host --name ${{ env.CONTAINER_NAME }} ${{ env.IMAGE_NAME }}:latestactions/checkout@v4
的项目地址:https://github.com/actions/checkoutactions/setup-go@v5
除了go语言还有各种其他的语言环境,可以在github上搜索得到,关键字***action setup***
-
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 使用 alpine:latest 作为基础镜像
FROM alpine:latest
# 创建应用目录
RUN mkdir -p /app
# 将编译好的二进制文件复制到容器的 /app 目录下
COPY myapp /app/myapp
# 设置工作目录
WORKDIR /app
# 设置容器启动时执行的命令
CMD ["/app/myapp"]这部分内容较为简单,不多解释
-
代码部分
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { // 创建一个默认的Gin引擎 r := gin.Default() // 定义一个GET请求的路由,路径为"/" r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "Hello, World!", }) }) // 默认在8080端口启动服务器 r.Run() }
当向这个应用发送GET请求时会得到**Hello,World!**的响应
5. 推送代码
推送后我们可以在仓库点击这里查看工作流流程
效果如下
验证
我们可以去主机上查看是否有对应容器和镜像
可以看到镜像构建成功同时容器也成功启动
我们发送请求来验证一下

大功告成!