Octopus Deploy Documentation

Deploy an ASP.NET Core (Docker) application to Kubernetes using Octopus and GitHub Actions

In this tutorial, we show you how to build a fully-functional continuous delivery pipeline for a simple ASP.NET Core (Docker) web application and deploy it to Kubernetes. We use GitHub Actions to build the code and run tests, and we use Octopus Deploy to deploy and promote releases.

To get up and running quickly, the TestDrive VMs provide preconfigured environments demonstrating various continuous delivery pipelines documented in these guides.

Introduction

The application we'll deploy is called Random Quotes, which is a simple web application that randomly displays a famous quote each time the page loads. It consists of a web front end and a database that contains the quotes. We'll build a complete Continuous Integration/Continuous Delivery (CI/CD) pipeline with automated builds, deployments to a dev environment, and sign offs for production deployments.

Deployment pipeline

For this tutorial, we assume you use Git for version controlling changes to your source code and GitHub Actions to compile code and run unit tests. Octopus Deploy will take care of the deployment. Here is what the full continuous integration and delivery pipeline will look like when we are finished:

Git C# C# C# TeamCity Artifactory create release DEV TEST PRODUCTION Release 1.1

The development team's workflow is:

  1. Developers commit code changes to Git.
  2. GitHub Actions detects the change and performs the continuous integration build, this includes resolving any dependencies and running unit tests.
  3. When the GitHub Actions build completes, the change will be deployed to the Dev environment.
  4. When one of your team members (perhaps a tester) wants to see what's in a particular release, they can use Octopus to manually deploy a release to the Test environment.
  5. When the team is satisfied with the quality of the release and they are ready for it to go to production, they use Octopus to promote the release from the Test environment to the Production environment.

Since Octopus is designed to be used by teams, in this tutorial we also set up some simple rules:

  • Anyone can deploy to the dev or test environments.
  • Only specific people can deploy to production.
  • Production deployments require sign off from someone in our project stakeholders group.
  • We'll send an email to the team after any test or production deployment has succeeded or failed.

This tutorial makes use of the following tools:

Octopus is an extremely powerful deployment automation tool, and there are numerous ways to model a development team's workflow in Octopus Deploy, but this tends to be the most common for small teams. If you're not sure how to configure Octopus, we recommend following this guide to learn the basics. You'll then know how to adjust Octopus to suit your team's workflow.

This tutorial takes about an hour to complete. That sounds like a long time, but keep in mind, at the end of the tutorial, you'll have a fully-functional CI/CD environment for your entire team, and you'll be ready to deploy to production at the click of a button. It's worth the effort!

Build vs. deployment

For any non-trivial application, you're going to deploy the software to multiple environments. For this tutorial, we're using the environments Dev, Test, and Prod. This means you need to choose between building your application once or building it before each deployment? To reduce the risk of a failed production deployment, Octopus strongly encourages the practice of building once, and deploying multiple times.

The following activities are a build time concern, so they will happen in GitHub Actions after any change to code is committed to Git:

  1. Check out the latest changes from Git.
  2. Resolve and install any dependencies from NuGet.
  3. Run unit tests.
  4. Create the Docker image.

This results in a green CI build and a Docker image that contains the application and everything it needs to run. Any configuration files will have their default values, but they won't know anything about dev vs. production settings just yet.

Lastly, it's very important that we give this Docker image file a tag with a version number. We will produce a new Docker image every time our CI build runs, and we don't want to accidentally deploy a previous version of the Docker image.

At this point, we have a single artifact that contains all the files our application needs to run, ready to be deployed. We can deploy it over and over, using the same artifact in each environment. If we deploy a bad release, we can go and find the older version of the artifact and re-deploy it.

The following activities happen at deployment time by Octopus Deploy:

  1. Changing any configuration files to include settings appropriate for the environment, e.g., database connection strings, API keys, etc.
  2. Running tasks that need to be performed during the deployment such as database migrations or taking the application temporarily offline.

Prerequisites

There are a number of tools you need to install to implement a complete CI/CD workflow. These include the GitHub Actions and Octopus servers, some command-line tools, and Kubernetes to host the final deployment.

