Skip to main content

2 posts tagged with "docker"

View All Tags

Running a devcontainer in an Azure App Service

· 4 min read

I love Visual Studio Code devcontainers. I was curious if you could use an App Service with a custom container as a remote devcontainer.

What follows are instructions on how to get a remote devcontainer running in an App Service over SSH.


While this does appear to work, it seems a little fragile. The connection times out sporadically and I haven't used it to do development in earnest yet, so caveat emptor.

Creating an app service compatible devcontainer image#

In order to connect to your app service devcontainer via, you'll need to configure it's sshd as documented in the Configure a custom container for Azure App Service guide from Microsoft.

Port 2222ListenAddress 180X11Forwarding yesCiphers aes128-cbc,3des-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctrMACs hmac-sha1,hmac-sha1-96StrictModes yesSyslogFacility DAEMONPasswordAuthentication yesPermitEmptyPasswords noPermitRootLogin yesSubsystem sftp internal-sftp

In addition to the devcontainer setup, you'll need to add add some additional configuration to your image for it to work in an app service. Specifically you'll need to:

  • Add your sshd_config
  • Set the root password to Docker! Yes, this does feel wrong but it's inaccessible unless you use an Azure AD authenticated tunnel.
  • Add a webserver for the app service to host, in this case I'm using nginx
# use the default microsoft dotnet 5 devcontainerFROM
# add required packages including openssh-server and nginxRUN apt-get update \    && apt-get -y install --no-install-recommends apt-transport-https curl ca-certificates lsb-release gnupg2 openssh-server nginx
RUN echo "root:Docker!" | chpasswd
# Copy the sshd_config file to the /etc/ssh/ directoryCOPY sshd_config /etc/ssh/
# Open port 2222 for SSH accessEXPOSE 80 2222
# Start up nginx and ssh and then sleep to keep the container aliveCMD /usr/sbin/service nginx start && /usr/sbin/service ssh start && /usr/bin/sleep infinity

Connecting to your devcontainer#

First, you'll need to create a tunnel to your app service devcontainer using the following command. Include the -p option to select a fixed port as this will make it easier to connect in future.

az webapp create-remote-connection --resource-group RESOURCE_GROUP_NAME -n APP_SERVICE_NAME -p 61000

Visual Studio requires that you use a key based authentication. Copy your ssh public key to your devcontainer using the following:

# if you're on windows, use git bash for thisssh-copy-id -i ~/.ssh/ -p 61000 root@localhost

To be able to pull from your git repository over ssh, you'll want to use an ssh-agent and ssh-agent forwarding. On windows, you can start your ssh-agent with the following command in a PowerShell session running as Administrator


Install the Remote - SSH Visual Studio Code Extension.

Once it's installed, click the Remote Explorer icon on the left side of your Visual Studio Code window and click the + button to add a new SSH connection.

Type in ssh -A root@localhost -p 61000

Then right click on the new localhost target in the list and click Connect to Host in New Window

Once you're connected, open up a terminal window and run:

mkdir /workspacescd /workspacesgit clone SSH_URL_FOR_YOUR_REPOSITORY

Then click Open Folder on the left and selected /workspaces/NAME_OF_YOUR_REPO and you should be good to go.

To see a working example of the setup, have a look at

How much will this cost?#

I've picked a relatively snappy P1v3 which has 2 cores and 8GB of RAM. Assuming a working year of approximately 48 weeks, working for 5 days a week with the machine running for half the day, a devcontainer running in an app service would cost £30.95 per developer.


My app service has restarted and I can't connect#

If your app service restarts, you'll need to copy your public ssh key again. Also, you'll have an old entry in your known_hosts file. If you try and ssh into your machine you'll see this delightful message:

$ ssh root@localhost -p 61000@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!Someone could be eavesdropping on you right now (man-in-the-middle attack)!It is also possible that a host key has just been changed.

Simply delete the offending line from your known hosts file. You could also switch off host key checking using StrictHostKeyChecking no and UserKnownHostsFile /dev/null in your ssh config file but you'd potentially open yourself up to man-in-the-middle attacks.

I'm having trouble getting SSH and Visual Studio Code working together#

Have a look here:

