GitHub Actions are a great tool for automating builds, tests and deployments in a composable and flexible way. The ServiceStack
x tool provides mix templates to incorporate into your existing applications and repositories that can speed up different types of workflows.
We've created a mix template for building and deploying your ServiceStack app with GitHub Actions, GitHub Container Repository and Docker Compose all via SSH for a minimalist server hosting setup.
Specifically, we'll be using
x mix build release-ghr-vanilla which has GitHub actions configured ready to deploy your ServiceStack application when a new GitHub release is created. This can be run at the root of your local repository folder, for example if you wanted to create an empty web application you would run:
git clone firstname.lastname@example.org:<your user or org>/DropletApp.git cd DropletApp x new web x mix build release-ghr-vanilla # 'y' to process with writing files from x mix. git add -A git commit -m "New ServiceStack project" git push
Pushing your new application to GitHub, the
build.yml will run a
dotnet build and
dotnet test within the CI environment. For deployments, we want to get a server setup for hosting the new application.
x mix release-*are designed to be used with ServiceStack applications that were created with most
x newproject templates that follow the ServiceStack recommended project structure. They are designed to be a starting point that you can edit once created to suit your needs.
Digital Ocean Droplets Host
In this tutorial, we'll be using a Digital Ocean Droplet as the target server and walk through the steps required to setup this automated deployment process for your ServiceStack application.
Since we are deploying a simple ServiceStack application, we are going to use the cheapest
Basic Droplet at $5USD/month. This is plenty to host even multiple low traffic ServiceStack applications but your use case might have different hardware requirements.
Create your new droplet
The basic droplet we are going to create will have the following configuration.
- Distribution - Ubuntu 20.04 LTS
- Plan - Basic
- Datacenter region - Which ever suits your use case
- VPC - default
- Authentication - SSH keys, create a new one just for this server, follow the instructions on the right hand panel.
The rest of the options, leave as default.
Create your new SSH key
If you ended up using an existing SSH key, now would be the time to create one specifically for deploying applications to this server, and only that function.
The reason this is important is because we will be using the private key within our GitHub Actions, which means the private key generated will be leaving your local computer and stored within GitHub Secrets. In the event that this key is compromised, we want to limit its use.
Digital Ocean has some excellent documentation for this process here.
Enable floating IP
Once your Droplet has started, you'll want to
Enable Floating IP so that we have a static public IP address to route to for a domain/subdomain.
This can be done via
Select your droplet
Enable Floating IPat the top right.
Now that our Droplet is running and has a public IP address, we'll want to install Docker and docker-compose.
SSH into your Droplet using the appropriate SSH key and your preferred SSH client (straight
ssh, Putty for Windows, etc).
Eg, with a Linux
ssh client, the command would be
ssh root@<your_IP_or_domain> as
root is the default user setup when creating an Ubuntu droplet.
the user may change depending on how your server is setup. See
man ssh for more details/options.
Install docker and docker-compose
Installing Docker for Ubuntu 20.04 can be done via the repository with some setup or via Docker provided convenience scripts. For a more detailed walk through, DigitalOcean have a good write up here. Scripted included below for ease of use.
Docker via convenience script
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh
These scripts required sudo privileges, see Docker notes regarding security. Full repository based script available here. Docker is installed remoting under root in this example for simplification. Information of Docker security can be found in the Docker docs
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version to confirm.
See DigitalOcean article for details on ensuring you have the latest version installed.
Get nginx reverse proxy and letsencrypt companion running
Now we have Docker and docker-compose installed on our new Droplet, we want to setup an nginx reverse proxy running in Docker. This will handle mapping requests to specific domain/subdomain requests to specific docker applications that have matching configuration as well as TLS registration via LetEncrypt. When a new docker container starts up and joins the bridge network, the nginx and letsencrypt companion detect the new application and look to see if routing and TLS certificate is needed.
x mix release-ghr-vanilla template, we include
deploy/nginx-proxy-compose.yml file which can be copied to the droplet and run.
Here is the nginx docker-compose file in full.
version: '2' services: nginx-proxy: image: jwilder/nginx-proxy container_name: nginx-proxy restart: always ports: - "80:80" - "443:443" volumes: - conf:/etc/nginx/conf.d - vhost:/etc/nginx/vhost.d - html:/usr/share/nginx/html - dhparam:/etc/nginx/dhparam - certs:/etc/nginx/certs:ro - /var/run/docker.sock:/tmp/docker.sock:ro network_mode: bridge letsencrypt: image: jrcs/letsencrypt-nginx-proxy-companion:2.0 container_name: nginx-proxy-le restart: always environment: - DEFAULT_EMAILemail@example.com volumes_from: - nginx-proxy volumes: - certs:/etc/nginx/certs:rw - acme:/etc/acme.sh - /var/run/docker.sock:/var/run/docker.sock:ro network_mode: bridge volumes: conf: vhost: html: dhparam: certs: acme:
You can use
scp or shared clipboard to copy the short YML file over. For this example, we are going to copy it straight to the
~/ (home) directory.
scp -i <path to private ssh key> ./nginx-proxy-compose.yml root@<server_floating_ip>:~/nginx-proxy-compose.yml
Once copied, we can use
docker-compose to bring up the nginx reverse proxy.
docker-compose -f ~/nginx-proxy-compose.yml up -d
To confirm these are running, you can run
docker ps so have a look at what containers are running on your server.
Now our droplet server is all setup to host our docker applications, we want to make referring to our server easier by setting up a DNS record.
Specifically, we want to create an
A record pointing to our Floating IP of our Droplet server.
You will need to use your DNS provider service to manage the DNS records of your domains.
GitHub Repository Setup
With the Droplet server all setup, first we'll need an application to deploy!
Create a new repository on GitHub. This setup will work with public or private repositories, select your options and clone it to your local machine.
git clone <GitHub URL> cd <project name>
We are going to use
x new web as a command to create a blank ServiceStack application. Run this in your newly cloned repository folder, the project name will be derived from the repository directory name.
If you create the project in a new directory before hand and want to name it, use
x new web <project name>.
x new command gives us a working ServiceStack project from a template,
x mix allows us to add additional templated files that work with templated ServiceStack projects.
Now our project is created, you can mix in our GitHub Action templates in the local repository folder by running:
x mix build release-ghr-vanilla
build mix template provides a GitHub Action that builds and tests our dotnet project. The
release-ghr-vanilla provides a GitHub Action that uses Docker to package the application, pushes the Docker image to GitHub Container Registry (ghcr.io) and deploys the application via SSH +
docker-compose to our new Droplet.
Just like other
x mix templates ServiceStack provides, these are a starting point to help get things running quickly with known patterns. Unlike external dependencies, they just copy the templated code that is editable and not tied to any code generation service that will update these files.
Files provided by the
- .github/workflows/release.yml - Release GitHub Action Workflow
- deploy/docker-compose-template.yml - Templated docker-compose file used by the application
- deploy/nginx-proxy-compose.yml - File provided to get nginx reserve proxy setup as used by steps above.
- Dockerfile - Self contained Docker that builds, publishes and hosts your application.
Make sure GitHub
Enable improved container support is turned on
The account or organization of your repository at the time of writing needs to "Enable improved container support".
This step may no longer be required once Improved Container Support is generally available.
Improved container support
Enable improved container support
See GitHub Docs for details.
Once these steps are done, our GitHub Actions will be able to push Docker images to GitHub Container Registry.
Create new repository in GitHub first.
git clone firstname.lastname@example.org:<your user or org>/WebApp.git # Where "WebApp" is the name of your repository cd WebApp x new web x mix build release-ghr-vanilla git add -A git commit -m "Add new ServiceStack project with GitHub Action files" git push
- Create a new GitHub repository
- Clone new repository locally
- Change directory to new repository
- Locally create ServiceStack project using
- Mix in GitHub Actions using
- Commit and push changes to GitHub
x mix templates needs 6 pieces of information to perform the deployment, this information is added to the GitHub repository as the following secrets.
- CR_PAT - GitHub Personal Token with read/write access to packages.
- DEPLOY_HOST - hostname used to SSH to, this should be a domain or subdomain with A record pointing to the server's IP adddress.
- DEPLOY_PORT - SSH port, usually 22
- DEPLOY_USERNAME - the username being logged into via SSH. Eg,
- DEPLOY_KEY - SSH private key used to remotely access deploy server/app host.
- LETSENCRYPT_EMAIL - Email address for your TLS certificate generation
These secrets can use the GitHub CLI for ease of creation. Eg, using the GitHub CLI the following can be set.
gh secret set CR_PAT -b"<CR_PAT, Container Registry Personal Access Token>" gh secret set DEPLOY_HOST -b"<DEPLOY_HOST, domain or subdomain for your application and server host.>" gh secret set DEPLOY_PORT -b"<DEPLOY_PORT, eg SSH port, usually 22>" gh secret set DEPLOY_USERNAME -b"<DEPLOY_USERNAME, the username being logged into via SSH. Eg, `ubuntu`, `ec2-user`, `root` etc.>" gh secret set DEPLOY_KEY < [path to ssh key] gh secret set LETSENCRYPT_EMAIL -b"<LETSENCRYPT_EMAIL, Email address for your TLS certificate generation, eg email@example.com>"
CR_PAT can be created via your GitHub Settings->Developer Settings->Personal access tokens page, and selecting the
write:packages permission. Copy the token somewhere secure, so we can use it when creating the secrets.
Both the creation of the token and use in secrets are only available on creation, so if you want/need to reuse this, note it down somewhere secure like your password manager for reuse. The
CR_PAT(Container Registry Personal Access Token) is required during the beta of GitHub Container Registry, however once released the standard
secrets.GITHUB_TOKENbuilt into GitHub Actions should be able to be used and is recommended to avoid higher data transfer charges.
Repository secrets can be created under Settings->Secrets.
To kick off any new deployment, we use GitHub Releases.
Provide a version number and name, the version will be used to tag the Docker image in GHCR.io. If you are using the GitHub CLI, you can also do this via the command line. For example,
gh release create v1 -t "v1" --notes ""
Go to the Actions tab in your repository to see the progress of your deployment.
The initial deployment might take upto a minute for LetsEncrypt to generate and use the certificate with your domain. Make sure your DNS is all setup before publishing the Release, otherwise further delays related to DNS TTL will likely occur. If you are having problems with your app hosting, be sure to configure the logs in the nginx and your app docker containers for any startup issues. You can also run in attached mode to watch the output of these containers via
docker-compose -f ~/nginx-proxy-compose.yml up.
GitHub Container Registry Pricing
If you're already have a Pro or Team plan, you get free allowances to using the GitHub Container Registry. It has the same pricing as the GitHub Packages product which is summarized as the following for Pro or Team.
- 2GB storage free
- 10GB data transfer free
- Additional storage $0.25 per GB
- Additional transfer out $0.50 per GB (GitHub Actions are free)
With Docker images though, they can get large pretty quickly. While GitHub Container Registry is still in beta, it is free to use but additional storage and transfer costs are something to keep in mind. Hopefully use of retention policies and other features can help manage to keep these prices down.
Once GitHub Container Registry is released the standard
secrets.GITHUB_TOKENbuilt into GitHub Actions should be able to be used and is recommended to avoid higher data transfer charges.
Having a CI process from the very start of a project/prototype is something that pays off quickly, even as a solo developer. The
release-ghr-vanilla template is designed to help get that process started by providing a "no fuss" pattern for prototyping ideas and keeping costs down while giving a dockerized path forward as your hosting requirements change. GitHub Actions provide a great way to build and maintain your CI process right where your code lives, and even though GitHub Container Repository is in the early stage, we think it provides a simplified workflow that works well for the indie/solo developer as well as teams. We intend to put together more of these templates and patterns for different use cases, feel free to give us feedback and let us know what you'd like to see!