Git

The source code for the sample application is hosted on GitHub. To access the code, you need the Git client. The Git documentation has instructions to download and install the Git client.

GitHub Actions

GitHub Actions are available for free on public GitHub repsoitories and with paid options for private repositories.

.NET Core SDK

The .NET Core SDK version 2.2 or above is required to compile and run the ASP.NET Core sample application. The SDK can be downloaded from the Microsoft .NET website.

The .NET Core SDK must be installed locally for development and also on the GitHub Actions server or any agents that will compile the application code.

Getting Started with Octopus Cloud

Before you can start an Octopus Cloud trial, you'll need an Octopus account.

You can sign up for an account at: octopus.com/register.

Create an Octopus Account

An Octopus account lets you manage your instances of Octopus Cloud.

  1. Enter your name.
  2. Provide your email address and click Create a password. Please note, these credentials are for your Octopus Account. You will also create credentials for your Octopus Cloud instance, when you create it.
  3. On the next screen, provide your company name.
  4. Chose a secure password and enter it twice.
  5. Click Create my Octopus account.

Create a Cloud Instance

  1. From the instances screen, click Create cloud instance.
  2. Enter an instance name for your Octopus Cloud instance.
  3. Choose a URL for the instance.
  4. Select the Cloud region for your instance. Currently the only option is US - Oregon.
  5. Click Enter account details.
  6. Create your first user for Octopus Cloud.
  7. Enter the username the user will use to log into Octopus Cloud.
  8. Create a password for the user and confirm the password.
  9. Click Continue to Confirmation.
  10. Confirm the details you've provided, agree to the terms, and click Looks good. Deploy my Octopus!.

You will be taken to the account provisioning screen. Please note it can take five to ten minutes for your Octopus Cloud instance to be ready. You will receive an email when the instance is ready to use.

When the instance is ready, you will see it (and any other instances you have access to) the next time you log in to your Octopus account at https://account.octopus.com/account/signin.

Kubernetes

There are many Kubernetes providers, and this guide makes use of Minikube, which is a Kubernetes distribution that creates a local environment for testing and development.

Octopus requires the kubectl command-line tool to be installed in order to interact with the Kubernetes cluster.

The certificate files created by Minikube will be processed using OpenSSL. Windows users can download a distribution of OpenSSL here. Linux users can install OpenSSL via their distribution's package repositories.

Docker

Building and pushing Docker images requires Docker to be installed. The Docker documentation has instructions on how to install Docker.

Docker Hub

A Docker Registry is required to host the Docker images. This guide makes use of Docker Hub.

The Built-in Octopus feed can not be used to host Docker images. A dedicated Docker Registry is required when deploying Docker images via Octopus.

Clone source code

The source code for the random quotes application is hosted in GitHub. The code can be cloned from https://github.com/OctopusSamples/RandomQuotes.git with the command:

git clone https://github.com/OctopusSamples/RandomQuotes.git

Octopus API key

In order to allow GitHub Actions to communicate with Octopus, we need to generate an API key. This is done in the Octopus web portal. The web portal can be opened from the Browse link in the Octopus Manager:

Screenshot of the Octopus Manager

From the Octopus Deploy web portal, sign in, and view your profile:

Screenshot of the Octopus profile drop down menu

Go to the API keys tab. This lists any previous API keys that you have created. Click on New API key:

Screenshot of the Octopus profile API keys page

Give the API key a name so that you remember what the key is for, and click Generate New:

Screenshot of the new API key dialog

Copy the new API key to your clipboard:

Screenshot of new API key

Continuous integration

GitHub Actions supports Node.js, Python, Java, Ruby, PHP, Go, Rust, .NET, and more. Build, test, and deploy applications in your language of choice. In this tutorial, we rely on GitHub Actions to do the following:

  • Clone the code from Git.
  • Resolve and install any dependencies from NuGet.
  • Run unit tests.
  • Create the Docker image.
  • Publish it to Docker Hub.

At a high level, GitHub Actions workflows are YAML documents committed to a GitHub repsository that define the runner that the steps are executed on, the triggers that execute the workflow, and the steps to be run.

