Continuous Integration Guide: Automated Build and Deployment of AspNetCore Projects Using GitHub Action
Another boring automation tutorial: GitHub Actions builds AspNetCore Docker image and pushes to Alibaba Cloud, then deploys with a local runner. Simplify the workflow? Who cares about these details? Proxy configuration is so annoying!
目录15 个章节
Foreword
Previously, I researched using GitHub Actions to automatically build and publish nuget packages: Developing Modern .NetCore Console Applications: (4) Using GitHub Actions for Automatic Building and Publishing nuget Packages
Now, let's take it a step further: use GitHub Actions to build Docker images within the provided runners, then submit them to Alibaba Cloud's private image registry, and finally pull the images down to run them on a local runner.
This article uses the AIHub project as an example.
AIHub is an AI platform that integrates multiple models, offering features such as large language models, computer vision, and natural language processing. It is developed based on AspNetCore + Blazor Server technology.
Preparing the Dockerfile
First, prepare the Dockerfile for building the image.
Generally, when using the dotnet cli to create a project, this Dockerfile can be generated automatically.
If it's not available, you can refer to my example below.
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["AIHub.Blazor/AIHub.Blazor.csproj", "AIHub.Blazor/"]
RUN dotnet restore "AIHub.Blazor/AIHub.Blazor.csproj"
COPY . .
WORKDIR "/src/AIHub.Blazor"
RUN dotnet build "AIHub.Blazor.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "AIHub.Blazor.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AIHub.Blazor.dll"]
You can try building it locally first to see if there are any issues.
docker build .
If everything is normal, you can proceed to the next step.
Creating a Private Image Repository
You can set one up yourself or use cloud service products. Here, I use Alibaba Cloud's "Container Registry (ACR)," and the personal edition is free.
Create an image repository, select "Local Repository" as the code source, and you can push images to the repository via the command line.
PS: I noticed there is also a GitHub repository as a code source inside, but I haven't tested it yet.
After creation, there will be an operation guide, which is not very important. We only need to focus on the username and password.
The username used for login is the full Alibaba Cloud account name, and the password is the one set when the service was activated.
You can modify the credential password on the Access Credential page.
OK, save this information well; it will be needed next.
The repository address used in this article is: registry.cn-hangzhou.aliyuncs.com/deali/aihub-test
Configure GitHub Action Secret
Configure the address, username, password, and other information of the Alibaba Cloud repository into Action Secret.
Here, the names I set are:
ALIYUN_DOCKER_REPOALIYUN_USERNAMEALIYUN_PWD
Building the Image
Write the GitHub workflow configuration, the details will not be repeated here. For those who are unclear, you can refer to this article: Developing Modern .NetCore Console Applications: (4) Using GitHub Actions for Automatic Build and NuGet Package Publishing
GitHub Workflow
This is provided by GitHub. I haven't used this solution myself, but those interested can give it a try.
# This workflow uses unauthenticated GitHub actions.
# They are provided by third parties and are subject to
# Separate Terms of Service, Privacy Policy, and Support
# Documentation.
# GitHub recommends pinning actions to commit SHAs.
# To get a newer version, you need to update SHA.
# You can also reference tags or branches, but this operation may change without warning.
name: Publish Docker image
on:
release:
types: [published]
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: my-docker-hub-namespace/my-docker-hub-repository
- name: Build and push Docker image
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
### Simple Version Workflow
This is a workflow I wrote myself, which is a bit simpler than the examples provided by GitHub.
```yaml
name: Docker Image CI
run-name: ${{ github.actor }} is building a image
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the Docker image
run: docker build . --file ./next-antd/AIHub.Blazor/Dockerfile --tag ${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}
- name: Login to aliyun docker repository
run: docker login -u ${{ secrets.ALIYUN_USERNAME }} -p ${{ secrets.ALIYUN_PWD }} ${{ secrets.ALIYUN_DOCKER_REPO }}
- name: Push image to aliyun docker repository
run: docker push ${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}
This build step constructs a Docker image on a GitHub-hosted runner and pushes it to a private repository (私有镜像仓库).
For the image's tag version, I used the run_id environment variable (环境变量), which represents the unique ID of this build task, for example, 1658821493.
Alternatively, you could use ${{ github.sha }} to represent the commit SHA of this trigger, but I chose not to as it's too long.
GITHUB_SHA: The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows". For example,ffac537e6cbbf934b08745a378932722df287a53.
P.S.: The official example uses a $(date +%s) timestamp, but I haven't figured out how to concatenate strings within an environment variable yet.
Setting up a Local Runner
The image is built, but it still needs to run on a local server.
It's simple—you just pull down the image and then run docker compose up. Now let's add this step to the workflow as well.
Adding a Runner
Visit this page: https://github.com/star-plan/aihub/settings/actions/runners
Click the New self-hosted runner button.
Select the corresponding system and architecture, then follow the provided commands, and you're all set.
In this guide, we've chosen the Linux - x64 approach.
The commands provided by GitHub are as follows (Note: this is just an example; the actual commands should be based on your project's configuration page).
# Create a folder
$ mkdir actions-runner && cd actions-runner# Download the latest runner package
$ curl -o actions-runner-linux-x64-2.311.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz# Optional: Validate the hash
The download step might encounter issues because, for some inexplicable reason, GitHub may be inaccessible. In such cases, you need to use the proxychains tool—please search for and configure it yourself.
PS: Wow, this runner is 180MB.
Then run the runner command
./config.sh --url [REPO_URL] --token [TOKEN]
- You'll be prompted to enter a runner group name; the default is fine.
- You'll need to provide a runner name; I entered
aihub. - You can also input labels, etc., as needed.
Once the configuration is complete, start the runner.
./run.sh
Installing as a Service
The runner started with ./run.sh is unstable; it stops running after you log out. Even if you run it in the background using & or Ctrl+Z, it’s still not very reliable.
GitHub Runner can be run as a Linux service, which is also simple to use.
First, stop the currently running runner.
ps aux | grep run.sh
After finding the pid, kill it
kill -9 pid
Install service
```bash
sudo ./svc.sh install
Start service
sudo ./svc.sh start
For more operations, please refer to the fourth reference material.
View Runner List
View the currently running runners on the GitHub page
Address: https://github.com/star-plan/aihub/settings/actions/runners
You can see there is already a runner named aihub

