使用 GitHub Actions 实现简易 CI/CD

发布: (2025年12月2日 GMT+8 15:01)
5 min read
原文: Dev.to

Source: Dev.to

Container Ship by Ian Taylor

Container Ship by Ian Taylor

作为一家小公司的软件工程师,IT 常被视为成本中心,我尽量保持部署流程简单:小体积的 Docker 镜像、安全的步骤,并且理想情况下零成本。

最近的一个 YouTube 视频展示了即使是私有仓库也可以被克隆,这意味着如果我们不小心,.envyml 或 Docker Compose 文件中的机密信息可能会泄露。这促使我重新审视自己的(简易)CI/CD 设置。下面是我用来将 .NET 应用部署到本地服务器的轻量化工作流。它仅作参考;你可能需要根据自己的环境调整路径、环境变量或服务名称。

免责声明

此 CI/CD 配置仅基于我的部署环境提供参考。服务器配置可能各不相同,你可能需要调整目录路径、环境变量或服务名称才能在自己的环境中正常工作。

本地开发(非常简单)

appsettings.json(或其他配置文件)中添加必要的配置,然后像往常一样本地运行应用。

为部署准备 Docker

Docker 设置

为本地和服务器部署创建多阶段 Dockerfile。下面的示例在 Alpine 上构建一个自包含的 .NET Web API 镜像,将 DOTNET_SYSTEM_GLOBALIZATION_INVARIANT 设置为 false(时区设置所需),并创建一个非 root 用户。

FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS publish
WORKDIR /src

COPY your_project.csproj ./
RUN dotnet restore "./your_project.csproj" --runtime linux-musl-x64

COPY . .
RUN dotnet publish "your_project.csproj" \
    -c Release \
    -o /app/publish \
    --no-restore \
    --runtime linux-musl-x64 \
    --self-contained true \
    /p:PublishSingleFile=true

FROM mcr.microsoft.com/dotnet/runtime-deps:9.0-alpine AS final
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
RUN apk add --no-cache icu-libs
ENV LD_LIBRARY_PATH=/usr/lib
RUN apk upgrade musl

RUN adduser --disabled-password \
    --home /app \
    --gecos '' dotnetuser && chown -R dotnetuser /app

USER dotnetuser
WORKDIR /app
COPY --from=publish /app/publish .

ENTRYPOINT ["./your_project"]

为本地开发创建 Docker Compose 文件,将所有环境变量放在 environment 部分。

version: '3.8'

services:
  project:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: project_name
    ports:
      - hardware_port1:container_port1
      - hardware_port2:container_port2
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ASPNETCORE_URLS=http://+:container_port1;http://+:container_port2
      - MYSQL_CONNECTION=server=localhost;port=3306;userid=root;password=your_password;database=your_database
    restart: always
    networks:
      - project_name
    extra_hosts:
      - "host.docker.internal:host-gateway"

networks:
  project_name:
    name: project_name_network
    driver: bridge

运行组合:

docker compose -f docker-compose.Development.yml up -d --build

如果运行成功,将开发用的 Docker Compose 文件和 appsettings.json 加入 .gitignore,防止它们被推送,同时将 appsettings.json 加入 .dockerignore(因为所有环境变量已经在 Compose 文件中)。

通过 GitHub Actions 部署到服务器

GitHub Action 设置

  1. 在仓库中,进入 Settings → Security → Secrets and Variables → Actions → New repository secret
  2. 将本地 Docker Compose 文件中的每个环境变量添加为 secret,保持相同的名称(例如 ASPNETCORE_ENVIRONMENT)。
  3. 将 SSH 凭证添加为 secret:SSH_HOSTSSH_USERNAMESSH_PRIVATE_KEYS

创建一个针对服务器的 Docker Compose 文件,引用这些 secret:

version: '3.8'

services:
  project:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: project_name
    ports:
      - hardware_port1:container_port1
      - hardware_port2:container_port2
    environment:
      - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT}
      - ASPNETCORE_URLS=${ASPNETCORE_URLS}
      - MYSQL_CONNECTION=${MYSQL_CONNECTION}
    networks:
      - project_name
    extra_hosts:
      - "host.docker.internal:host-gateway"

networks:
  project_name:
    name: project_name_network
    driver: bridge

添加工作流文件(例如 .github/workflows/deploy.yml),该文件负责构建镜像、将镜像和 Compose 文件传输到服务器并执行部署。

name: Build and Deploy

on:
  push:
    branches: [ master ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build Docker image
        run: |
          docker build -t your_project:latest .
          docker save your_project:latest -o your_project.tar

      - name: Create .env file from secrets
        run: |
          cat > .env << EOF
          ASPNETCORE_ENVIRONMENT=${{ secrets.ASPNETCORE_ENVIRONMENT }}
          ASPNETCORE_URLS=${{ secrets.ASPNETCORE_URLS }}
          MYSQL_CONNECTION=${{ secrets.MYSQL_CONNECTION }}
          EOF

      - name: Copy files to server
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEYS }}
          source: "your_project.tar,docker-compose.yml,.env"
          target: "/tmp/deploy_your_project"

      - name: Deploy with Docker Compose
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEYS }}
          script: |
            # Create app directory if it doesn't exist
            mkdir -p ~/apps/your_project

            # Move uploaded files into project directory
            mv /tmp/deploy_your_project/docker-compose.yml ~/apps/your_project/
            mv /tmp/deploy_your_project/.env ~/apps/your_project/

            # Load Docker image
            docker load -i /tmp/deploy_your_project/your_project.tar

            # (optional) Move tar file into project directory
            mv /tmp/deploy_your_project/your_project.tar ~/apps/your_project/

            # Navigate to the app directory and start the services
            cd ~/apps/your_project
            docker compose up -d --remove-orphans
Back to Blog

相关文章

阅读更多 »

Jenkins 在 AWS + Docker

Jenkins 在 AWS + Docker 的封面图片 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-upload...