GitHub Actions workflows have access to encrypted secrets. Two secrets are defined called:

  • OCTOPUS_SERVER_URL, which defines the URL of the Octopus server (for example, https://myserver.octopus.app for hosted Octopus instances)
  • OCTOPUS_API_TOKEN, which defines the API key used to access the Octopus instance.

The YAML document below is saved to a file called .github/workflows/build.yaml. It is configured to be triggered on a git push and defines steps to build, test, package, and publish the sample application:

name: Docker Build
'on':
  workflow_dispatch: {}
  push: {}
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
      with:
        fetch-depth: '0'
    - name: Set up DotNET Core
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 3.1.402
    - name: Set up QEMU
      uses: docker/setup-qemu-action@v2
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2
    - name: Login to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_SAMPLES_USERNAME }}
        password: ${{ secrets.DOCKERHUB_SAMPLES_PASSWORD }}
    - name: Install GitVersion
      uses: gittools/actions/gitversion/setup@v0.9.14
      with:
        versionSpec: 5.x
    - id: determine_version
      name: Determine Version
      uses: gittools/actions/gitversion/execute@v0.9.14
      with:
        additionalArguments: /overrideconfig mode=Mainline
    - name: Install Octopus Deploy CLI
      uses: OctopusDeploy/install-octopus-cli-action@v1
      with:
        version: latest
    - name: Install Dependencies
      run: dotnet restore
      shell: bash
    - name: Test
      run: dotnet test -l:trx
      shell: bash
    - if: always()
      name: Report
      uses: dorny/test-reporter@v1
      with:
        name: DotNET Tests
        path: '**/*.trx'
        reporter: dotnet-trx
        fail-on-error: 'false'
    - name: Build and push
      uses: docker/build-push-action@v3
      with:
        context: RandomQuotes
        push: true
        tags: octopussamples/randomquotes:${{ steps.determine_version.outputs.semVer }}
    - name: Create Octopus Release
      uses: OctopusDeploy/create-release-action@v1.1.1
      with:
        api_key: ${{ secrets.OCTOPUS_API_TOKEN }}
        project: Random Quotes
        server: ${{ secrets.OCTOPUS_SERVER_URL }}
        deploy_to: Dev
        packages: Deploy container to Kubernetes:randomquotes:${{ steps.determine_version.outputs.semVer }}
    

There is a lot of work being performed by this workflow, so let's break it down.

The workflow is triggered on each push to the git repository. The workflow can also be triggered manually with the workflow_dispatch event:

name: Docker Build
'on':
  workflow_dispatch: {}
  push: {}

The job called build is run on an Ubuntu runner:

jobs:
  build:
    runs-on: ubuntu-latest
    

The first step checks out the source code from the git repository. Setting the fetch-depth option to 0 means all the history for all branches and tags is fetched. This is required by the GitVersion action used in later steps:

    steps:
    - uses: actions/checkout@v3
      with:
        fetch-depth: '0'

This step sets up DotNET Core 3 which is used to build the application source code:

    - name: Set up DotNET Core
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 3.1.402

These steps setup Docker and log into Docker Hub:

    - name: Set up QEMU
      uses: docker/setup-qemu-action@v2
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2
    - name: Login to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_SAMPLES_USERNAME }}
        password: ${{ secrets.DOCKERHUB_SAMPLES_PASSWORD }}

GitVersion builds meaningful version strings based on the commits to git.

This step installs GitVersion:

    - name: Install GitVersion
      uses: gittools/actions/gitversion/setup@v0.9.14
      with:
        versionSpec: 5.x

This step calls GitVersion which outputs many different variables representing different parts of a SemVer version string generated from the commits to the git repository. Later steps use these output values as the version of the package generated by this workflow:

    - id: determine_version
      name: Determine Version
      uses: gittools/actions/gitversion/execute@v0.9.14
      with:
        additionalArguments: /overrideconfig mode=Mainline

This step installs the Octopus CLI, which is used by the subsequent Octopus GitHub Actions:

    - name: Install Octopus Deploy CLI
      uses: OctopusDeploy/install-octopus-cli-action@v1
      with:
        version: latest