Runner Network Proxy
GitHub is so thoughtful about this! They've already considered situations where GitHub might be inaccessible. I'm literally crying here 😭
You can set a proxy in environment variables or configure it in the .env file. I'll choose the .env method here.
https_proxy=http://proxy.local:8080
no_proxy=example.com,myserver.local:443
For more documentation on setting up a local runner, please refer to the third item in the references.
Deploying Services Using a Local Runner
As usual, we use docker-compose to orchestrate the services.
First, create a directory to store the docker-compose.yml file and some persistent files.
The path for this example is: /home/apps/aihub
version: '3.6'
services:
web:
image: ${IMAGE}
container_name: aihub-test
restart: always
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:80
volumes:
- ./aihub.app.db:/app/aihub.app.db
ports:
- "12002:80"
networks:
- default
- swag
networks:
swag:
name: swag
external: true
default:
name: aihub-test
I used an environment variable for the image part because the tag of the built image is different each time. I need to write the tag into the .env file within the workflow. Of course, you could also hardcode it as latest.
The .env file looks like this:
IMAGE=registry.cn-hangzhou.aliyuncs.com/deali/aihub-test:6861065827
OK, we're finally at the last step. Let's continue editing our workflow to add a deployment job.
name: Docker Image CI
run-name: ${{ github.actor }} is building a image
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the Docker image
run: docker build . --file ./next-antd/AIHub.Blazor/Dockerfile --tag ${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}
- name: Login to aliyun docker repository
run: docker login -u ${{ secrets.ALIYUN_USERNAME }} -p ${{ secrets.ALIYUN_PWD }} ${{ secrets.ALIYUN_DOCKER_REPO }}
- name: Push image to aliyun docker repository
run: docker push ${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}
deployment:
runs-on: [self-hosted, linux, x64]
needs: [build]
steps:
- name: export environment variant
run: echo 'IMAGE=${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}' > /home/apps/aihub/.env
- name: docker compose deploy
run: docker compose -f /home/apps/aihub/docker-compose.yml --project-directory /home/apps/aihub up -d
Note:
-
Jobs run in parallel by default. Therefore, you need to add the
needsconfiguration in thedeploymentjob to ensure it runs only after thebuildjob completes.Using
jobs.<job_id>.needsidentifies all jobs that must successfully complete before the current job runs. It can be a string or an array of strings. If any job fails or is skipped, all jobs that depend on it are skipped, unless those jobs use a conditional expression allowing it to continue. If a run contains a series of jobs that depend on each other, a failure or skip applies to all jobs in the dependency chain starting from the point of failure or skip. If you want a job to run even if its dependency jobs were not successful, use thealways()conditional expression injobs.<job_id>.if. -
The
deploymentjob runs locally, soruns-onuses multiple labels to select the local runner. -
When executing the
docker composecommand, the working directory is inside the GitHub Action tool's_workdirectory. Therefore, you need to specify the--project-directoryparameter or specify the.envfile location using--env-file.
View Execution Results
You can view the execution results on the GitHub Action page.

