Setting up WordPress to serve next-gen WebP images

Why do this?

If you are looking to improve performance on your website, this will automatically convert the images on your site to the more efficient WebP format. This will help with Google Lighthouse scores (especially in solving the “Serve images in next-gen format” issue).


Install the WebP Express Plugin (and buy the developer a coffee!)

Go into the WebP Express settings, accept the defaults, and click ‘Save settings’.

Verify this is working by running Google’s Lighthouse analytics. You should not see the item ‘Serve images in next-gen formats” item in your listing.

Installing Dependencies

Depending on the server configuration you have, you may need to do more work after installing the WebP Express Plugin. If you see the following, you’ll need to configure your server to allow access:

SSH into the server and install the gd using the following:

sudo apt-get install php7.2-gd

In addition, you may need to install mod_headers:

sudo a2enmod headers
sudo systemctl restart apache2

Setting up Jenkins in Azure

Before getting started, you’ll need to have:

  • An Azure tenant and subscription.
  • OpenSSH (installation for Windows 10)

Installing Jenkins via Azure Marketplace

The easiest way to install Jenkins is to use the Azure Marketplace link. A couple suggestions when setting up:

  • I recommend using an SSH Public Key to sign in. If you haven’t yet, generate one using ssh-keygen and then get it using cat ~.ssh\
  • Set up a domain name label, especially if you aren’t planning to put this behind a different domain.
  • Set the VM as B1ms starting off – you can upgrade later as the system is used more.

After creation, modify the NSG created and use your public IP to secure SSH access (check your public IP).

Next, SSH into the server using the IP and check to see if you can update the OS (as of this writing, the image ships with Ubuntu 16.04 LTS, and can be upgraded to 18.04 LTS).

Connecting a Domain

