惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
H
Help Net Security
V
Visual Studio Blog
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
MyScale Blog
MyScale Blog
The Cloudflare Blog
Martin Fowler
Martin Fowler
D
Docker
腾讯CDC
F
Fortinet All Blogs
雷峰网
雷峰网
GbyAI
GbyAI
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
博客园 - 叶小钗

峰峰的小窝

逆向提取软件数据库 正射影像拼接和AI实例分割系统设计实现 照片高斯建模实现 从零开始的自动驾驶(六)无刷电调硬件改了又改 从零开始的自动驾驶(五)整体设计|峰峰的小窝 从零开始的自动驾驶(四)无刷电调硬件修改|峰峰的小窝 从零开始的自动驾驶(三)ROS2与雷达|峰峰的小窝 从零开始的自动驾驶(二)编译开发板Linux|峰峰的小窝 从零开始的自动驾驶(一)无刷电调硬件|峰峰的小窝 搭建代理服务器跳板|峰峰的小窝 迎接新的时代|峰峰的小窝 一段故事的结束|峰峰的小窝 AI全自动写小说|峰峰的小窝 桌面直播通话APP设计实现|峰峰的小窝 2025 年总结|峰峰的小窝 SPI串并转换模块|峰峰的小窝 一种另类的数据标注方法|峰峰的小窝 GPS定位模块设计|峰峰的小窝 STM32硬件和软件设计指南|峰峰的小窝 基于Docker的多实例管理系统|峰峰的小窝 Hello PCB|峰峰的小窝 Hello ESP8266|峰峰的小窝 实时语音智能体快速搭建|峰峰的小窝 Docker镜像构建和部署|峰峰的小窝 Web应用私有化|峰峰的小窝 虚幻FFMPEG解码视频|峰峰的小窝 Fish-Speech的编译优化|峰峰的小窝 阶段总结|峰峰的小窝
Git自动化测试部署|峰峰的小窝
FFeng 我能想到的,最大的成功就是无愧于自己的心。 · 2025-07-18 · via 峰峰的小窝

我有一个自建的Gitea跑在一个配置还不错的服务器上,在上次https://srcblog.ffeng123.win:23443/archives/ccd8829c-f256-4392-8841-e00a5820b2c3,给它装了Docker。

看到联调时后端出各种不太聪明的Bug意识到单元测试的重要性、与服务器打交道多了后厌倦了手动部署···

于是我决定,尝试自动化测试和部署。

说是自动化测试和部署,主要还是解决自动化部署的问题。

Gitea的Action

Gitea可以在提交时、打标签时等时候,触发一个自动执行的程序,这个自动程序叫Action,Github也有,而且大同小异。

Gitea ActionGithub Action

项目的 .gitea/workflows 下的所有yml文件会被扫描作为Action,Action执行的时机和内容都在文件里面写着。

开始之前

Gitea开箱即用,但Gitea的Action不是,自建Gitea需要事先部署Gitea Runner。

参考Gitea的文档

Runner可以是可执行文件部署或者Docker部署,但无论哪种部署方式,机器上都必须有Docker。

无论Runner跑在哪里,Runner在执行任务的时候都会为任务临时启动一个Docker容器,且容器内能够操作Docker

Action模板

部署Hugo静态网站

Action用的SH脚本:build.sh

这个Action会在v开头的标签推送到服务器时触发,自动准备Hugo环境、构建项目、向生产服务器上传构建的内容。

需要在Gitea的项目配置里面配置这些密钥:

  • PROD_PATH:生产服务器上传最终内容的目录

  • PROD_SERVER:SSH IP

  • PROD_USER: SSH 用户名

  • PROD_PASSWORD:SSH密码

name: 部署开发服务器