Done!
References
- https://docs.github.com/en/actions/learn-github-actions/variables
- https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
- https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners
- https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service
- https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/using-a-proxy-server-with-self-hosted-runners
- https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow
- https://docs.docker.com/compose/environment-variables/set-environment-variables/
_EOF_
Continuous Integration Guide: Automated Build and Deployment of AspNetCore Projects Using GitHub Action
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
GitLab的安装、配置、使用
前言 上周去参与“中国数字经济创新发展大会”了,然后又忙新项目的事情,博客又有一段时间没有更新,今天周一事情比较少,立刻开始写文,最近有挺多值得记录的东西~ 进入正文,最近我们搭了个Gitlab,并且把正在开发的项目代码都迁移到自建的Gitlab上,整个过程还是比较流畅的~~(就是有点费内存~~ PS:立即下单给这台服务器买了四条16G内存组四通道~ 在此之前,我们团队一直使用Gitee企业版作为
2022-07-21 · DevOps · 约 1 分钟
开启GitLab的邮件通知功能以及一些外观配置
这次把邮件功能、域名、外观啥的配置好了,写篇文章记录一下
2024-07-17 · DevOps · 约 1 分钟
GitLab的CI_CD配置与使用
前言 写代码这项工作,本质就是将工作自动化,减少手工操作提供效率,~~因为人的本质都是懒狗~~,程序员也不能例外,为了各种意义的效率提升~~(懒)~~,我们需要持续集成工具,将代码测试、编译、发布这些重复性很高的工作,全部变成自动化,减轻工作量,提高效率,解放生产力,~~为华夏伟大复兴贡献力量~~ 说回正题,持续集成工具主流的就 Jenkins 、Gitlab CI/CD 这些,因为我们刚好在用
2022-09-30 · DevOps · 约 1 分钟
code-porter 代码工作区迁移工具
换电脑几十个项目手动拷?你不是懒就是蠢。写了code-porter,自动扫描、智能打包(bundle/zip),脏仓库也能保留未提交改动,一行uvx搞定。零依赖跨平台,U盘一插完事。别问我为什么不用rsync——它懂你项目结构?
2026-05-22 · DevOps · 约 1 分钟
讨论
共 0 条加载评论中…