If you’re planning to use a different domain to host Jenkins (as opposed to the provided, set the following DNS record:

  • Host: desired subdomain (ex. jenkins ->
  • Value: DNS record from Azure.

Since DNS will take a second, check to verify you can access the new server.

Setting up SSL using Let’s Encrypt

The next step is setting up SSL using Let’s Encrypt to allow for an HTTPS connection. First, open the 443 port on the VM:

az network nsg rule update -g RG_NAME --nsg-name NSG_NAME -n http-rule --destination-port-ranges 80, 443

Now SSH into the server and modify SSL Offloading:

sudo nano /etc/nginx/sites-available/default

Use the following configuration:

server {
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;

server {
    listen 443 ssl;
    server_name CUSTOMDOMAIN;
    ssl_certificate /etc/letsencrypt/live/CUSTOMDOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/CUSTOMDOMAIN/privkey.pem;
    location / {
        proxy_set_header        Host $host:$server_port;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;

        # Fix the “It appears that your reverse proxy set up is broken" error.
        proxy_pass          http://localhost:8080;
        proxy_read_timeout  90;

Then run the following commands:

sudo service nginx stop
git clone
./letsencrypt/letsencrypt-auto certonly
sudo service nginx restart

Accessing and Logging Into Jenkins

After completed, access the Jenkins instance at your domain. Verify that both the SSL connection is valid and that you are on the ‘Unlock Jenkins’ page:

Run the following command in the SSHed server to get a code for the screen:

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

Next, you’ll get a request to either install suggested plugins or select plugins as desired. I recommend going through and selecting the plugins desired to keep the installation minimal. Remove anything from the list that you may not need (such as Subversion). You can always add plugins later if you find you need.

After that, create an admin user for yourself, and you’ll be ready to get started!

Next Steps

After you’ve finished setting up Jenkins, a few next steps would be:


Creating a Function App With a Full CI/CD Pipeline with VSCode and Jenkins

Before starting this, you’ll need to have a few things on your machine:

  • Azure Functions VSCode Extension
  • Azure Functions Core Tools (choco install azure-functions-core-tools)
  • .NET Core Build Tools (choco install visualstudio2017-workload-netcorebuildtools)
  • An Azure subscription
  • A Function app created inside of the Azure subscription
  • A Jenkins server with the following set up:
    • Azure Function plugin installed
    • A service principal configured (use az ad sp create-for-rbac -n "jenkins" --role contributor --scopes /subscriptions/{SubID} and then add to Jenkins Credentials)

Creating and Locally Running the Function App

Inside VSCode, create a function app project with the following:

  • Create a new folder for use
  • Language: C#
  • Template: HttpTrigger
  • Security: Anonymous

After creating, you’ll need to resolve a few dependencies, which VSCode should prompt for.

I ran into issues with getting the function app debugging locally, but I was able to run it without issue by running the following in PowerShell

dotnet clean
dotnet build
func host start

Once you get it running, you can use the URL provided to test:

Locally Running a TimerTrigger Function

To use a TimerTrigger functions, there are just a few changes:

  • You will need to have a Storage Account available to allow for running locally.
  • When running, you can invoke the function by making a POST to http://localhost:7071/admin/functions/{FUNCTION_NAME} (make sure to provide an empty JSON body in the payload)

Adding to Git and Deploying via Jenkins

The next step is checking in the example code to Git, so you have a place to get the codebase from for deployment.

After checking in the codebase, create a Multibranch Pipeline project in Jenkins.

Use the following Jenkinsfile as a reference:

pipeline {
  agent any
  stages {
    stage('Build') {
      steps {
        sh 'dotnet clean'
        sh 'dotnet build'
    stage('Deploy to Function App') {
      when { branch 'master' }
      steps {
        azureFunctionAppPublish appName: "fa-poc-123",
          azureCredentialsId: 'jenkins-sp',
          resourceGroup: "fa-poc-ue-rg",
          sourceDirectory: '',
          targetDirectory: '',
          filePath: ''

After that, check in your codebase and you should have a deployed Function App with the codebase provided. Verify it using the Function App URL provided.

Copying a Database in Azure with Always Encrypted Data

When trying to copy a database with Always Encrypted data (say, to a different environment), you’ll generally want to recycle the Column Master Key used to match the vault stored in the same Azure resource group. This takes a little bit of work to do:


You’ll need to have the following software installed:

  • SSMS
  • Azure CLI

You’ll also need to make sure the database you’re copying from has a key that already exists. Run the following query on your newly copied database:

select * from sys.column_master_keys

And then check to see if the key exists in the appropriate vault:

az keyvault key show --id KEY_PATH

If it exists, you’ll be able to copy the database over without issue.


Create a new key in the Key Vault:

az keyvault key create --name Always-Encrypted-Auto1 --vault-name VAULT_NAME

Next, create a new Column Master Key, using the created key above.

With two CMKs, rotate the initial CMK (using credentials from the source key vault, and then the destination key vault).

Next, clean up the previous CMK:

After this is done, you can delete the old CMK.

Setting up HTTPS on an AKS Cluster


The following is required:

  • An ingress controller should already be installed.
  • The public IP of the Ingress controller should have a DNS name.
  • Helm needs to be running at 2.13.1.
  • The Kubernetes cluster should be publicly accessible (to allow cert creation)

To check if Helm is running with version 2.13.1 (there is a bug that doesn’t allow 2.14+ to work). To check, run the following:

helm version

Client: &version.Version{SemVer:"v2.13.1", GitCommit:"618447cbf203d147601b4b9bd7f8c37a5d39fbb4", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.13.1", GitCommit:"618447cbf203d147601b4b9bd7f8c37a5d39fbb4", GitTreeState:"clean"}

If the output doesn’t match as below, you need to downgrade Helm. Install:

# only needed if Client above is not 2.13.1
choco uninstall kubernetes-helm
choco install kubernetes-helm --version 2.13.1

helm init --upgrade --force-upgrade


Run the following commands:

# Install the CustomResourceDefinition resources separately
kubectl apply -f

# Create the namespace for cert-manager
kubectl create namespace cert-manager

# Label the cert-manager namespace to disable resource validation
kubectl label namespace cert-manager

# Add the Jetstack Helm repository
helm repo add jetstack

# Update your local Helm chart repository cache
helm repo update

# Install the cert-manager Helm chart
helm install --name cert-manager --namespace cert-manager --version v0.8.0 jetstack/cert-manager

Create the following YAML file `cluster-issuer.yml’:

kind: ClusterIssuer
  name: letsencrypt-staging
  namespace: ingress-nginx
      name: letsencrypt-staging
    http01: {}

Apply the changes to the cluster:

kubectl apply -f ../shared/cluster-issuer.yml

Create the certificate.yml file:

kind: Certificate
  name: tls-secret
  namespace: ingress-nginx
  secretName: tls-secret-staging
    - http01:
        ingressClass: nginx
    name: letsencrypt-staging
    kind: ClusterIssuer

Apply those changes:

kubectl apply -f certificate.yml


Confirm creation of the certificate:

kubectl describe certificate tls-secret --namespace ingress-nginx

You should see the following:

Normal   OrderCreated        27s                cert-manager  Created Order resource "tls-secret-3300974441"
Normal   CertIssued          3s (x2 over 20m)   cert-manager  Certificate issued successfully
Normal   OrderComplete       3s                 cert-manager  Order "tls-secret-3300974441" completed successfully

Verify HTTPS can be accessed.

Applying IP Restrictions to a Large Set of Azure Resources

To do this, use PowerShell and Azure CLI to collect all of the NSGs and get all of the NSGs in the subscription:

az account set -s <SUB_ID>
$nsgs = az network nsg list | ConvertFrom-Json

Then go through every NSG and create the rule:

$nsgs | ForEach-Object -Process { az network nsg rule create --name NAME --nsg-name $ --priority PRIORITY --resource-group $_.resourceGroup --protocol Tcp --destination-port-ranges 80 443 --source-address-prefixes IP_ADDRESSES }

Next, get a list of the App Services:

$webapps = az webapp list | ConvertFrom-Json

And go through and add the list of IPs required (must use individual IPs):

$webapps | ForEach-Object -Process { $WebAppConfig = Get-AzResource -ResourceName $ -ResourceType Microsoft.Web/sites/config -ResourceGroupName $_.resourceGroup -ApiVersion 2018-11-01; $WebAppConfig.Properties.ipSecurityRestrictions = @([PSCustomObject] @{ ipAddress = '' },@{ ipAddress = '' });  Set-AzResource -ResourceId $WebAppConfig.ResourceId -Properties $WebAppConfig.Properties -ApiVersion 2018-11-01 }

Removing Access

To delete the same list of rules from the NSGs, use the same name:

$nsgs | ForEach-Object -Process { az network nsg rule delete -g resourceGroup --nsg-name $ -n NAME }


Creating a Buy One Get One Half Off Discount In NopCommerce

I recently worked with a client on trying to set up a Buy One Get One Half Off deal on NopCommerce – here’s what I did to get it working:


Before starting, you should have the following:


Go to the Discounts page and create a new Discount with the following information:

Next, go to the ‘Restrictions’ tab and add a restriction for the Product, using the ‘Add Product’ functionality Make sure you add the :2 to the end to force purchase of two:

Deploying web.config with an Angular Project

When deploying an Angular project out to Azure, you’ll need to include a web.config file to allow for things such as the following:

  • Getting routing to work.
  • Serving static content.

First, create a web.config file in src/. Here’s an example of what it might look like:

<?xml version="1.0"?>
                <rule name="Angular Routing" stopProcessing="true">
                <match url=".*" />
                <conditions logicalGrouping="MatchAll">
                    <add input="{REQUEST_FILENAME}" matchType="IsFile"
                         negate="true" />
                    <add input="{REQUEST_FILENAME}" matchType="IsDirectory"
                         negate="true" />
                <action type="Rewrite" url="/" />
            <mimeMap fileExtension="woff" mimeType="application/font-woff" />
            <mimeMap fileExtension="json" mimeType="application/json" />

After this is done, make a change to angular.json to bundle the web.config file in the build:

"assets": [

Now let’s verify by running ng build --prod:

Adding Bootstrap To An Angular CLI Project


Before setting this up, you’ll need to have an Angular project to add Bootstrap to. If you’re starting fresh, you can create one easily with ng new <name>.


First, install bootstrap from npm:

npm install bootstrap

Then add the following to the top of your styles.css file:

@import '~bootstrap/dist/css/bootstrap.min.css';

That’s it! Bootstrap is now enabled for your Angular CLI application.