Over the past year I’ve spent a lot more time than I would have expected working in GitHub Actions. Prior to the past few weeks I brute forced my way through actions primarily around Jekyll and some of the sites I converted off of DNN to static Jekyll based content.

Recently though a client of mine asked that I help to setup a deployment pipeline for on of their DNN based websites, so I spent quite some time getting things figured out from a DNN perspective. This blog post is a result of that work plus additional time spent cleaning up the process to try to come up with something useful for other DNN Developers to use in their own custom modules.


TL;DR check out this GitHub Workflow for Continuous Deployment for DNN extensions.

A quick summary before I go into a lot more details below: I’ve setup a GitHub workflow that is designed to run in a DNN module’s repository on GitHub, specifically one that is built using my DNN Templates for Visual Studio and leveraging https://github.com/cantarus/polydeploy module for remote deployment. The action will setup the environment so that it can grab the deploy client executable, it compiles the module in release mode to build the INSTALL zip package, it then uploads that Zip file to the website configured as the deployment target.



Now let’s get into the details on how to setup your own DNN Extensions to use GitHub Actions for automating their deployment.


The workflow depends on you having Cantarus’ PolyDeploy module installed and running on a DNN site that you will be deploying to. I started with Brian Dukes’ fork of PolyDeploy as he has done some cleanup that hasn’t been merged back into the original repo. You need to download the INSTALL package and install that on your production (Target) website. This is what will provide the services on the site that your Action will leverage for remote install. Once you have that installed you will need to setup a new API User, that user will be provided an API Key and Encryption Keys that you will need to make GitHub Secrets. You can only view these when you create the account, after that they will be masked out of view.



GitHub Secrets

These provide the credentials and security to allow access for the script to deploy your extension, you should protect these, because with this information anyone can upload extensions/code to your website. In your GitHub repository (for your extension) you will need to create three secrets, API_KEY, ENCRYPTION_KEY and TARGET_URI.




The Workflow File

You will need to configure a Workflow file in the the path .github/workflows in the root of your solution/project file. You can check out the Workflow that I have configured for dnnSimpleArticle and use it as your base, you will want to change the NAME of the module throughout the file, search and replace dnnsimplearticle. In the case of my module I have created a file called dnnsimplearticle.yml, you can name the YAML file whatever you like, likely something along the lines of your project/solution name.

Dig Into the Workflow

The workflow is built using the windows-latest image, so that we can easily compile the .NET project/solution that makes up our extension. It is configured to be executed anytime you check in against the MAIN branch. (side note: I need to rename the branch in this repo, nevermind I did that)


  runs-on: windows-latest

After that the file gets all of the content of the dnnSimpleArticle repository using the actions/checkout@v2 script, and then it loads a script to configure the MSBuild path that will be used later in the compilation call.

- name: Checkout

  uses: actions/checkout@v2

- name: Add msbuild to PATH

  uses: microsoft/[email protected]

In order to perform the upload call to the PolyDeploy API services on the target website, I use the deploy client command line executable that Cantarus has built. To use that, the action downloads Brian Dukes’ latest DeployClient package. That package is then extracted into a local folder /polydeploy/out for use later in the script.

- uses: robinraju/release-downloader@v1


    repository: "bdukes/polydeploy"

    fileName: "DeployClient_00.09.03.zip"

    tag: "v0.9.3-custom"

    # stick the deploy client in polydeploy folder

    out-file-path: "polydeploy"

- name: Unzip PolyDeploy Client

  run: |

  Expand-Archive -Path 'polydeploy\DeployClient_00.09.03.zip' -DestinationPath 'polydeploy\out\'

  shell: powershell

From there we need to ensure that the proper nuget packages are loaded so that we can then compile and create a package from the module. To do that we call nuget package restore and then use MSBUILD to do the compilation for us. The MSBuild call is pretty simple, pass in the solution file and the release mode option so that the buildscripts that come with my templates fire to perform the packaging.

- name: Restore NuGet Packages

  run: nuget restore dnnsimplearticle.sln

- name: Build and Publish Web App

  run: msbuild dnnsimplearticle.sln /p:Configuration=Release

The next two steps are fairly straight forward, we need to get the version number of our Extension package that was just built that gets stored into an output variable (steps.getversion.outputs.versionString). We get that with the help of a GitHub action courtesy of Daniel Valadas, his action will read from the DNN manifest file in our project. With that we need to put the INSTALL package for our extension into the DeployClient folder, I build the name of the file using the versionString variable and the rest of the name of the module’s package. I can probably automate more of this naming in the future.

- name: get-version-from-dnn-manifest

  uses: valadas/[email protected]

  id: getversion


    manifestPath: dnnsimplearticle.dnn

- name: Copy Install Package to deployclient folder

  run: |

    cp install/DNNSimpleArticle_${{ steps.getversion.outputs.versionString }}_Install.zip polydeploy/out/DNNSimpleArticle_${{ steps.getversion.outputs.versionString }}_Install.zip

  shell: cmd

The final thing is to have the deploy client executable perform the deployment for us. It’s pretty straight forward, you’ll need to pass in a number of parameters to the executable including the SECRETS configured earlier.

- name: Run DeployClient to upload Zip to Production

  run: |

    cd polydeploy/out

    DeployClient.exe --no-prompt --target-uri ${{ secrets.TARGET_URI }} --api-key ${{ secrets.API_KEY }} --encryption-key ${{ secrets.ENCRYPTION_KEY }}

  shell: cmd

You can track the status of the workflow by looking at the actions tab Actions tab. If you clicked on that link, that’s the actions tab for dnnSimpleArticle, you’ll see a lot of trial an error if you look at the early action history.

This is the summary view of a successful workflow check in for dnnSimpleArticle.


If you click on the job on the left you can get a lot more detail on the workflow process, super useful if you’re trying to track down errors.



In the PolyDeploy module on your production site you can find an event log showing the installation’s success.



I am not claiming this is the most efficient way to do automated deployments for DNN extensions, but for now, it works well enough for my use case. I may make some tweaks to this over time, we shall see. I also am not covering any specifics about what your development workflow is or should be. At this point, with dnnSimpleArticle, if I check code in on the MAIN branch, that code will get compiled and deployed to the target website I had configured. You might not want to deploy quite so frequently, if that’s the case you may want to do most of your work out of other branches and only merge into the MAIN branch when ready. Or you might want to liver dangerously and deploy early and often, that’s what I like to practice.

A note of thanks: Brian Dukes helped give me some breadcrumbs as I went down this journey, I always appreciate his help!