How To Chat with a Local AI Model using Ollama and .NET on MacOS

The .NET team publish Docker images for every release of the .NET SDK and runtime. Running .NET in containers is a great way to experiment with a new release or try out an upgrade of an existing project, without deploying any new runtimes onto your machine.
In case you missed it, .NET 5 is the latest version of .NET and it's the end of the ".NET Core" and ".NET Framework" names. .NET Framework ends with 4.8 which is the last supported version. and .NET Core ends with 3.1 - and evolves into straight ".NET". The first release is .NET 5 and the next version - .NET 6 - will be a long-term support release.
If you're new to the SDK/runtime distinction, check my blog post on the .NET Docker images for Windows and Linux.
You can use the .NET 5.0 SDK image to run a container with all the build and dev tools installed. These are official Microsoft images, published to MCR (the Microsoft Container Registry).
Create a local folder for the source code and mount it inside a container:
mkdir -p /tmp/dotnet-5-docker
docker run -it --rm \
-p 5000:5000 \
-v /tmp/dotnet-5-docker:/src \
mcr.microsoft.com/dotnet/sdk:5.0
All you need to run this command is Docker Desktop on Windows or macOS, or Docker Community Edition on Linux.
Docker will pull the .NET 5.0 SDK image the first time you use it, and start running a container. If you're new to Docker this is what the options mean:
-it
connects you to an interactive session inside the container-p
publishes a network port, so you can send traffic into the container from your machine--rm
deletes the container and its storage when you exit the session-v
mounts a local folder from your machine into the container filesystem - when you use /src
inside the container it's actually using the /tmp/dotnet-5-docker
folder on your machinemcr.microsoft.com/dotnet/sdk:5.0
is the full image name for the 5.0 release of the SDKAnd this is how it looks:
When the container starts you'll drop into a shell session inside the container, which has the .NET 5.0 runtime and developer tools installed. Now you can start playing with .NET 5, using the Docker container to run commands but working with the source code on your local machine.
In the container session, run this to check the version of the SDK:
dotnet --list-sdks
The dotnet new
command creates a new project from a template. There are plenty of templates to choose from, we'll start with a nice simple REST service, using ASP.NET WebAPI.
Initialize and run a new project:
# create a WebAPI project without HTTPS or Swagger:
dotnet new webapi \
-o /src/api \
--no-openapi --no-https
# configure ASP.NET to listen on port 5000:
export ASPNETCORE_URLS=http://+:5000
# run the new project:
dotnet run \
--no-launch-profile \
--project /src/api/api.csproj
When you run this you'll see lots of output from the build process - NuGet packages being restored and the C# project being compiled. The output ends with the ASP.NET runtime showing the address where it's listening for requests.
Now your .NET 5 app is running inside Docker, and because the container has a published port to the host machine, you can browse to http://localhost:5000/weatherforecast on your machine. Docker sends the request into the container, and the ASP.NET app processes it and sends the response.
What you have now isn't fit to ship and run in another environment, but it's easy to get there by building your own Docker image to package your app.
I cover the path to production in my Udemy course Docker for .NET Apps
To ship your app you can use this .NET 5 sample Dockerfile to package it up. You'll do this from your host machine, so you can stop the .NET app in the container with Ctrl-C
and then run exit
to get back to your command line.
Use Docker to publish and package your WebAPI app:
# verify the source code is on your machine:
ls /tmp/dotnet-5-docker/api
# switch to your local source code folder:
cd /tmp/dotnet-5-docker
# download the sample Dockerfile:
curl -o Dockerfile https://raw.githubusercontent.com/sixeyed/blog/master/dotnet-5-with-docker/Dockerfile
# use Docker to package from source code:
docker build -t dotnet-api:5.0 .
Now you have your own Docker image, with your .NET 5 app packaged and ready to run. You can edit the code on your local machine and repeat the docker build
command to package a new version.
The SDK container you ran is gone, but now you have an application image so you can run your app without any additional setup. Your image is configured with the ASP.NET runtime and when you start a container from the image it will run your app.
Start a new container listening on a different port:
# run a container from your .NET 5 API image:
docker run -d -p 8010:80 --name api dotnet-api:5.0
# check the container logs:
docker logs api
In the logs you'll see the usual ASP.NET startup log entries, telling you the app is listening on port 80. That's port 80 inside the container though, which is published to port 8010 on the host.
The container is running in the bckground, waiting for traffic. You can try your app again, running this on the host:
curl http://localhost:8010/weatherforecast
When you're done fetching fictional weather forecasts, you can stop and remove your container with a single command:
docker rm -f api
And if you're done experimenting, you can remove your image and the .NET 5 images:
docker image rm dotnet-api:5.0
docker image rm mcr.microsoft.com/dotnet/sdk:5.0
docker image rm mcr.microsoft.com/dotnet/aspnet:5.0
Now your machine is back to the exact same state before you tried .NET 5.
You can do exactly the same thing for .NET 6, just changing the version number in the image tags. .NET 6 is in preview right now but the 6.0
tag is a moving target which gets updated with each new release (check the .NET SDK repository and the ASP.NET runtime repository on Docker Hub for the full version names).
To try .NET 6 you're going to run this for your dev environment:
mkdir -p /tmp/dotnet-6-docker
docker run -it --rm \
-p 5000:5000 \
-v /tmp/dotnet-6-docker:/src \
mcr.microsoft.com/dotnet/sdk:6.0
Then you can repeat the steps to create a new .NET 6 app and run it inside a container.
And in your Dockerfile you'll use the mcr.microsoft.com/dotnet/sdk:6.0
image for the builder stage and the mcr.microsoft.com/dotnet/aspnet:6.0
image for the final application image.
It's a nice workflow to try out a new major or minor version of .NET with no dependencies (other than Docker). You can even put your docker build
command into a GitHub workflow and build and package your app from your cource code repo - check my YouTube show Continuous Deployment with Docker and GitHub for more information on that.
To run .NET apps in containers you need to have the .NET Framework or .NET Core runtime installed in the container image. That's not something you need to manage yourself, because Microsoft provide Docker images with the runtimes already installed, and you'll use those as the base image to package your own apps.
There are several variations of .NET images, covering different versions and different runtimes. This is your guide to picking the right image for your applications.
I cover this in plenty of detail in my Udemy course Docker for .NET Apps
Your app has a bunch of pre-requisites it needs to run, things like an operating system and the language runtime. Typically the owners of the platform package an image with all the pre-reqs installed and publish it on Docker Hub - you'll see Go, Node.js, Java etc. all as official images.
Microsoft do the same for .NET apps, so you can use one of their images as the base image for your container images. They're regularly updated so you can patch your images just by rebuilding them using the latest Microsoft image.
The Docker images for .NET apps are hosted on Microsoft's own container registry, mcr.microsoft.com, but they're still listed on Docker Hub, so that's where you'll go to find them:
Those are umbrella pages which list lots of different variants of the .NET images, splitting them between SDK images and runtime images.
You can package .NET apps using a runtime image with a simple Dockerfile like this:
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8
SHELL ["powershell"]
COPY app.msi /
RUN Start-Process msiexec.exe -ArgumentList '/i', 'C:\app.msi', '/quiet', '/norestart' -NoNewWindow -Wait
(see the full ASP.NET 4.8 app Dockerfile on GitHub).
That's an easy way to get into Docker, taking an existing deployment package (an MSI installer in this case) and installing it using a PowerShell command running in the container.
This example uses the ASP.NET 4.8 base image, so the image you build from this Dockerfile:
It's a simple approach but its problematic because the Dockerfile is the packaging format and it should have all the details about the deployment, but all the installation steps are hidden in the MSI - which is a redundant additional artifact.
Instead you can compile the app from source code using Docker, which is where the SDK images come in. Those SDK images have all the build tools for your apps: MSBuild and NuGet or the dotnet
CLI. You use them in a multi-stage Docker build, where stage 1 compiles from source and stage 2 packages the compiled build from stage 1:
# the build stage uses the SDK image:
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 as builder
COPY src /src
RUN dotnet publish -c Release -o /out app.csproj
# the final app uses the runtime image:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
COPY --from=builder /out/ .
ENTRYPOINT ["dotnet", "app.dll"]
(see the full ASP.NET Core app Dockerfile on GitHub).
This approach is much better because:
I show you how to use GitHub actions with multi-stage Docker builds in my YouTube show ECS-C2: Continuous Deployment with Docker and GitHub.
There are still lots of variants of the .NET Docker images, so the next job is to work out which ones to use for different apps.
.NET Framework apps are the simplest because they only run on Windows, and they need the full Windows Server Core feature set (you can't run .NET fx apps on the minimal Nano Server OS). You'll use these for any .NET Framework apps you want to containerize - you can run them using Windows containers in Docker, Docker Swarm and Kubernetes.
All the current .NET Framework Docker images use mcr.microsoft.com/windows/servercore:lts2019
as the base image - that's the latest long-term support version of Windows Server Core 2019. Then the .NET images extend from the base Windows image in a hierarchy:
The Docker image names are shortened in that graphic, they're all hosted on MCR so they all need to be prefixed with mcr.microsoft.com/
. The tag for each is the latest release, so that's a moving target - the :ltsc2019
Windows image is updated every month with new OS patches, so if you use that in your FROM
instruction you'll always get the current release.
Microsoft also publish images with more specific tags, so you can pin to a particular release and you know that image won't change in the future. The .NET 4.8 SDK image was updated last year to include .NET 5 updates, and that broke some builds - so you could use mcr.microsoft.com/dotnet/framework/sdk:4.8-20201013-windowsservercore-ltsc2019
in your builder stage, which is pinned to the version before the change.
Here's how you'll choose between the images:
windows/servercore:lts2019
comes with .NET 4.7, so you can use it for .NET Console apps, but not ASP.NET or .NET Core apps;dotnet/framework/runtime:4.8
has the last supported version of .NET Framework which you can use to run containerized console apps;dotnet/framework/sdk:4.8
has MSBuild, NuGet and all the targeting packs installed, so you should be able to build pretty much any .NET Framework app - you'll use this in the builder stage only;dotnet/framework/aspnet:4.8
has ASP.NET 4.8 installed and configured with IIS, so you can use it for any web apps - WebForms, MVC, Web API etc.There's also dotnet/framework/wcf:4.8
for running WCF apps. All the Dockerfiles for those images are on GitHub at microsoft/dotnet-framework-docker in the src
folder, and there are also a whole bunch of .NET Framework Docker sample apps.
Those images have the 4.x runtime installed, so they can run most .NET Framework apps - everything from 1.x to 4.x but not 3.5. The 3.5 runtime adds another gigabyte or so and it's only needed for some apps, so they have their own set of images:
dotnet/framework/runtime:3.5
dotnet/framework/sdk:3.5
dotnet/framework/aspnet:3.5
.NET Core gets a bit more complicated, because it's a cross-platform framework with different images available for Windows and Linux containers. You'll use the Linux variants as a preference because they're leaner and you don't need to pay OS licences for the host machine.
If you're not sure on the difference with Docker on Windows and Linux, check out ECS-W1: We Need to Talk About Windows Containers on YouTube or enrol on Docker for .NET Apps on Udemy.
The Linux variants are derived from Debian, and they use a similar hierarchical build approach and have the same naming standards as the .NET Framework images:
Again those image names need to be prefixed with mcr.microsoft.com/
, and the tags are for the latest LTS release so they're moving targets - right now aspnet:3.1
is an alias for aspnet:3.1.11
, but next month the same 3.1
tag will be used for an updated release.
dotnet/core/runtime:3.1
has the .NET Core runtime, so you can use it for console apps;dotnet/core/sdk:3.1
has the SDK installed so you'll use it in the builder stage to compile .NET Core apps;dotnet/core/aspnet:3.1
has ASP.NET Core 3.1 installed, so you can use it to run web apps (they're still console apps in .NET Core, but the web runtime has extra dependencies)..NET Core 3.1 will be supported until December 2022; 2.1 is also an LTS release with support until August 2021, and there are images available for the 2.1 runtime using the same image names and the tag :2.1
. You'll find all the Dockerfiles and some sample apps on GitHub in the dotnet/dotnet-docker repo.
There are also Alpine Linux variants of the .NET Core images, which are smaller and leaner still. If you're building images to run on Linux and you're not interested in cross-platform running on Windows, these are preferable - but some dependencies don't work correctly in Alpine (Sqlite is one), so you'll need to test your apps:
dotnet/core/runtime:3.1-alpine
dotnet/core/sdk:3.1-alpine
dotnet/core/aspnet:3.1-alpine
If you do want to build images for Linux and Windows from the same source code and the same Dockerfiles, stick with the generic :3.1
tags - these are multi-architecture images, so there are versions published for Linux, Windows, Intel and Arm 64.
The Windows variants are all based on Nano Server:
Note that they have the same image names - with multi-architecture images Docker will pull the correct version to match the OS and CPU you're using. You can check all the available variants by looking at the manifest (you need experimental features enabled in the Docker CLI for this):
docker manifest inspect mcr.microsoft.com/dotnet/core/runtime:3.1
You'll see a chunk of JSON in the response, which includes details of all the variants - here's a trimmed version:
"manifests": [
{
"digest": "sha256:6c67be...",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"digest": "sha256:d50e61...",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
}
},
{
"digest": "sha256:3eb5f6...",
"platform": {
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.17763.1697"
}
},
{
"digest": "sha256:4d53d2d...",
"platform": {
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.18363.1316"
}
}
]
You can see in there that the single image tag dotnet/core/runtime:3.1
has image variants available for Linux on Intel, Linux on Arm and multiple versions of Windows on Intel. As long as you keep your Dockerfiles generic - and don't include OS-specific commands in RUN
instructions - you can build your own multi-arch .NET Core apps based on Microsoft's images.
.NET 5 is the new evolution of .NET Core, and there are Docker images for the usual variants on MCR:
dotnet/runtime:5.0
dotnet/sdk:5.0
dotnet/aspnet:5.0
Note that "core" has been dropped from the image name - there's more information in this issue .NET Docker Repo Name Change.
Migrating .NET Core apps to .NET 5 should be a simple change, but remember that 5 is not an LTS version - you'll need to wait for .NET 6, which is LTS (see Microsoft's .NET Core and .NET 5 Support Policy.
This adventure lets you code on your normal dev machine from some other machine, using the browser. It's powered by Docker plus:
And it's very simple. You just run code-server
in a Docker container on your dev machine, mapping volumes for the data you want to be able to access and publishing a port. Then you expose that port to the Internet using ngrok
, make a note of the URL and walk out the door.
code-server
has done all the hard work here. They publish images to codercom/code-server on Docker Hub. There are only x64 Linux images right now.
Run the latest version with:
docker container run \
-d -p 8443:8443 \
-v /scm:/scm \
codercom/code-server:1.621 \
--allow-http --no-auth
That command runs VS Code as a headless server in a background container. The options:
8443
on your local machine into the container/scm
directory into /scm
on the containerYou can run insecure on your home network (if you trust folks who can access your network), because you'll add security with
ngrok
.
Now you can browse to http://localhost:8443 and you have VS Code running in the browser:
That volume mount means all of the code in the scm
folder on my machine is accessible from the VS Code instance. And you can fire up a terminal in VS Code in the browser, which means you can do pretty much anything else you need to do. But remember the terminal is executing inside the container, so the environment is the container.
The code-server
images comes with a few dev tools installed, like Git and OpenSSL. But there are no dev toolkits, so you can't actually compile or run any code... Unless you're using multi-stage Dockerfiles and official images with SDKs installed. Then all you need is Docker.
code-server
doesn't have the Docker CLI installed, but I've added that in my fork. So you can run my version and mount the local Docker socket as a volume, meaning you can use docker
commands inside the browser-based VS Code instance:
docker container run \
-d -p 8443:8443 \
-v /scm:/scm \
-v /var/run/docker.sock:/var/run/docker.sock \
--network code-server \
sixeyed/code-server:1.621 \
--allow-http --no-auth
(I'm also using an explicit Docker network here which I created with docker network create code-server
. You'll see why in a moment).
Now you can refresh your browser at http://localhost:8443, open up a terminal and run all the docker
commands you like (with sudo
). The Docker CLI inside the container is connected to the Docker Engine which is running the container.
Let's try out the .NET Core 3.0 preview. You can run these commands in VS Code on the browser. They all execute inside the container:
git clone https://github.com/sixeyed/whoami-dotnet.git
cd whoami-dotnet
sudo docker image build -t sixeyed/whoami-dotnet:3.0-linux-amd64 .
sudo docker container run -d \
--network code-server --name whoami \
sixeyed/whoami-dotnet:3.0-linux-amd64
Now the whoami
container is running in the same Docker network as the code-server
container, so you can reach it by the container name:
curl http://whoami
And here it is for real:
Now this is a usable development environment. The multi-stage Dockerfile I've built starts with a build stage that uses an image with the .NET Core SDK, so there's no need to install any tools in the dev environment. You can do the same with Java, Go etc. - they all have official build images on Docker Hub.
And the final step is to make it publicly available through ngrok
.
Sign up for an ngrok account, and follow the setup instructions to install the software and apply your credentials. Now you can expose any local port through a public Internet tunnel - just by running something like ngrok http 8443
.
But you can do more with ngrok
. This command sets up a tunnel for my VS Code server with HTTPS and basic authentication:
ngrok http -bind-tls=true -auth="elton:DockerCon" 8443
You'll see output like this, telling you the public URL for your tunnel and some stats about who's using it:
The Forwarding
line tells you the public URL and the local port it's forwarding. Mine is https://112f7fb1.ngrok.io
(you can use custom domains instead of the random ones). That endpoint is HTTPS so it's secure, and it's using basic auth so you'll need the username and password you specified in the ngrok
command:
Now you can access the headless VS Code instance running on your dev machine from anywhere on the Internet. Browser sessions are separate, so you can even have multiple people doing different things on the same remote code server:
ngrok
collects metrics while it's running, and there's an admin portal you can browse to locally - it shows you all the requests and responses the tunnel has handled:
I've only had a quick look, but it seems like this could work on Windows. ngrok
already has Windows support, and it should just mean packaging code-server
with a different Dockerfile.
Sounds like a nice weekend project for someone. Docker on Windows - second edition! will help :)
The .NET team publish Docker images for every release of the .NET SDK and runtime. Running .NET in containers is a great way to experiment with a new release or try out an upgrade of an existing project, without deploying any new runtimes onto your machine.
In case you missed it, .NET 5 is the latest version of .NET and it's the end of the ".NET Core" and ".NET Framework" names. .NET Framework ends with 4.8 which is the last supported version. and .NET Core ends with 3.1 - and evolves into straight ".NET". The first release is .NET 5 and the next version - .NET 6 - will be a long-term support release.
If you're new to the SDK/runtime distinction, check my blog post on the .NET Docker images for Windows and Linux.
You can use the .NET 5.0 SDK image to run a container with all the build and dev tools installed. These are official Microsoft images, published to MCR (the Microsoft Container Registry).
Create a local folder for the source code and mount it inside a container:
mkdir -p /tmp/dotnet-5-docker
docker run -it --rm \
-p 5000:5000 \
-v /tmp/dotnet-5-docker:/src \
mcr.microsoft.com/dotnet/sdk:5.0
All you need to run this command is Docker Desktop on Windows or macOS, or Docker Community Edition on Linux.
Docker will pull the .NET 5.0 SDK image the first time you use it, and start running a container. If you're new to Docker this is what the options mean:
-it
connects you to an interactive session inside the container-p
publishes a network port, so you can send traffic into the container from your machine--rm
deletes the container and its storage when you exit the session-v
mounts a local folder from your machine into the container filesystem - when you use /src
inside the container it's actually using the /tmp/dotnet-5-docker
folder on your machinemcr.microsoft.com/dotnet/sdk:5.0
is the full image name for the 5.0 release of the SDKAnd this is how it looks:
When the container starts you'll drop into a shell session inside the container, which has the .NET 5.0 runtime and developer tools installed. Now you can start playing with .NET 5, using the Docker container to run commands but working with the source code on your local machine.
In the container session, run this to check the version of the SDK:
dotnet --list-sdks
The dotnet new
command creates a new project from a template. There are plenty of templates to choose from, we'll start with a nice simple REST service, using ASP.NET WebAPI.
Initialize and run a new project:
# create a WebAPI project without HTTPS or Swagger:
dotnet new webapi \
-o /src/api \
--no-openapi --no-https
# configure ASP.NET to listen on port 5000:
export ASPNETCORE_URLS=http://+:5000
# run the new project:
dotnet run \
--no-launch-profile \
--project /src/api/api.csproj
When you run this you'll see lots of output from the build process - NuGet packages being restored and the C# project being compiled. The output ends with the ASP.NET runtime showing the address where it's listening for requests.
Now your .NET 5 app is running inside Docker, and because the container has a published port to the host machine, you can browse to http://localhost:5000/weatherforecast on your machine. Docker sends the request into the container, and the ASP.NET app processes it and sends the response.
What you have now isn't fit to ship and run in another environment, but it's easy to get there by building your own Docker image to package your app.
I cover the path to production in my Udemy course Docker for .NET Apps
To ship your app you can use this .NET 5 sample Dockerfile to package it up. You'll do this from your host machine, so you can stop the .NET app in the container with Ctrl-C
and then run exit
to get back to your command line.
Use Docker to publish and package your WebAPI app:
# verify the source code is on your machine:
ls /tmp/dotnet-5-docker/api
# switch to your local source code folder:
cd /tmp/dotnet-5-docker
# download the sample Dockerfile:
curl -o Dockerfile https://raw.githubusercontent.com/sixeyed/blog/master/dotnet-5-with-docker/Dockerfile
# use Docker to package from source code:
docker build -t dotnet-api:5.0 .
Now you have your own Docker image, with your .NET 5 app packaged and ready to run. You can edit the code on your local machine and repeat the docker build
command to package a new version.
The SDK container you ran is gone, but now you have an application image so you can run your app without any additional setup. Your image is configured with the ASP.NET runtime and when you start a container from the image it will run your app.
Start a new container listening on a different port:
# run a container from your .NET 5 API image:
docker run -d -p 8010:80 --name api dotnet-api:5.0
# check the container logs:
docker logs api
In the logs you'll see the usual ASP.NET startup log entries, telling you the app is listening on port 80. That's port 80 inside the container though, which is published to port 8010 on the host.
The container is running in the bckground, waiting for traffic. You can try your app again, running this on the host:
curl http://localhost:8010/weatherforecast
When you're done fetching fictional weather forecasts, you can stop and remove your container with a single command:
docker rm -f api
And if you're done experimenting, you can remove your image and the .NET 5 images:
docker image rm dotnet-api:5.0
docker image rm mcr.microsoft.com/dotnet/sdk:5.0
docker image rm mcr.microsoft.com/dotnet/aspnet:5.0
Now your machine is back to the exact same state before you tried .NET 5.
You can do exactly the same thing for .NET 6, just changing the version number in the image tags. .NET 6 is in preview right now but the 6.0
tag is a moving target which gets updated with each new release (check the .NET SDK repository and the ASP.NET runtime repository on Docker Hub for the full version names).
To try .NET 6 you're going to run this for your dev environment:
mkdir -p /tmp/dotnet-6-docker
docker run -it --rm \
-p 5000:5000 \
-v /tmp/dotnet-6-docker:/src \
mcr.microsoft.com/dotnet/sdk:6.0
Then you can repeat the steps to create a new .NET 6 app and run it inside a container.
And in your Dockerfile you'll use the mcr.microsoft.com/dotnet/sdk:6.0
image for the builder stage and the mcr.microsoft.com/dotnet/aspnet:6.0
image for the final application image.
It's a nice workflow to try out a new major or minor version of .NET with no dependencies (other than Docker). You can even put your docker build
command into a GitHub workflow and build and package your app from your cource code repo - check my YouTube show Continuous Deployment with Docker and GitHub for more information on that.
To run .NET apps in containers you need to have the .NET Framework or .NET Core runtime installed in the container image. That's not something you need to manage yourself, because Microsoft provide Docker images with the runtimes already installed, and you'll use those as the base image to package your own apps.
There are several variations of .NET images, covering different versions and different runtimes. This is your guide to picking the right image for your applications.
I cover this in plenty of detail in my Udemy course Docker for .NET Apps
Your app has a bunch of pre-requisites it needs to run, things like an operating system and the language runtime. Typically the owners of the platform package an image with all the pre-reqs installed and publish it on Docker Hub - you'll see Go, Node.js, Java etc. all as official images.
Microsoft do the same for .NET apps, so you can use one of their images as the base image for your container images. They're regularly updated so you can patch your images just by rebuilding them using the latest Microsoft image.
The Docker images for .NET apps are hosted on Microsoft's own container registry, mcr.microsoft.com, but they're still listed on Docker Hub, so that's where you'll go to find them:
Those are umbrella pages which list lots of different variants of the .NET images, splitting them between SDK images and runtime images.
You can package .NET apps using a runtime image with a simple Dockerfile like this:
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8
SHELL ["powershell"]
COPY app.msi /
RUN Start-Process msiexec.exe -ArgumentList '/i', 'C:\app.msi', '/quiet', '/norestart' -NoNewWindow -Wait
(see the full ASP.NET 4.8 app Dockerfile on GitHub).
That's an easy way to get into Docker, taking an existing deployment package (an MSI installer in this case) and installing it using a PowerShell command running in the container.
This example uses the ASP.NET 4.8 base image, so the image you build from this Dockerfile:
It's a simple approach but its problematic because the Dockerfile is the packaging format and it should have all the details about the deployment, but all the installation steps are hidden in the MSI - which is a redundant additional artifact.
Instead you can compile the app from source code using Docker, which is where the SDK images come in. Those SDK images have all the build tools for your apps: MSBuild and NuGet or the dotnet
CLI. You use them in a multi-stage Docker build, where stage 1 compiles from source and stage 2 packages the compiled build from stage 1:
# the build stage uses the SDK image:
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 as builder
COPY src /src
RUN dotnet publish -c Release -o /out app.csproj
# the final app uses the runtime image:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
COPY --from=builder /out/ .
ENTRYPOINT ["dotnet", "app.dll"]
(see the full ASP.NET Core app Dockerfile on GitHub).
This approach is much better because:
I show you how to use GitHub actions with multi-stage Docker builds in my YouTube show ECS-C2: Continuous Deployment with Docker and GitHub.
There are still lots of variants of the .NET Docker images, so the next job is to work out which ones to use for different apps.
.NET Framework apps are the simplest because they only run on Windows, and they need the full Windows Server Core feature set (you can't run .NET fx apps on the minimal Nano Server OS). You'll use these for any .NET Framework apps you want to containerize - you can run them using Windows containers in Docker, Docker Swarm and Kubernetes.
All the current .NET Framework Docker images use mcr.microsoft.com/windows/servercore:lts2019
as the base image - that's the latest long-term support version of Windows Server Core 2019. Then the .NET images extend from the base Windows image in a hierarchy:
The Docker image names are shortened in that graphic, they're all hosted on MCR so they all need to be prefixed with mcr.microsoft.com/
. The tag for each is the latest release, so that's a moving target - the :ltsc2019
Windows image is updated every month with new OS patches, so if you use that in your FROM
instruction you'll always get the current release.
Microsoft also publish images with more specific tags, so you can pin to a particular release and you know that image won't change in the future. The .NET 4.8 SDK image was updated last year to include .NET 5 updates, and that broke some builds - so you could use mcr.microsoft.com/dotnet/framework/sdk:4.8-20201013-windowsservercore-ltsc2019
in your builder stage, which is pinned to the version before the change.
Here's how you'll choose between the images:
windows/servercore:lts2019
comes with .NET 4.7, so you can use it for .NET Console apps, but not ASP.NET or .NET Core apps;dotnet/framework/runtime:4.8
has the last supported version of .NET Framework which you can use to run containerized console apps;dotnet/framework/sdk:4.8
has MSBuild, NuGet and all the targeting packs installed, so you should be able to build pretty much any .NET Framework app - you'll use this in the builder stage only;dotnet/framework/aspnet:4.8
has ASP.NET 4.8 installed and configured with IIS, so you can use it for any web apps - WebForms, MVC, Web API etc.There's also dotnet/framework/wcf:4.8
for running WCF apps. All the Dockerfiles for those images are on GitHub at microsoft/dotnet-framework-docker in the src
folder, and there are also a whole bunch of .NET Framework Docker sample apps.
Those images have the 4.x runtime installed, so they can run most .NET Framework apps - everything from 1.x to 4.x but not 3.5. The 3.5 runtime adds another gigabyte or so and it's only needed for some apps, so they have their own set of images:
dotnet/framework/runtime:3.5
dotnet/framework/sdk:3.5
dotnet/framework/aspnet:3.5
.NET Core gets a bit more complicated, because it's a cross-platform framework with different images available for Windows and Linux containers. You'll use the Linux variants as a preference because they're leaner and you don't need to pay OS licences for the host machine.
If you're not sure on the difference with Docker on Windows and Linux, check out ECS-W1: We Need to Talk About Windows Containers on YouTube or enrol on Docker for .NET Apps on Udemy.
The Linux variants are derived from Debian, and they use a similar hierarchical build approach and have the same naming standards as the .NET Framework images:
Again those image names need to be prefixed with mcr.microsoft.com/
, and the tags are for the latest LTS release so they're moving targets - right now aspnet:3.1
is an alias for aspnet:3.1.11
, but next month the same 3.1
tag will be used for an updated release.
dotnet/core/runtime:3.1
has the .NET Core runtime, so you can use it for console apps;dotnet/core/sdk:3.1
has the SDK installed so you'll use it in the builder stage to compile .NET Core apps;dotnet/core/aspnet:3.1
has ASP.NET Core 3.1 installed, so you can use it to run web apps (they're still console apps in .NET Core, but the web runtime has extra dependencies)..NET Core 3.1 will be supported until December 2022; 2.1 is also an LTS release with support until August 2021, and there are images available for the 2.1 runtime using the same image names and the tag :2.1
. You'll find all the Dockerfiles and some sample apps on GitHub in the dotnet/dotnet-docker repo.
There are also Alpine Linux variants of the .NET Core images, which are smaller and leaner still. If you're building images to run on Linux and you're not interested in cross-platform running on Windows, these are preferable - but some dependencies don't work correctly in Alpine (Sqlite is one), so you'll need to test your apps:
dotnet/core/runtime:3.1-alpine
dotnet/core/sdk:3.1-alpine
dotnet/core/aspnet:3.1-alpine
If you do want to build images for Linux and Windows from the same source code and the same Dockerfiles, stick with the generic :3.1
tags - these are multi-architecture images, so there are versions published for Linux, Windows, Intel and Arm 64.
The Windows variants are all based on Nano Server:
Note that they have the same image names - with multi-architecture images Docker will pull the correct version to match the OS and CPU you're using. You can check all the available variants by looking at the manifest (you need experimental features enabled in the Docker CLI for this):
docker manifest inspect mcr.microsoft.com/dotnet/core/runtime:3.1
You'll see a chunk of JSON in the response, which includes details of all the variants - here's a trimmed version:
"manifests": [
{
"digest": "sha256:6c67be...",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"digest": "sha256:d50e61...",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
}
},
{
"digest": "sha256:3eb5f6...",
"platform": {
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.17763.1697"
}
},
{
"digest": "sha256:4d53d2d...",
"platform": {
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.18363.1316"
}
}
]
You can see in there that the single image tag dotnet/core/runtime:3.1
has image variants available for Linux on Intel, Linux on Arm and multiple versions of Windows on Intel. As long as you keep your Dockerfiles generic - and don't include OS-specific commands in RUN
instructions - you can build your own multi-arch .NET Core apps based on Microsoft's images.
.NET 5 is the new evolution of .NET Core, and there are Docker images for the usual variants on MCR:
dotnet/runtime:5.0
dotnet/sdk:5.0
dotnet/aspnet:5.0
Note that "core" has been dropped from the image name - there's more information in this issue .NET Docker Repo Name Change.
Migrating .NET Core apps to .NET 5 should be a simple change, but remember that 5 is not an LTS version - you'll need to wait for .NET 6, which is LTS (see Microsoft's .NET Core and .NET 5 Support Policy.
This adventure lets you code on your normal dev machine from some other machine, using the browser. It's powered by Docker plus:
And it's very simple. You just run code-server
in a Docker container on your dev machine, mapping volumes for the data you want to be able to access and publishing a port. Then you expose that port to the Internet using ngrok
, make a note of the URL and walk out the door.
code-server
has done all the hard work here. They publish images to codercom/code-server on Docker Hub. There are only x64 Linux images right now.
Run the latest version with:
docker container run \
-d -p 8443:8443 \
-v /scm:/scm \
codercom/code-server:1.621 \
--allow-http --no-auth
That command runs VS Code as a headless server in a background container. The options:
8443
on your local machine into the container/scm
directory into /scm
on the containerYou can run insecure on your home network (if you trust folks who can access your network), because you'll add security with
ngrok
.
Now you can browse to http://localhost:8443 and you have VS Code running in the browser:
That volume mount means all of the code in the scm
folder on my machine is accessible from the VS Code instance. And you can fire up a terminal in VS Code in the browser, which means you can do pretty much anything else you need to do. But remember the terminal is executing inside the container, so the environment is the container.
The code-server
images comes with a few dev tools installed, like Git and OpenSSL. But there are no dev toolkits, so you can't actually compile or run any code... Unless you're using multi-stage Dockerfiles and official images with SDKs installed. Then all you need is Docker.
code-server
doesn't have the Docker CLI installed, but I've added that in my fork. So you can run my version and mount the local Docker socket as a volume, meaning you can use docker
commands inside the browser-based VS Code instance:
docker container run \
-d -p 8443:8443 \
-v /scm:/scm \
-v /var/run/docker.sock:/var/run/docker.sock \
--network code-server \
sixeyed/code-server:1.621 \
--allow-http --no-auth
(I'm also using an explicit Docker network here which I created with docker network create code-server
. You'll see why in a moment).
Now you can refresh your browser at http://localhost:8443, open up a terminal and run all the docker
commands you like (with sudo
). The Docker CLI inside the container is connected to the Docker Engine which is running the container.
Let's try out the .NET Core 3.0 preview. You can run these commands in VS Code on the browser. They all execute inside the container:
git clone https://github.com/sixeyed/whoami-dotnet.git
cd whoami-dotnet
sudo docker image build -t sixeyed/whoami-dotnet:3.0-linux-amd64 .
sudo docker container run -d \
--network code-server --name whoami \
sixeyed/whoami-dotnet:3.0-linux-amd64
Now the whoami
container is running in the same Docker network as the code-server
container, so you can reach it by the container name:
curl http://whoami
And here it is for real:
Now this is a usable development environment. The multi-stage Dockerfile I've built starts with a build stage that uses an image with the .NET Core SDK, so there's no need to install any tools in the dev environment. You can do the same with Java, Go etc. - they all have official build images on Docker Hub.
And the final step is to make it publicly available through ngrok
.
Sign up for an ngrok account, and follow the setup instructions to install the software and apply your credentials. Now you can expose any local port through a public Internet tunnel - just by running something like ngrok http 8443
.
But you can do more with ngrok
. This command sets up a tunnel for my VS Code server with HTTPS and basic authentication:
ngrok http -bind-tls=true -auth="elton:DockerCon" 8443
You'll see output like this, telling you the public URL for your tunnel and some stats about who's using it:
The Forwarding
line tells you the public URL and the local port it's forwarding. Mine is https://112f7fb1.ngrok.io
(you can use custom domains instead of the random ones). That endpoint is HTTPS so it's secure, and it's using basic auth so you'll need the username and password you specified in the ngrok
command:
Now you can access the headless VS Code instance running on your dev machine from anywhere on the Internet. Browser sessions are separate, so you can even have multiple people doing different things on the same remote code server:
ngrok
collects metrics while it's running, and there's an admin portal you can browse to locally - it shows you all the requests and responses the tunnel has handled:
I've only had a quick look, but it seems like this could work on Windows. ngrok
already has Windows support, and it should just mean packaging code-server
with a different Dockerfile.
Sounds like a nice weekend project for someone. Docker on Windows - second edition! will help :)