Building an Azure App Service with a custom container using Bicep

· 3 min read

Azure App Service with custom containers is a convenient way to host docker containers in Azure. While there are a number of tutorials on how to do this with az cli there aren't too many that show how to do it with ARM templates or Azure Bicep. Read on to see how to set up an Azure Container Registry, create scope maps and scope mapped tokens, build and push a custom image and finally use that custom image in your Azure App Service.

First, a word on scope maps and tokens#

Azure Container Registry has a number of authentication mechanisms for pulling docker images. The only one that lets you limit access to specific docker images are scoped mapped tokens which is why I'm going to use them in this example.


Scope Maps and Scope Map Tokens are currently in preview, so use with caution in production.

Setting up your ACR#

You'll need a Container Registry to host your custom container. The following bicep sets up a Premium Azure Container Registry. The reason I'm using a Premium SKU is so that I can create scoped maps and scope mapped tokens.

param location string = resourceGroup().location
resource acr 'Microsoft.ContainerRegistry/registries@2019-12-01-preview' = {  name: acrName  location: location  sku: {    name: 'Premium' // to support scope mapped tokens  }  properties: {    adminUserEnabled: false  }}

You may also want to assign an owner to your ACR

param ownerPrincipalId string
resource ownerRoleAssignment 'Microsoft.Authorization/roleAssignments@2018-01-01-preview' = {  name: guid('${}/${ownerPrincipalId}/owner')  scope: acr  properties: {    roleDefinitionId: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635'    principalId: ownerPrincipalId  }}

And set up your scope maps and scope mapped tokens

// create a scope map for your repositoryresource bicepAppServiceContainerScopeMap 'Microsoft.ContainerRegistry/registries/scopeMaps@2020-11-01-preview' = {  parent: acr  name: 'bicepAppServiceContainer'  properties: {    actions: [      'repositories/bicep-app-service-container/content/read'      'repositories/bicep-app-service-container/metadata/read'    ]  }}
// create a token and associate it with your scope mapresource bicepAppServiceContainerToken 'Microsoft.ContainerRegistry/registries/tokens@2020-11-01-preview' = {  parent: acr  name: 'bicepAppServiceContainer'  properties: {    scopeMapId:    status: 'enabled'  }}

Generating passwords for your tokens#

The bicep above will create the scope map tokens but will not generate the passwords. You'll need az cli for that.

ACR_PULL_TOKEN=$(az acr token credential generate -n bicepAppServiceContainer -r $ACR_NAME --expiration-in-days 30 --query passwords[0].value -o tsv)

This will generate the passwords and set ACR_PULL_TOKEN to be the value of one of the generated passwords.

Setting up your App Service with a Custom Container#

Use the bicep below to create a linux App Service plan and an App Service that uses your custom container.

@description('The name of the app service that you wish to create.')param siteName string
@description('The docker registry hostname.')param dockerRegistryHost string
@description('The docker registry username.')param dockerUsername string;
@secure()@description('The docker registry password.')param dockerPassword string
@description('The relative docker image name.')param dockerImage string
var servicePlanName = 'plan-${siteName}-001'
resource servicePlan 'Microsoft.Web/serverfarms@2016-09-01' = {  kind: 'linux'  name: servicePlanName  location: resourceGroup().location  properties: {    name: servicePlanName    reserved: true  }  sku: {    tier: 'Standard'    name: 'S1'  }  dependsOn: []}
resource siteName_resource 'Microsoft.Web/sites@2016-08-01' = {  name: siteName  location: resourceGroup().location  properties: {    siteConfig: {      appSettings: [        {          name: 'WEBSITES_ENABLE_APP_SERVICE_STORAGE'          value: 'false'        }        {          name: 'DOCKER_REGISTRY_SERVER_URL'          value: 'https://${dockerRegistryHost}'        }        {          name: 'DOCKER_REGISTRY_SERVER_USERNAME'          value: dockerUsername        }        {          name: 'DOCKER_REGISTRY_SERVER_PASSWORD'          value: dockerPassword        }      ]      linuxFxVersion: 'DOCKER|${dockerRegistryHost}/${dockerImage}'    }    serverFarmId:  }}

To see a working example, have a look at the following repository