This step installs the application's dependencies:

    - name: Install Dependencies
      run: dotnet restore
      shell: bash

This step runs the unit tests:

    - name: Test
      run: dotnet test -l:trx
      shell: bash

This step publishes the results of the test against the workflow run. The if parameter is set to always() to ensure that the results are published even if the tests fail:

      - if: always()
        name: Report
        uses: dorny/test-reporter@v1
        with:
          name: DotNET Tests
          path: '**/*.trx'
          reporter: dotnet-trx
          fail-on-error: 'false'

This step builds the Docker image and pushes it to Docker Hub:

    - name: Build and push
      uses: docker/build-push-action@v3
      with:
        context: RandomQuotes
        push: true
        tags: Deploy container to Kubernetes:randomquotes:${{ steps.determine_version.outputs.semVer }}

Commit the file and push it to the GitHub repository with the command:

git add .; git commit -m "Added workflow file"; git push

GitHub Actions then runs the workflow.

Deploying with Octopus Deploy

Now that GitHub Actions has successfully built the application, we need to configure Octopus to deploy it into our environments.

Create the environments

Environments represent the stages that a deployment must move through as part of the deployment pipeline. We'll create three environments: Dev, Test, and Prod.

Log into Octopus, and click the Infrastructure link, then the Environments link, and click ADD ENVIRONMENT:

A screenshot of the Octopus environments page

Enter Dev as the New environment name, and click SAVE:

A screenshot of the new environment dialog creating an environment called Dev

Repeat the process for the Test and Prod environments:

A screenshot of the new environment dialog creating an environment called Test A screenshot of the new environment dialog creating an environment called Prod

Uploading the certificates

We need to upload two certificates to Octopus in order to connect to the Minikube Kubernetes cluster. The certificates are defined in the certificate-authority, client-certificate, and client-key fields in the ~/.kube/config file generated when Minikube was installed. An example of the Kubernetes configuration file is shown below:

apiVersion: v1
clusters:
- cluster:
    certificate-authority: /home/matthew/.minikube/ca.crt
    server: https://172.18.93.203:8443
  name: minikube
contexts:
- context:
    cluster: minikube
    user: minikube
  name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
user:
  client-certificate: /home/matthew/.minikube/client.crt
  client-key: /home/matthew/.minikube/client.key

The first file to be uploaded is the client certificate. This certificate contains the contents of both the client-certificate and client-key files merged into a PFX file. We use OpenSSL to generate the PFX file with the command:

openssl pkcs12 -passout pass: -export -out /tmp/client.pfx -in ~/.minikube/client.crt -inkey ~/.minikube/client.key

To upload this certificate, click the Library link:

Click the Certificates link:

Click Add Certificate:

Enter Minikube Client Certificate as the Name:

Select the generated PFX file as the Certificate File, and click SAVE:

The second file is the cluster certificate authority. Click the Library link:

Click the Certificates link:

Click Add Certificate:

Enter Minikube CA as the Name:

Select the Kubernetes CA certificate file as the Certificate File, and click SAVE:

Create the Kubernetes target

Kubernetes clusters are represented in Octopus as targets.

To create a target for the Minikube cluster, click the Infrastructure link:

Click the Deployment Targets link:

Click the ADD DEPLOYMENT TARGET link:

Click Kubernetes Cluster, and click ADD for the Kubernetes Cluster Target tile:

Enter Kubernetes as the Display Name:

Select the Dev environment:

Enter k8s as the Target Role:

Select Client Certificate as the Authentication option, and select the Minikube Client Certificate we created earlier:

Enter the URL of the Minikube cluster. This IP address of the cluster can be found by running the command:

minikuke ip

Select the Minikube CA certificate we created earlier as the cluster certificate, enter randomquotes-dev as the Kubernetes namespace, and click SAVE:

Repeat the process to create targets for the Test and Prod environments, updating the target name and namespace accordingly.

Create the Octopus deployment project

With the environments defined and a target created, we now need to create a deployment project in Octopus.

Log into Octopus, click the Projects link, and click ADD PROJECT:

A screenshot of the ADD PROJECT button

Enter Random Quotes for the project name, and click SAVE:

