Deployment with GitHub Actions
ServiceStack GitHub Action Deployments
The release.yml in this template enables GitHub Actions CI deployment to a dedicated server with SSH access.
release.yml is designed to work with a ServiceStack app deploying directly to a single server via SSH. A docker image is built and stored on GitHub's
ghcr.io docker registry when a GitHub Release is created.
GitHub Actions specified in
release.yml then copy files remotely via scp and use
docker-compose to run the app remotely via SSH.
What's the process of
Deployment server setup
To get this working, a server needs to be setup with the following:
- SSH access
- ports 443 and 80 for web access of your hosted application
This can be your own server or any cloud hosted server like Digital Ocean, AWS, Azure etc. We use Hetzner Cloud to deploy all ServiceStack's GitHub Project Templates as it was the best value US cloud provider we've found.
When setting up your server, you'll want to use a dedicated SSH key for access to be used by GitHub Actions. GitHub Actions will need the private SSH key within a GitHub Secret to authenticate. This can be done via ssh-keygen and copying the public key to the authorized clients on the server.
To let your server handle multiple ServiceStack applications and automate the generation and management of TLS certificates, an additional docker-compose file is provided in this template,
nginx-proxy-compose.yml. This docker-compose file is ready to run and can be copied to the deployment server.
For example, once copied to remote
~/nginx-proxy-compose.yml, the following command can be run on the remote server.
docker-compose -f ~/nginx-proxy-compose.yml up -d
This will run an nginx reverse proxy along with a companion container that will watch for additional containers in the same docker network and attempt to initialize them with valid TLS certificates.
GitHub Actions secrets
release.yml uses the following secrets.
||Hostname used to SSH deploy .NET App to, this can either be an IP address or subdomain with A record pointing to the server|
||Username to log in with via SSH e.g, ubuntu, ec2-user, root|
||SSH private key used to remotely access deploy .NET App|
||Email required for Let's Encrypt automated TLS certificates|
These secrets can use the GitHub CLI for ease of creation. Eg, using the GitHub CLI the following can be set.
gh secret set DEPLOY_HOST -b"<DEPLOY_HOST>" gh secret set DEPLOY_USERNAME -b"<DEPLOY_USERNAME>" gh secret set DEPLOY_KEY < key.pem # DEPLOY_KEY gh secret set LETSENCRYPT_EMAIL -b"<LETSENCRYPT_EMAIL>"
These secrets are used to populate variables within GitHub Actions and other configuration files.
A published version of your .NET App created with the standard dotnet publish tool:
dotnet publish -c Release
is used to build a production build of your .NET App inside the standard
Dockerfile for dockerizing .NET Applications.
Additional custom deployment tasks can be added to your project's package.json postinstall script which also gets run at deployment.
If preferred additional MS Build tasks can be run by passing in custom parameters in the publish command, e.g:
dotnet publish -c Release /p:APP_TASKS=prerender
MyApp.csproj can detect with a target that checks for it:
<!-- Prerender tasks run in release.yml --> <Target Name="AppTasks" AfterTargets="Build" Condition="$(APP_TASKS) != ''"> <CallTarget Targets="Prerender" Condition="$(APP_TASKS.Contains('prerender'))" /> </Target> <Target Name="Prerender"> <Message Text="Prerender..." /> </Target>
Pushing updates and rollbacks
By default, deployments occur on commit to your main branch. A new Docker image for your ServiceStack API is produced, pushed to GHCR.io and hosted on your Linux server with Docker Compose.
The template also will run the release process on the creation of a GitHub Release making it easier to switch to manual production releases.
release.yml workflow can be run manually specifying a version. This enables production rollbacks based on previously tagged releases.
A release must have already been created for the rollback build to work, it doesn't create a new Docker build based on previous code state, only redeploys as existing Docker image.