Category Archives: Docker

error NU1301: Unable to load the service index for source https://api.nuget.org/v3/index.json

I was running a docker build against a .NET Web API project, on a Windows OS machine connected to a VPN and I was getting the error error NU1301: Unable to load the service index for source https://api.nuget.org/v3/index.json.

This fixed it for me…

  • Open Docker Desktop
  • Click the Settings button
  • Click on the Docker Engine option
  • Add a dns section to the JSON configuration, i.e.
    {
      "dns": [
          "8.8.8.8"
      ],
      "experimental": false
    }
    

Running PowerShell in a Windows container

I was messing around with Windows based docker images, running IIS and an ASP.NET 4.7.x application and wanted to connect to it’s “shell” to explore what was installed etc.

For Linux container you’ll probably use bash. From a Windows container we can just use powershell

Just change “the-container-id” to the id of the container you wish to connect to

docker container exec -it the-container-id powershell

Is my ASP.NET core application running in Kubernetes (or Docker) ?

I came across a situation where I needed to change some logic in my ASP.NET core application dependant upon whether I was running inside Kubernetes or an Azure Web app, luckily there is an environment variable that we can use (and whilst we’re at it, there’s one for Docker as well)

var isHostedInKubernetes = 
   Environment.GetEnvironmentVariable("KUBERNETES_SERVICE_HOST") != null;
var isHostedInDocker = 
   Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true";

Switching the Docker Desktop Daemon

Docker Desktop allows us to work on Windows images and Linux, but sadly (at least at the moment) we may need to switch the builder used by it, either by using the UI or

DockerCli -SwitchDaemon
DockerCli -SwitchLinuxEngine
DockerCli -SwitchWindowsEngine

Dockerize your React application

You’ve created you React application and are now looking to create a docker image for it.

Before we look at the Dockerfile, create yourself a .dockerignore file that looks like this

.git
node_modules

We do not require the .git folder in our image and we’re going to install our node modules via npm install as you’ll find copying the node_modules folder(s) is slow.

Let’s jump straight in and look at a Dockerfile that will take our React code containerize it and set the container up to run nginx to host it.

FROM node:21-alpine3.18 as build

WORKDIR /usr/app
COPY . .
RUN npm install
RUN npm run build

FROM nginx:alpine
COPY --from=build /usr/app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

We’re using a node alpine image as our base. This is a lightweight image as we won’t our image to be lean. Next we create our WORKDIR, this can be set to your preferred location but just remember to reuse that location in the COPY command. We’re using .dockerignore to ignore .git and node_modules which allows us to then just copy everything to the image, where we then install then build our React application.

Finally we use nginx to serve our application, first copying the build folder to nginx and finally we set up the CMD to run nginx once the container is started, thus hosting our React application.

To build our image we can just use the following (change the tag name and version to suit your application)

docker build -t my-app:0.1.0 .

and to run, again set your application name any port redirects and use you tagged and version

docker run --rm --name my-app -p 8080:80 -d my-app:0.1.0

We could also extend this sample to include copying of nginx.conf to the image, for example if you want the supply a config like this

worker_processes 4;

events { worker_connections 1024; }

http {
  server {
    listen 4200;
    root  /usr/share/nginx/html;
    include /etc/nginx/mime.types;

  location / {
    root   /usr/share/nginx/html;
    index  index.html;
    try_files $uri $uri/ /index.html;
  }
}
}

If we assume we’re storing out nginx.conf file in a folder named .ngnix (this is not required it’s just for this example) then we could add the following to the Dockerfile, after the line FROM nginx:alpine add the following and whilst we’re at it let’s get rid of the default files that might be located in the images nginx/html folder