A screenshot of the Add New Project dialog

We start by defining the variables that we will consume as part of the deployment. Click the Variables link, and then select the Project option:

The first variable we'll define is called appsettings. The contents of this variable is an environment agnostic copy of the appsettings.json file found in the ASP.NET Core project compiled into the Docker image. Specifically, the EnvironmentName key has been replaced with a marker that will be replaced by the name of the environment Octopus is deploying into. The value of this variable is shown below:

{
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Warning"
        }
    },
    "AppSettings": {
        "AppVersion": "0.0.0",
        "EnvironmentName": "#{Octopus.Environment.Name}"
    }
}

The contents of this variable will be exposed as the appsettings.json file by Kubernetes later in the deployment process:

We define a variable called K8S Node Port with unique values bound to each environment. This means that when we deploy the project to each environment, a new port will be used to expose the application in the Kubernetes cluster.

By exposing each environment as a unique port, we can implement multiple environments with a single cluster.

Kubernetes Node Ports are in the range 30000 - 32767 by default.

  • Define a value of 30000 scoped to the Dev environment.
  • Define a value of 30001 scoped to the Test environment.
  • Define a value of 30002 scoped to the Prod environment.

Then click SAVE to save the changes:

We will now define the deployment process. Click the Deployments link, then click the Overview link, and then click DEFINE YOUR DEPLOYMENT PROCESS:

Click ADD STEP:

Enter Kubernetes into the search box:

Click ADD on the Deploy Kubernetes containers tile:

Enter Deploy container to Kubernetes as the Step name:

Enter k8s as the Role:

Enter randomquotes as the Deployment name:

Click ADD VOLUME:

Enter appsetting-volume as the Volume name, select the Reference the config map created as part of this step option, and click OK.

This will create a Kubernetes volume whose files are defined in a ConfigMap. The ConfigMap will be defined in later steps. It is this volume that will expose our transformed configuration file, whose contents we defined as an Octopus variable earlier:

Click ADD CONTAINER:

Enter randomquotes as the Image Name:

Enter the Docker image name created by the CI server as the Package ID.

Replace dockerhubusername with your Docker Hub username.

Click ADD PORT:

Enter web as the Port name and the port exposed by the container as the Port number:

Click ADD VOLUME MOUNT:

Enter appsettings-volume as the Volume name, enter /app/appsettings.json as the Mount path, and appsettings.json for the Sub path.

The appsettings.json sub path references a key in the ConfigMap that we will create in later steps. The /app/appsettings.json mount path specifies that the contents of the ConfigMap item with the key appsettings.json will be exposed as the file /app/appsettings.json. This is how we overwrite the contents of a Docker image file to make it environment agnostic.

Click OK to save the container details:

Enter randomquotes as the Service Name:

Click ADD PORT:

Enter web as the Service port name, the port exposed by the container as the Service port, and #{K8S Node Port} as the Node port. Then click OK.

By using a variable for the node port, we can expose different environments on different ports within the same cluster:

Enter randomquotes as the Config Map Name.

Click ADD CONFIGMAP ITEM, enter appsettings.json as the Key and #{appsettings} as the value.

This ConfigMap is the one referenced by the Volume we created earlier, and the appsettings.json key is the sub path we referenced in the container volume mapping.

Click Save to save the step:

Deploy!

We now have a deployment project in Octopus ready to deploy our ASP.NET Core (Docker) application to our Dev, Test, and Prod environments. The next step is to create and deploy a release.

Click CREATE RELEASE.

The release creation screen provides an opportunity to review the packages that will be included and to add any release notes. By default, the latest package is selected automatically. Click SAVE:

This screen allows you to select the environment that will be deployed into. Lifecycles can be used to customize the progression of deployments through environments (this is demonstrated later in the guide), however, we will accept the default option to deploy to the Dev environment by clicking DEPLOY TO DEV...:

Click DEPLOY to deploy the application into the Dev environment:

The application is then deployed:

Congratulations! You have now built and deployed your first application. Visit http://minikubeip:30000 in a browser (replacing minikubeip with the IP address of your Minikube cluster) to view a random quote. Note the port (30000) matches the value of the #{K8S Node Port} variable that we assigned to the Kubernetes service exposing the web application:

