You're using GitHub to version your ASP.NET Core or Blazor app, and you're hosting on SmarterASP.NET? This guide shows you how to fully automate your deployments with GitHub Actions, without leaving the GitHub ecosystem, without any third-party tools, and without copying a single file by hand.
TL;DR
- GitHub Actions orchestrates the build and deployment from your repository, on a Windows runner (required for MSDeploy).
- SmarterASP.NET exposes a WebDeploy (MSDeploy) endpoint on port 8172, we use it directly.
- Secrets (credentials, passwords) are stored in GitHub Secrets, non-sensitive values in GitHub Variables.
-skiprules protect critical server-side files (Let's Encrypt certificates, sitemaps…).
GitHub Actions vs Azure DevOps: which one to pick?
If your code is already on GitHub, GitHub Actions is the natural choice: everything is in one place, the configuration lives in the repository, and the free tier is generous (2,000 minutes/month for private repositories). Azure DevOps makes more sense if you already have a Microsoft ecosystem in place (boards, artifacts, test plans), but for a .NET project hosted on SmarterASP.NET, GitHub Actions gets the job done without friction.
The deployment logic is identical in both cases: dotnet publish produces a folder, msdeploy.exe syncs it to the server. Only the configuration file syntax changes.
ℹ Prerequisites
You need a GitHub account, a repository containing your .NET project, and your SmarterASP.NET credentials (username / password from your control panel). WebDeploy must be enabled in your site settings.
Not a customer yet? My partner link gives you access to a promotional offer.
Workflow anatomy
A GitHub Actions workflow is a YAML file placed in .github/workflows/. It is made up of jobs, which are themselves made up of steps. Here, we do everything in a single build_and_deploy job to keep things simple, the publish output is the direct result of the build, so there's no need to store an intermediate artifact.
push to main
└── job: build_and_deploy (windows-latest)
├── Checkout
├── Setup .NET
├── Restore
├── Build
├── Publish
├── WebDeploy → SmarterASP.NET
└── Warm-up
✓ Why
windows-latest?
msdeploy.exeis a Windows-only tool. Ubuntu or macOS runners cannot execute it. GitHub provides Windows runners in its free tier, so this is not an issue.
The full annotated workflow
Create the file .github/workflows/deploy.yml in your repository:
name: Build & Deploy to SmarterASP.NET
# Triggers: push to main, or manual run from the GitHub Actions tab
on:
push:
branches: [ main ]
workflow_dispatch: # Allows manual re-runs from the Actions interface
jobs:
build_and_deploy:
runs-on: windows-latest # Required: msdeploy.exe only exists on Windows
environment: prod # GitHub Environment (enables protection rules and approvals)
steps:
# 1. Check out the source code
- name: Checkout
uses: actions/checkout@v4
# 2. Install the required .NET SDK
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x' # Adapt to your target version
# 3. Restore NuGet dependencies
- name: Restore
run: dotnet restore src/YourApp/YourApp.csproj
# 4. Compile in Release mode
- name: Build
run: dotnet build src/YourApp/YourApp.csproj --configuration Release --no-restore
# 5. Publish the application to the ./publish folder
# --no-build avoids recompiling what was just built
- name: Publish
run: dotnet publish src/YourApp/YourApp.csproj --configuration Release --output publish --no-build
# 6. Deploy via WebDeploy (MSDeploy) to SmarterASP.NET
- name: WebDeploy to SmarterASP.NET
shell: pwsh
env:
# Non-sensitive values: stored in GitHub Variables (Settings → Variables)
WEBSITE_NAME: ${{ vars.WEBDEPLOY_WEBSITE_NAME }}
SERVER_COMPUTER_NAME: ${{ vars.WEBDEPLOY_SERVER_URL }}
SERVER_USERNAME: ${{ vars.WEBDEPLOY_USERNAME }}
PARAM_EXCLUDED_FILES: ${{ vars.EXCLUDED_FILES }}
PRESERVE_REMOTE_FILES: ${{ vars.PRESERVE_REMOTE_FILES }}
# Sensitive secret: stored in GitHub Secrets (Settings → Secrets)
SERVER_PASSWORD: ${{ secrets.WEBDEPLOY_PASSWORD }}
run: |
# Look for msdeploy at the two standard locations on Windows runners
$msdeploy = @(
"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe",
"C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe"
) | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $msdeploy) { throw "msdeploy.exe not found on this runner" }
Write-Host "msdeploy found: $msdeploy"
Write-Host "Deploying to: $env:WEBSITE_NAME"
$sourcePath = "$PWD\publish"
$msdeployArgs = @(
"-verb:sync",
"-source:contentPath=$sourcePath",
"-dest:contentPath=$env:WEBSITE_NAME,computerName=$env:SERVER_COMPUTER_NAME,userName=$env:SERVER_USERNAME,password=$env:SERVER_PASSWORD,authtype=Basic,includeAcls=False",
"-allowUntrusted", # Useful if the certificate chain causes issues on the runner
"-disableLink:AppPoolExtension",
"-disableLink:ContentExtension",
"-disableLink:CertificateExtension",
"-enableRule:AppOffline" # Places an app_offline.htm during deployment
)
# Option: do not delete unknown files on the remote server
if ($env:PRESERVE_REMOTE_FILES -eq "true") {
$msdeployArgs += "-enableRule:DoNotDeleteRule"
}
# --- Custom exclusions (via EXCLUDED_FILES variable) ---
$env:PARAM_EXCLUDED_FILES -split ";" | Where-Object { $_.Trim() -ne "" } | ForEach-Object {
$regex = "(?i).*$([Regex]::Escape($_.Trim()))$"
Write-Host "Excluding file: $regex"
$msdeployArgs += "-skip:skipAction=Delete,objectName=filePath,absolutePath=$regex"
}
# --- Protect critical server-side folders ---
# Let's Encrypt certificates (ACME challenge folder)
$msdeployArgs += "-skip:skipAction=Delete,objectName=dirPath,absolutePath=(?i).*\\\.well-known.*"
# Dynamically generated sitemaps written by the app at runtime
$msdeployArgs += "-skip:skipAction=Delete,objectName=dirPath,absolutePath=(?i).*\\wwwroot\\sitemaps(`$|\\.*)"
$msdeployArgs += "-skip:skipAction=Delete,objectName=filePath,absolutePath=(?i).*\\wwwroot\\sitemaps\\.*"
Write-Host "Running msdeploy..."
& $msdeploy @msdeployArgs
# Check exit code PowerShell does not always propagate errors automatically
if ($LASTEXITCODE -ne 0) {
Write-Error "msdeploy failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
# 7. Warm-up: send an HTTP request to wake the app from its cold start
- name: Application warm-up
shell: pwsh
continue-on-error: true # A failed warm-up should not invalidate a successful deployment
run: |
$url = "${{ vars.WEBSITE_URL }}"
$maxRetries = 3
$retryDelay = 10
Write-Host "Warming up $url..."
for ($i = 1; $i -le $maxRetries; $i++) {
try {
$r = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 45
Write-Host "Site responded: $($r.StatusCode) - OK"
break
} catch {
Write-Host "Attempt $i/$maxRetries failed: $_"
if ($i -lt $maxRetries) { Start-Sleep -Seconds $retryDelay }
}
}
Variables and secrets to configure in GitHub
Go to Settings → Secrets and variables → Actions in your repository. GitHub distinguishes two categories:
- Secrets: encrypted, never shown in logs, accessed via
${{ secrets.NAME }} - Variables: visible in the interface, accessed via
${{ vars.NAME }}
| Name | Type | Example value |
|---|---|---|
WEBDEPLOY_WEBSITE_NAME |
Variable | mysite.com |
WEBDEPLOY_SERVER_URL |
Variable | https://win1234.site4now.net:8172/msdeploy.axd?site=mysite.com |
WEBDEPLOY_USERNAME |
Variable | your-smarterasp-login |
WEBDEPLOY_PASSWORD |
Secret | ••••••••• |
WEBSITE_URL |
Variable | https://mysite.com |
EXCLUDED_FILES |
Variable | appsettings.Production.json;Web.config |
PRESERVE_REMOTE_FILES |
Variable | false |
✓ Tip
Find yourWEBDEPLOY_SERVER_URLin the SmarterASP.NET control panel → Publish your website → Connection tab. It always starts withhttps://winXXXX.site4now.net:8172.
The prod environment and protection rules
The workflow references environment: prod. To create this environment in GitHub, go to Settings → Environments → New environment and name it prod.
The benefit? You can add:
- Required reviewers, a collaborator must approve before the deployment runs.
- A wait timer, for example, 5 minutes between the push and the actual deployment.
- Allowed branches, only the
mainbranch can trigger a deployment toprod.
This is the equivalent of Azure DevOps Environment approvals, built directly into GitHub.
Step by step: getting it up and running
Enable WebDeploy in the SmarterASP.NET control panel. Verify that port 8172 is accessible from the outside.
Create the file
.github/workflows/deploy.ymlat the root of your repository. Copy the YAML above and adapt the path to your.csprojand the .NET version.Add secrets and variables under Settings → Secrets and variables → Actions in your GitHub repository.
Create the
prodenvironment under Settings → Environments. Add a protection rule if you work in a team.Push to
mainand watch the Actions tab of your repository. Each step shows its logs in real time.If a step fails, click on it to see the error message. msdeploy is usually very explicit (wrong credentials, closed port, etc.).
Common pitfalls
"msdeploy not found"
GitHub's windows-latest runners include Web Deploy V3. The script checks both standard locations. If it still fails, add an installation step before deployment:
- name: Install WebDeploy
run: choco install webdeploy -y
shell: pwsh
My appsettings.Production.json gets overwritten
Add it to the EXCLUDED_FILES variable: appsettings.Production.json. The PowerShell script converts it into a -skip rule that protects the file on the server on every deployment.
⚠ Warning
Never version production secrets inappsettings.json. Useappsettings.Production.jsonoutside your repository, or GitHub Secrets with runtime environment variable injection.
The warm-up fails with a 302 or an SSL error
Expected if your site redirects HTTP → HTTPS. The continue-on-error: true on the warm-up step means GitHub does not treat this as a workflow failure. The deployment is complete regardless.
Difference from Azure DevOps: no intermediate artifact
In the Azure DevOps pipeline from the previous article, an artifact was published between the Build stage and the Deploy stage. Here, since everything runs in a single job, the ./publish folder is directly available to the next step. This is simpler, but it also means you can't reuse that artifact for a second environment without restructuring the workflow into multiple jobs.
Going further
Split build and deploy into two jobs
If you want to add automated tests or deploy to multiple environments from the same artifact, split the workflow into two jobs: a build job that uploads an artifact via actions/upload-artifact, and a deploy job that downloads it via actions/download-artifact. This is the equivalent of the multi-stage structure in Azure DevOps.
Trigger only on specific paths
To avoid redeploying when only a documentation file changes:
on:
push:
branches: [ main ]
paths:
- 'src/**'
- '.github/workflows/deploy.yml'
Notify on failure
Add a conditional step at the end of the workflow:
- name: Notify on failure
if: failure()
shell: pwsh
run: |
# Call a Teams, Slack, or other webhook
Invoke-RestMethod -Uri "${{ secrets.SLACK_WEBHOOK }}" -Method Post -Body '{"text":"🚨 Deployment failed on main"}'
✓ Final best practice
Enable branch protection rules onmainunder Settings → Branches. Require pull requests to go through a review before merging. Combined with theprodenvironment approval, you get a solid deployment workflow without any external tooling.
Conclusion
GitHub Actions makes continuous deployment to SmarterASP.NET achievable in under an hour of configuration. Everything lives in your repository, the configuration is versioned, and the deployment history is visible directly in the Actions tab. It's a serious alternative to Azure DevOps for projects that don't need the full Microsoft ecosystem.
If you've already read the Azure DevOps Pipelines article, you'll notice that the PowerShell/MSDeploy logic is almost identical, only the YAML wrapper changes. That's intentional: once you understand WebDeploy, you can switch from one CI/CD tool to another without relearning the deployment mechanism.
Questions about EF Core migrations at deploy time, multi-environment setups, or GitHub Environments? Drop them in the comments.


Commentaires (0)
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire