Using Octopus Deploy to deploy .NET Core applications to a Raspberry Pi

Published on: 21 Feb 2018 by: Ben Pearce

Octopus enjoying a Raspberry Pi

.NET Core has come a long way in the last few years, and Octopus Deploy has too. A while back, we added support for running Calamari without Mono, and in this post I will walk you through how to deploy .NET Core applications on to a Raspberry Pi 3, no Mono required.

In this post, I will show you that it is possible to deploy and run .NET Core applications on the Raspberry Pi 3, and along the way describe some of the different ways that you can interact with your Octopus Deploy server.

At the time of writing, support for ARM architecture is not included by default and needs to be manually enabled.

Requirements Before Starting

ASP.NET includes NodeServices in its bundle which requires Node to be installed before it can serve any requests. When you install Node.js on the Raspberry Pi, it installs version 4.x and the executable is called nodejs, but NodeServices is looking for node in your path. You can fix this by creating a symlink: sudo ln -s /usr/bin/nodejs /usr/bin/node

Build the Application

Create a Basic .NET Core Application

dotnet new angular

Modify the Application to Listen For External Requests

By default, an ASP.NET Core application will only serve requests to http://localhost:5000, to allow the web host to serve requests to your local network, add the following after the .UseStartup<Startup>() in Program.cs:

.UseKestrel(options => {
    options.Listen(System.Net.IPAddress.Any, 5000);
})

For more information on configuring the Kestrel Web Host, check the docs.

Build the Application

npm install
dotnet build
mkdir publish
dotnet publish -o publish --self-contained -r linux-arm

Package It Up

The simplest way to create a package of a .NET Core application is using the Octo.exe command line tool.

Create an artifacts directory and then use the Octo Pack command to create the package:

mkdir artifacts
octo.exe pack --id core4pi --version 1.0.0 --format zip --outFolder artifacts --basePath publish

Using Octo.exe again, push the package to the server:

octo.exe push --server http://octopus/ --apikey API-ABCDEF123456 --package artifacts\core4pi.1.0.0.zip

Building a Service Definition

To get the application to run as a service, see Microsoft’s documentation for hosting .NET Core on Linux.

Create a file called core4pi.service containing the following text:

[Unit]
Description=core4pi

[Service]
WorkingDirectory=#{Octopus.Action[deploy web site].Output.Package.InstallationDirectoryPath}
ExecStart=/usr/local/bin/dotnet "#{Octopus.Action[deploy web site].Output.Package.InstallationDirectoryPath}/core4pi.dll"
Restart=always
RestartSec=10
User=pi
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

The [deploy web site] strings in the core4pi.service text above, represents the name of the step that deploys the package.
This output variable will contain the path to the newly installed service. This will ensure when the service is installed it is looking at the latest version.

Create a package for the service definition and push it to the Octopus Server:

octo.exe pack --id core4pi.service --version 1.0.0 --format zip --outFolder artifacts
octo.exe push --server http://octopus/ --apikey API-ABCDEF123456 --package artifacts\core4pi.service.1.0.0.zip

Create Infrastructure

If you don't already have an Octopus environment configured for your Raspberry Pi, create one either at the command line:

octo.exe create-environment --server http://octopus/ --apikey API-ABCDEF123456 --name "Pi Dev"

Or using the web interface via Infrastructure > Environments > Add Environments.

Next, create an account to access the Pi, this can either be a Username / Password or an SSH Key in the Infrastructure > Accounts section in the web interface.

Then finally, create a deployment target under Infrastructure > Deployment Targets > Add Deployment Target as an SSH target. Set the targets role to something that represents the responsibility of the target, e.g PiWeb. After filling in the details (IP Address or DNS name, SSH port and account), under the .NET section, ensure that you select Mono not installed, don't worry about the platform, we will change that later.

Modify the Target Config to Specify the Calamari Version as linux-arm

This code can easily be run from LinqPad

string machineId = "Machines-1";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(@"http:\\octopus");
client.DefaultRequestHeaders.Add("X-Octopus-Apikey", "API-ABCDEF123456");
var machineJson = client.GetAsync($"api/machines/{machineId}").Result.Content.ReadAsStringAsync().Result;
machineJson = machineJson.Replace("linux-x64","linux-arm");
client.PutAsync($"api/machines/{machineId}", new StringContent(machineJson));

The machine id, defined on the first line, can be obtained from the Web Portal URL when viewing the deployment target app#/infrastructure/machines/_machineId_/settings or using the command line:

octo list-machines --server http://octopus/ --apikey API-ABCDEF123456

You can also filter the list at the command line by using the JSON output format and filtering in Powershell:

octo list-machines --server http://octopus/ --apikey API-ABCDEF123456 --outputformat=json | 
    ConvertFrom-Json | 
    % { $_ } |
    Where { $_.Name -eq 'target name' }

The % { $_ } line unwraps the top-level array that is being returned, which seems to be a quirk of the ConvertFrom-Json command in Powershell.

Download Calamari for linux-arm

curl https://octopus.myget.org/F/octopus-dependencies/api/v2/package/Calamari.linux-arm/4.4.15 -L -o "c:\Program Files\Octopus Deploy\Octopus\Calamari.linux-arm.nupkg"

Replace the output path, -o, with the path to your Octopus Installation, if required.

The linux-arm Calamari package, will be provided in future releases.
If upgrading the Octopus Server installation before support is included by default, you will need to update the Calamari version to match the version of the other Calamari packages. The version number can be obtained from the Calamari.nupkg package using Nuget Package Explorer.

Creating the Deployment Project

Create a new Project via the section in the Octopus web interface, or using the command line:

octo create-project --server http://octopus/ --apikey API-ABCDEF123456 --name "PiWeb" --projectgroup "All projects" --lifecycle "Default Lifecycle"

Create a Deployment Step For the Application

In the new PiWeb project, define your deployment process.

Add a Deploy a Package step, called deploy web site.

The step name here will allow the values in the service definition file to be updated correctly.

Set the Environment to the Pi Dev environment.

Set the Role to the PiWeb role (or whatever you set the SSH target role to).

Under the Package section, select the package that you pushed to the server, core4pi.

The rest of the options in here don't need to be configured. Save it.

Create a Deployment Step For the Service Definition

Add another Deploy a Package step. This one will install a service on the target to run the application. For the package selection, select the core4pi.service package from the Octopus Server (built in) package feed.

You will need to Configure Features for this step:

In the Substitute Variables in Files feature add the name of the service definition file core4pi.service:

Under the Configuration Scripts feature, select Bash, paste the below script in to the Deployment Script section:

#!/bin/bash
if [ -e /lib/systemd/system/core4pi.service ]
then
    echo stopping service
    sudo systemctl stop core4pi.service
fi

echo installing service
sudo cp core4pi.service /lib/systemd/system/
sudo chmod 644 /lib/systemd/system/core4pi.service
sudo systemctl daemon-reload
sudo systemctl enable core4pi.service
echo starting service
sudo systemctl start core4pi.service

This script will be executed during the step execution and performs the service installation.

Deploy It

On the Project navigation menu, press Create Release.

The Create Release page will allow you to set a version number for the release, you can just leave the default. It will also allow to pick which versions of the packages you want to deploy, by default it will pick the latest version.

Press Save and then press Deploy to PI Dev and then Deploy to start the deployment process.

Create Release can also be performed from the command line

octo create-release --server http://octopus/ --apikey API-ABCDEF123456 --project "PiWeb"
octo deploy-release --server http://octopus/ --apikey API-ABCDEF123456 --project "PiWeb" --deployto="Pi Dev" --version "0.0.1"

The first time you deploy, Octopus Server will update Calamari on the target machine, this may take a couple of minutes.

Test It

After the deployment has finished, navigate to the IP address or DNS name of your Raspberry Pi on port 5000, you should see the application

Conclusion

With the alignment of a number of different technologies, deploying .NET to a Raspberry Pi is possible, and Octopus Deploy makes it painless. Throughout this post, you have also seen a number of different ways that you can integrate with your Octopus server, including command line, API and the web portal.

If you are interested in automating the deployment of your .NET Core applications, download a trial copy of Octopus Deploy, and take a look at our documentation.