Continuous deployments

The process of deploying a successful build to the Dev environment is currently a manual one; GitHub Actions pushes the file to Octopus, and we must trigger the initial deployment to the Dev environment from within Octopus. Typically though, deployments to the Dev environment will be performed automatically if a build and all of its tests succeed.

To trigger the initial deployment to the Dev environment after a successful build, we will go back to the project we created in GitHub Actions and add an additional step to create an Octopus release and then deploy it to the Dev environment.

When added to the workflow file, the YAML below creates a new release in Octopus with each build and deploys it to the development environment.

    - name: Create Octopus Release
      uses: OctopusDeploy/create-release-action@v1.1.1
      with:
        api_key: ${{ secrets.OCTOPUS_API_TOKEN }}
        project: Random Quotes
        server: ${{ secrets.OCTOPUS_SERVER_URL }}
        deploy_to: Dev
        packages: Deploy container to Kubernetes:randomquotes:${{ steps.determine_version.outputs.semVer }}

Additional configuration

Now we will explore some of the more advanced features of Octopus that allow us to customize the deployment progression through environments, secure deployments to production environments, add deployment sign offs, view the audit logs, and add notifications.

Lifecycles

Our project currently uses the default lifecycle, which defines a progression through all the environments in the order they were created.

A custom lifecycle allows the progression of a deployment to be further refined, by defining only a subset of environments that can be deployed to, allowing some environments to be skipped entirely, or requiring that a minimum number of environments are successfully deployed to before moving onto the next environment.

Here we will create a custom lifecycle that makes deployments to the Dev environment optional. This means that initial deployments can be made to the Dev or Test environments, but a successful deployment must be made to the Test environment before it can be progressed to the Prod environment.

Skipping the Dev environment like this may be useful for promoting a release candidate build directly to the Test environment for product owners or testers to review.

Click the Library link, click the Lifecycles link, and click ADD LIFECYCLE:

A screenshot showing the Octopus lifecycles page

Set the lifecycle name to Dev, Test, and Prod, and the description to Progression from the Dev to the Prod environments:

A screenshot showing the new lifecycle name and description

Phases are used to group environments that can accept a deployment. Simple lifecycles, such as the lifecycle we are creating, have a one-to-one relationship between phases and environments.

Click ADD PHASE:

A screenshot showing the ADD PHASE button

Enter Dev as the phase name, and select the Optional phase option. This means that deployments can skip this phase and any environments defined in it, and deploy directly to the next phase.

Because we are mapping each environment to its own phase, the name of the phase matches the name of the environment:

A screenshot showing the new lifecycle phase

Click ADD ENVIRONMENT:

A screenshot showing the ADD ENVIRONMENT button

Click the dropdown arrow, select the Dev environment, and click OK:

A screenshot showing the selection of the Dev environment

Repeat the process to add a new phase for the Test and Prod environments, leaving the default All must complete option selected:

A screenshot showing the selection of the Test environment A screenshot showing the selection of the Prod environment

Click SAVE:

A screenshot showing the SAVE button

Now, we need to switch the deployment project from the Default Lifecycle to the newly created lifecycle.

Click the Projects link, and click the Random Quotes project tile:

A screenshot showing the Random Quotes project

Click the Process link, and click CHANGE:

A screenshot showing the lifecycle CHANGE button in the project's process screen

Select the Dev, Test, and Prod lifecycle. Notice the Dev environment is shown as optional.

Click SAVE:

A screenshot showing the lifecycle selection dialog

Click CREATE RELEASE, and click SAVE to save the new release:

A screenshot showing the release creation screen

Because the Dev environment has been configured as optional, the initial release can be made to either the Dev or Test environments. We'll skip the Dev environment and deploy straight to Test.

Click DEPLOY TO..., and select the Test environment:

A screenshot showing the Test environment selected to deploy to

Click DEPLOY to deploy the application to the Test environment:

A screenshot of the DEPLOY button

The deployment is then performed directly in the Test environment, skipping the Dev environment:

A screenshot of the deployment being performed to the Test environment

Opening http://minikubeip:30001 displays the copy of the Random Quotes application deployed to the Test environment. Note the port 30001 is the value of the K8S Node Port variable scoped to the Test environment.

Also see the footer text that says running in Test. This is an example of a single Docker image being customized for the environment it is being deployed into:

Approvals

It's a common business requirement to have testers or product owners manually verify that a particular build meets the requirements before a deployment can be considered successful.

Octopus supports this workflow through the use of manual intervention steps. We'll add a manual intervention step to the end of the deployment process, which requires a responsible party to verify the build meets the requirements.

Open the Random Quotes project, click the Process link, and click the ADD STEP button:

A screenshot of the Random Quotes process page

Search for the Manual Intervention Required step, and add it to the process:

A screenshot of the Manual Intervention Required step

Enter Deployment Sign Off for the Step Name:

A screenshot of the new step name field

Enter the following for the Instructions:

Open the application and confirm it meets all the requirements.
A screenshot of the instructions field

Because every build is automatically deployed to the Dev environment, it doesn't make sense to force someone to manually approve all those deployments. To accommodate this, we do not enable the manual intervention step for deployments to the Dev environment.

Expand the Environments section under the Conditions heading, select the Skip specific environments option, and select the Dev environment.

Click SAVE to save the step:

A screenshot of the step environment conditions and SAVE button

When this application is deployed to the Test or Prod environments, a prompt will be displayed requiring manual sign off. Click ASSIGN TO ME to assign the task to yourself:

A screenshot of the ASSIGN TO ME button

Add a note in the provided text box, and click PROCEED to complete the deployment:

A screenshot of the manual intervention message and PROCEED button

The deployment will then complete successfully:

A screenshot of the deployment process

Email notifications

Octopus has native support for sending email notifications as part of the deployment process. We will add a step to the deployment process to let people know when a release has been deployed to an environment.

To start, we need to configure an SMTP server to send our emails. For this guide, we'll use the free SMTP server provided by Google.

Click the Configuration link:

A screenshot of the Configuration link

Click the SMTP link:

A screenshot of the SMTP link
  • Enter smtp.gmail.com as the SMTP Host.
  • Enter 587 as the SMTP Port.
  • Enable the Use SSL/TLS option.
  • Enter your Gmail address as the From Address.
  • Enter your Gmail address and password in the credentials.

You will enable the Less secure apps option on your Google account for Octopus to send emails via the Google SMTP server.

A screenshot of the SMTP configuration

Open the Random Quotes project, click the Process link, and click ADD STEP:

A screenshot of the ADD STEP button

Search for the Send an Email step, and add it to the process:

A screenshot of the Send an Email step

Enter Random quotes deployment status for the Step Name:

A screenshot of the Step Name field

Enter the email address to receive the notification in the To field:

A screenshot of the email To field

Enter Random quotes deployment status as the Subject:

A screenshot of the email Subject field

Enter the following as the Body:

Deployment to #{Octopus.Environment.Name}
#{each step in Octopus.Step}
StepName: #{step}
Status: #{step.Status.Code}
#{/each}

Here we use the #{Octopus.Environment.Name} variable provided by Octopus to add the name of the environment that was deployed to, and then loop over the status codes in the #{Octopus.Step} variable to return the status of each individual step.

The complete list of system variables can be found in the Octopus documentation.

A screenshot of the email Body field

We want to be notified of the status of this deployment regardless of whether the deployment succeeded or failed. Click the Run Conditions section to expand it.

Select the Always run option, which ensures the notification email is sent even when the deployment or the manual intervention fail:

A screenshot of the Run Condition options

Given every change to the source code will result in a deployment to the Dev environment, we do not want to generate reports for deployments to this environment.

Click the Environments section to expand it. Select the Skip specific environments option, and select the Dev environment to skip it.

This is the last piece of configuration for the step, so click SAVE to save the changes:

A screenshot of the Conditions Environments options and SAVE button

Deploy the project to the Test or Prod environments. When the deployment succeeds, the notification email will be sent:

A screenshot of the sample email

Permissions

One of the strengths of Octopus is that it models environments as first-class entities. This means the security layer can apply rules granting access only to specific environments. We'll take advantage of this ability to model and secure environments to create two users, an internal deployer who can deploy to the Dev and Test environments, and a production deployer who can deploy to the Prod environment.

We start by creating the users. Click the Configuration link:

A screenshot of the Configuration link

Click the Users link:

A screenshot of the Users link

Click ADD USER:

A screenshot of the ADD USER button

Enter internaldeployer as the Username:

A screenshot of the Username field

Enter Internal Deployer as the Display Name:

A screenshot of the Display Name field

Enter the user's email address. We have used a dummy address of internaldeployer@example.org here:

A screenshot of the Email Address field

Enter a password and confirm it. Then click SAVE to create the user:

A screenshot of the password field and SAVE button

Repeat the process for a user called productiondeployer. The summary for the productiondeployer user is shown below:

A screenshot of the user's details

The newly created users are not assigned to any teams and have no permissions to do anything. To grant them permissions, we must first create two teams. The internal deployment team will grant access to deploy to the Dev and Test environments, while the production deployment team will grant access to deploy to the Prod environment.

Click the Configuration link:

A screenshot of the Configuration link

Click the Teams link:

A screenshot of the Teams link

Click ADD TEAM:

A screenshot of the ADD TEAM button

Enter Internal Deployers for the New team name, and Grants access to perform a deployment to the internal environments for the Team description. Click SAVE to create the team:

A screenshot of the Add New Team dialog

We need to add the internaldeployer user to this team. Click ADD MEMBER:

A screenshot of the ADD MEMBER button

Select the Internal Deployer user from the dropdown list, and click ADD:

A screenshot of the Add Member dialog

The team does not grant any permissions yet. To add permissions click the USER ROLES tab, and click INCLUDE USER ROLE:

A screenshot of the INCLUDE USER ROLE button

Select the Deployment creator role from the dropdown list. As the name suggests, this role allows a user to create a deployment, which results in the deployment process being executed.

Click DEFINE SCOPE:

A screenshot of the Include User Role dialog

We only want to allow the internal deployer to deploy to the internal environments. Select the Dev and Test environments, and click APPLY:

A screenshot of the APPLY button

The permissions are then applied. We need a second permission to allow the internal deployer to view the projects dashboard. Click INCLUDE USER ROLE again:

A screenshot of the INCLUDE USER ROLE button

Select the Project viewer role. This role does not need to be scoped, so click the APPLY button:

A screenshot of the Include User Role dialog

Here are the final set of roles applied to the team:

A screenshot of the team's USER ROLES tab

Repeat the process to create a team called Production Deployers that includes the productiondeployer user, and grants the Deployment creator role scoped to the Prod environment:

A screenshot of the team's USER ROLES tab

When we log in as the internaldeployer user, we see that the Random Quotes project dashboard shows DEPLOY... buttons for the Dev and Test environments. Any deployments in the production environment will be visible, but they cannot be created by this user:

A screenshot of the Projects page

When we log in as the productiondeployer user, we see that the Random Quotes project dashboard shows DEPLOY... buttons only for the Prod environment. Also note that the lifecycle rules still apply, and only successful deployments to the Test environment are available to be promoted to the Prod environment:

A screenshot of the Projects page

Audit Log

Important interactions in Octopus are tracked in the audit log. This is useful for teams that have security or legislative requirements to track the changes and deployments made to their infrastructure.

To view the audit log, click the Configuration link:

A screenshot of the Configuration link

Click the Audit link:

A screenshot of the Audit link

A complete list of records are shown, with filtering available to help find specific events:

A screenshot of the Audit page

Conclusion

In this guide we ran through the process of building a complete CI/CD pipeline with:

  • GitHub Actions building and testing the source code and pushing the package to Octopus.
  • Octopus deploying the package to the Dev, Test, and Prod environments.
  • Email notifications generated when deployments succeed or fail.
  • Manual sign off for deployments to the Test and Prod environments.
  • Users with limited access to create releases in a subset of environments.

This is a solid starting point for any development team, but Octopus offers so much more! Below are more resources you can access to learn about the advanced functionality provided by Octopus:

Need support? We're here to help.