COPY ./.nginx/nginx.conf /etc/nginx/nginx.conf
RUN rm -rf /usr/share/nginx/html/*

Docker Compose

Whilst we’re here, let’s create a simple docker-compose.yaml file for our new image.

version: '3.8'
services:
  front-end:
    build:
      context: ./ui
      dockerfile: ./ui/Dockerfile
    ports:
      - 4200:4200
    image: putridparrot/my-app:0.1.0
    container_name: my-app

We might like to store configuration for this image on the hosting server, i.e. via a volume in which case we’d simply add to the bottom of this file the following

    volumes:
      - ./ui/public/appsettings.json:/usr/share/nginx/html/appsettings.json

In this example we’re using an appsettings.json file to configure the environment, or it might include feature flag settings or whatever and assuming it’s stored in the public folder of you React application.

Now we just docker-compose up.

Docker on Windows

Note: I’m going through draft posts that go back to 2014 and publishing where they still may have value. They may not be 100% upto date but better published late than never.

Up until recently I’ve been solely using Docker on Linux, but I’ve a need to use it with Windows now. I’ve installed Docker Desktop and set to Switch to Windows containers.

So first off we’re going to download a Windows image, whilst there are several variants, for server, nano windows, we’re going to go pretty much full blown using

docker pull mcr.microsoft.com/windows:2004

Note: It may take some time to download, it’s > 3GB

Now just to prove it worked let’s run it in interactive mode and starting the cmd prompt using

docker run -it mcr.microsoft.com/windows:2004 cmd.exe

Exit your image by typing exit followed by return.

I’m going to want to create a volume to save/share files with the Windows machine running the container, so let’s create a folder c:\shared and then copy or create a file in here, just to allow us to prove we can connect to it through the container, so mine’s predictably HelloWorld.txt, now run

docker run -it -v c:\shared:c:\shared mcr.microsoft.com/windows:2004 cmd.exe

Note: Change cmd.exe to powershell.exe if you prefer

I’m naming the folder in the image the same as the real machine’s folder, but you can change it to suit, just change the second path like this

docker run -it -v c:\shared:c:\shared2 mcr.microsoft.com/windows:2004 cmd.exe

and if you created a simple text file with a message in it, once we’re inside the container we can type

type HelloWorld.txt

from the c:\shared folder to see it it’s as expected.

Now I’m wanting to start to install applications into the container, but with no GUI we need “silent installs”

msiexec /i .\myapp.msi /QN /L*V install.log

Postgresql in Docker

Let’s run up the Docker image with an instance of PostgreSQL

docker run --name mypostgres -d postgres

Now, connect to the instance so we can create a database etc.

docker exec -it mypostgres bash
createdb -U postgres MyDatabase

Note: if you find this error message psql: FATAL: role “root” does not exist, you’ll need to switch to the postgres user, see below.

Switch to the postgres user (su substitute user).

su postgres
psql

At which point, we’re now in the psql application and can create databases etc.

Eureka in Docker

I’ve actually covered this in a previous post, but because it was part of Eureka Server (using Steeltoe) revisited. It didn’t quite stand out when I was searching for it, so let’s recreate here in it’s own post…

Pull the docker image using

docker pull steeltoeoss/eureka-server

Now to run it

docker run --publish 8761:8761 steeltoeoss/eureka-server

This will now be available locally or remotely on port 8761, i.e.

	
http://locahost:8761

Deploying an ASP.NET core application into a Docker image within Kubernetes

In the previous post we looked and an “off the shelf” image of nginx, which we deployed to Kubernetes and were able to access externally using Ingress. This post follows on from that one, so do refer back to it if you have any issues with the following configurations etc.

Let’s look at the steps for deploying our own Docker image to k8s and better still let’s deploy a dotnet core webapp.

Note: Kubernetes is deprecating its support for Docker, however this does not mean we cannot deployed docker images, just that we need to use the docker shim or generated container-d (or other container) images.

The App

We’ll create a standard dotnet ASP.NET core Razor application which you can obviously do what you wish to, but we’ll take the default implementation and turn this into a docker image and then deploy it to k8s.

So to start with…

  • Create a .NET core Razor application (mine’s named razordemo), you can do this from Visual Studio or using
    dotnet new webapp -o razordemo --no-https -f net5.0
    
  • If you’re running this on a remote machine don’t forget to change launchSettings.json localhost to 0.0.0.0 if you need to.
  • Run dotnet build

It’d be good to see this is all working, so if let’s run the demo using

dotnet run

Now use your browser to access http://your-server-ip:5000/ and you should see the Razor demo home page, or use curl to see if you get valid HTML returned, i.e.

curl http://your-server-ip:5000

Generating the Docker image

Note: If you changed launchSettings.json to use 0.0.0.0, reset this to localhost.

Here’s the Dockerfile for building an image, it’s basically going to publish a release build of our Razor application then set up the image to run the razordemo.dll via dotnet from a Docker instance.

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY razordemo.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "razordemo.dll"]

Now run docker build using the following

docker build . -t razordemo --no-cache

If you want to check the image works as expect then run the following

docker run -d -p 5000:80 razordemo 

Now check the image is running okay by using curl as we did previously. If all worked you should see the Razor demo home page again, but now we’re seeing if within the docker instance.

Docker in the local registry

Next up, we want to deploy this docker image to k8s.

k8s will try to get an image from a remote registry and we don’t want to deploy this image outside of our network, so we need to rebuild the image, slightly differently using

docker build . -t localhost:32000/razordemo --no-cache

Reference: see Using the built-in registry for more information on the built-in registry.

Before going any further, I’m using Ubuntu and micro8s, so will need to enable the local registry using

microk8s enable registry

I can’t recall if this is required, but I also enabled k8s DNS using

microk8s.enable dns

Find the image ID for our generated image using

docker images

Now use the following commands, where {the image id} was the one found from the above command

docker tag {the image id} localhost:32000/razordemo
docker push localhost:32000/razordemo

The configuration

This is a configuration based upon my previous post (the file is named razordemo.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  selector:
    matchLabels:
      run: webapp
  replicas: 1
  template:
    metadata:
      labels:
        run: webapp
    spec:
      containers:
      - name: webapp
        image: localhost:32000/razordemo
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webapp
  labels:
    run: webapp
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: webapp
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: razor-ingress
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: webapp
            port: 
              number: 80

Now apply this configuration to k8s using (don’t forget to change the file name to whatever you named your file)

kubectl apply -f ./razordemo.yaml

Now we should be able to check the deployed image, by either using the k8s dashboard or run

kubectl get ep webapp

Note the endpoint and curl to that endpoint, if all worked well you should be able to see the ASP.NET generate home page HTML and better still access http://your-server-ip from another machine and see the webpage.

Eureka Server (using Steeltoe) revisited

The Eureka server is used as a registry for maintaining lists of services and their endpoints. It’s used a lot in the microservice world by way of a microservice registering it’s existence somewhere (in this case in Eureka). When a client (whether it’s another service or anything else for that matter) want to access a service it asks the registry for an instance, in this case we connect to the Eureka server and find the services instance(s) that are available for a specific application name and are ofcourse UP.

Installing and running Eureka Server

In a previous post Spring boot Eureka server we wrote a Java application to run a Eureka server. In this post we’re going to use Docker to host the server and then use Steeltoe with .NET to interact with the instance.

To get an image of Eureka server, let’s use the Steeltoe docker image (Spring and others exist, the Steeltoe image is not intended for production, but is fine for what we want to do)

docker pull steeltoeoss/eureka-server

Now run up the docker image using

docker run --publish 8761:8761 steeltoeoss/eureka-server

If all goes well, connect to the Spring Eureka dashboard using

http://locahost:8761

change localhost to the ip/host you’re running the Eureka server from, now you should now see the Spring Eureka web page.

Registering a .NET core client with Eureka

I’ve a post on this topic A .NET service registered with Eureka, but let’s go through the process again with the current version of Steeltoe (as the NuGet packages have changed somewhat).

  • Create an ASP.NET Core Web Application, as this will represent a REST service that we want to interact with. My project is called RegisterExample and is an API project.
  • Add the NuGet package Steeltoe.Discovery.ClientCore and Steeltoe.Discovery.Eureka
  • In Startup.cs within ConfigureServices add the following
    services.AddDiscoveryClient(Configuration);
    
  • Within the Configure method add the following
    app.UseDiscoveryClient();
    
  • Finally add the following to the appsettings.json file
    // Eureka info
      "eureka": {
        "client": {
          "serviceUrl": "http://localhost:8761/eureka/",
          "shouldFetchRegistry": "false",
          "shouldRegisterWithEureka": true,
          "validateCertificates": false
        },
        "instance": {
          "appName": "weatherapi",
          "port": "8080",
          "ipAddress": "localhost",
          "preferIpAddress": true
        }
      }
    

We’re going to leave the API with the default weatherapi, hence the appName in the appsettings.

Notice we set the value for “shouldFetchRegistry” to false as this service will not be acting as a client to any other services. Obviously change this is you also need to discovery other services. “shouldRegisterWithEureka” is set to true as we want this service to automatically register itself with Eureka.

Now navigate to the URL of your Eureka server again (or refresh) and you should see a new Application. In my case I have an application with the name weatherapi. This name comes from our appsettings.json configuration application/name.

Info and Health

If you click on the instance link within the Eureka server dashboard, you will navigate to

https://localhost:5001/info

(or whatever ip/hostname and port your service is running on) you get a 404, so let’s fix that in our project.

Info, by default will display some basic information about the application, product version etc. However you can also add further custom information if you want, but example the git build SHA1 hash or just some general info.

  • Add NuGet package Steeltoe.Management.EndpointCore
  • In Startup.cs ConfigureServices, add the following
    services.AddSingleton<IInfoContributor, MyInfoContributor>();
    services.AddInfoActuator(Configuration);
    services.AddHealthActuator(Configuration);
    

    The first of these lines will add our implementation of an IInfoContributor to allow for custom info.

  • Still in Startup.cs, but now the method Configure, add the following to the UseEndpoints endpoint routes

    endpoints.Map<InfoEndpoint>();
    endpoints.Map<HealthEndpoint>();
    
  • Now we’ll create a simple implementation of and IInfoContributor which allows us to add our own info, so add the following class

    public class MyInfoContributor : IInfoContributor
    {
       public void Contribute(IInfoBuilder builder)
       {
          builder.WithInfo("MyInfo", new { SomeName = "Scooby Doo" });
       }
    }
    

Now when we run our service we hope to see our info data, however by default Steeltoe seems to set the info and health endpoint to /actuator/info and /actuator/health respectively. Eureka seems to expect /info. So go to the appsettings.json and add the following to the Instance section

"StatusPageUrlPath": "/actuator/info",
"HealthCheckUrlPath": "/actuator/health" 

Note: I’m not sure what I’m missing here and why the defaults don’t match up, but these configuration changes will tell the Eureka server (when we register our service with it) that it should use these paths for info and health.

Now, if you run the service again for /actuator/info you should see something like this

{"MyInfo":{"someName":"Scooby Doo"},
"applicationVersionInfo":{"ProductName":"RegisterExample",
"FileVersion":"1.0.0.0","ProductVersion":"1.0.0"},
"steeltoeVersionInfo":"ProductName":"Steeltoe.Management.Endpoint",
"FileVersion":"3.0.2.0","ProductVersion":
"3.0.2\u002B4089779c66d127f40325a3be9b613149b3b090f2"}}

and health something like

{"status":"UP","details":{"liveness":{},"diskSpace":{"total":4000769372160,
"free":3889734168576,"threshold":10485760,"status":"UP"},"eurekaServer":
{"remoteInstStatus":"UNKNOWN","fetchStatus":"Not fetching","heartbeat":
"Successful","heartbeatStatus":"UP","heartbeatTime":"2021-01-23T20:40:44",
"status":"UP","applications":"NONE"},"readiness":{}}}