.NET 小技巧 - 定義專案自訂作業實現部署半自動化
| | | 1 | |
開發 .NET 專案時,編譯跟部署時常伴隨一些相關作業。
我最近遇到的例子是修改 Telegram AI 機器人,修改測試 OK 後,下個動作是編譯 Docker Image,存成 tar 檔,scp 上傳到家裡的 Linux 主機,執行 docker load 更新再重啟容器。
這類作業的最高境界是用 Github 或其他版控伺服器實現 CI/CD。當 Git 觸發 Push 或 PR 時,版控伺服器負責編譯 .NET 專案、打包成 Docker Image,並推送到 Container Registry。目的主機則執行 Watchtower 容器,定時檢查 Docker Image 是否有新版本,若有就自動拉取並重啟容器。
CI/CD 是吾人該追求的完全體,而我目前的解法很拙劣 - 寫個 .bat 檔,在裡面跑 dotnet publish、docker build、docker save、scp 複製、ssh 遠端跑指令 docker load 及 docker compose up -d。現階段我打算先前進一步,找到比手刻 .bat 優雅一點的做法,累積一些基本功,未來再持續進化。
評估後,在 .csproj 裡定義 <Target> 自訂任務是個不錯解法,將編譯、部署相關邏輯包進專案定義,比起另外帶一個 .bat/.ps1 拖油瓶優雅多了。
分享手邊兩個應用實例。
案例一,AI 模型成本報表網站,原本靠筆記記指令跟寫 .bat 把一串動作包在一起。改良版變成在 .csproj 在新增 BuildDocker 與 DeployDocker 兩個 Task 後,用 dotent msbuild -t:BuildDocker 建置容器 Image,用 dotnet msbuild -t:DeployDocker 完成 Image 轉存 tar、scp 上傳 Linux、透過 ssh 在 Linux 上完成更新 Image 及重啟 Docker。(註:要先設好 金鑰免密碼登入 SSH/SCP。延伸閱讀:免工具快速部署 SSH 金鑰到遠端 Linux 主機、WSL 與 Windows 共用 SSH 金鑰登入遠端主機)
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<Target Name="BuildDocker">
<Exec Command="docker build -t aoai-cost-report:latest ." />
</Target>
<Target Name="DeployDocker">
<Exec Command="docker save aoai-cost-report:latest -o aoai-cost-report.tar" />
<Exec Command="scp aoai-cost-report.tar my-home-server:~/dockers/aoai-cost-report" />
<Exec Command="ssh my-home-server 'cd ~/dockers/aoai-cost-report && docker load -i aoai-cost-report.tar && docker compose up -d'" />
</Target>
</Project>
案例二,自己寫的桌面小工具,編譯的 .exe 檔要複製到 X:\Tools\Homebrew\Sidi 目錄下,我在 .csproj 加了一個 Target,指定每次 Publish 後該目錄下的 .exe 更新為新版本,另外為防止程式還在執行,檔案被鎖定無法覆寫,先使用 Taskkill 試著將已經在執行中的程序刪除。如此,每次下 dotnet publish 時便會自動更新。
有個小眉角是需要加上 dotnet publish -tl:off 關掉 .NET SDK 的 Terminal Logger,用比較精簡美觀的格式顯示輸出,但會過濾掉 MSBuild 的 <Message Importance="High">)才會原樣顯示。另個替代做法是改寫成 <Exec Command="echo ...">,如此不需顧慮 Terminal Logger,也是不錯選擇。<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>
<Target Name="DeployFiles" AfterTargets="Publish">
<Exec Command="taskkill /IM Sidi.exe /F 2>nul" IgnoreExitCode="true" />
<Copy SourceFiles="$(PublishDir)Sidi.exe" DestinationFolder="X:\Toos\Homebrew\Sidi" />
<Message Text="X:\Toos\Homebrew\Sidi\Sidi.exe is updated" Importance="High" />
</Target>
</Project>

Explains using custom MSBuild Target tasks in .NET projects to automate build and deployment workflows. Shows examples for Docker image build/deploy and post-publish file updates, offering a cleaner alternative to batch scripts and a step toward future CI/CD.
Comments
# by yoyo
我認為CI/CD pipeline script還是不要寫在專案檔內比較好, 另外寫在 .bat/.ps1 可降低耦合性,pipeline有問題時也更容易debug測試,再用CI/CD工具串接 gitea 也有CI/CD 功能