on:
  push:
    tags:
      - "v*"

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: 签出代码
        uses: actions/checkout@v3

      - name: 构建项目
        run: |
          chmod +x ./build.sh
          ./build.sh      # 使用密码认证的部署方式

      - name: 部署到生产服务器
        run: |
          sudo apt-get update && sudo apt-get install -y sshpass openssh-client rsync
          echo "准备部署到生产服务器..."
          # 配置 SSH 以跳过主机密钥验证
          mkdir -p ~/.ssh
          echo "Host *" >> ~/.ssh/config
          echo "    StrictHostKeyChecking no" >> ~/.ssh/config
          echo "    UserKnownHostsFile=/dev/null" >> ~/.ssh/config
          chmod 600 ~/.ssh/config
          # 执行部署
          sshpass -p "${{ secrets.PROD_PASSWORD }}" rsync -avz --delete -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" ./public/ ${{ secrets.PROD_USER }}@${{ secrets.PROD_SERVER }}:${{ secrets.PROD_PATH }}

对于其他技术栈的纯前端项目,可以修改Action中的./public/ (构建结果目录)和构建项目时执行的./build.sh (例如换成npm run build,官方的ubuntu-latest上有npm)

部署开发用Golang后端

这个Action会在每次推送时触发,构建Docker并在Runner所在的机器上部署Docker。

name: 部署开发服务器
run-name: 正在部署开发服务器 🚀
on: [push]
branches:
  - master

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: 📥 检出代码
        uses: actions/checkout@v4

      - name: 🐳 构建并部署
        run: |
          chmod +x scripts/deploy.sh
          scripts/deploy.sh

deploy.sh:

#!/bin/bash

# Docker Compose 部署脚本配置
COMPOSE_FILE="docker-compose.yml"
SERVICE_NAME="my-server"
PORT="8080"

echo "🚀 开始使用 Docker Compose 部署 ${SERVICE_NAME}..."

# 检查 docker-compose.yml 文件是否存在
if [ ! -f "${COMPOSE_FILE}" ]; then
    echo "❌ ${COMPOSE_FILE} 文件不存在"
    exit 1
fi

# 停止并删除旧容器
echo "🗑️ 停止旧服务..."
docker compose down || true

# 强制重新构建镜像(清理构建缓存)
echo "🧹 清理旧镜像和构建缓存..."
docker compose down --rmi local || true
# docker builder prune -f || true

# 构建并启动服务
echo "📦 重新构建并启动服务..."
docker compose up -d --build --force-recreate || {
    echo "❌ 服务启动失败"
    exit 1
}

# 等待服务启动
echo "⏳ 等待服务启动..."
sleep 10

# 检查服务状态
for i in {1..10}; do
    echo "🔍 第 $i 次检查服务状态..."
    if [ "$(docker compose ps -q ${SERVICE_NAME})" ]; then
        echo "✅ 服务已成功启动"
        break
    else
        if [ $i -eq 10 ]; then
            echo "❌ 服务启动失败"
            docker compose logs ${SERVICE_NAME}
            exit 1
        else
            echo "⏳ 服务尚未启动,稍后重试..."
            sleep 5
        fi
    fi
done

echo "🎉 部署完成!"
echo "📊 部署信息:"
echo "   服务名称: ${SERVICE_NAME}"
echo "   服务端口: ${PORT}"
echo "   服务地址: http://localhost:${PORT}"
echo "📋 服务状态:"
docker compose ps

Dockerfile:

FROM golang:1.24-alpine AS builder

WORKDIR /app

RUN apk add --no-cache git ca-certificates tzdata make ffmpeg
COPY go.mod go.sum ./
RUN go mod download
COPY . .

RUN make test
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags '-w -s' -o main cmd/api/main.go


FROM alpine:latest

RUN apk --no-cache add ca-certificates curl tzdata ffmpeg && \
    addgroup -g 1000 appgroup && \
    adduser -u 1000 -G appgroup -s /bin/sh -D appuser

WORKDIR /app

COPY --from=builder /app/main .
#COPY --from=builder /app/.env* ./

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

CMD ["./main"]

docker-compose.yml:

services:
    my-server:
        build: .
        ports:
            - "8080:8080"
        environment: {}
        env_file:
            - .env
        restart: unless-stopped
        healthcheck:
            test: [ "CMD", "curl", "-f", "http://localhost:8080/health" ]
            interval: 30s
            timeout: 10s
            retries: 3
            start_period: 40s
        networks:
            - main
networks:
    main:
        driver: bridge

deploy.sh和docker-compose.yml中的名称 my-server 应该是相同的,用于脚本检测容器是否启动成功。