Use config file in .Net core Console App

For this example, I’m using a Console App created in .Net Core 3.1 using Visual Studio 2019.

Add a json file to your Project and name it appSettings.json, it could like like below:

{
  "credentials": {
    "username": "xxxx",
    "password": "xxxx"
  },
  "URL": "https://workdayapipoc.egonzehnder.com"
}

Install the following Nuget packages in your Project:

<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />

The versions could vary depending on the time you’re adding these packages.

In your Program.cs file, add the namespace:

using Microsoft.Extensions.Configuration;

In the Main method, add the following code:

class Program
{
    static void Main(string[] args)
    {
		//....
		
		IConfiguration Config = new ConfigurationBuilder()
                .AddJsonFile("appSettings.json")
                .Build();
				
		var URL = Config.GetSection("URL").Value;
		
		//Assuming you're using Encrypted values in configuration.
		var dusername = EncryptionClass.Decrypt(Config.GetSection("credentials")["username"]);
        var dpassword = EncryptionClass.Decrypt(Config.GetSection("credentials")["password"]);
		
		//.....
		
    }
}

Redirect with URLRewrite based on QueryString

Consider a situation where you need to redirect your Application to the Error page when you receive a particular value in your query string. This can be done using URL Rewrite with an inbound rule either directly in IIS or add rules directly in your web.config.

Below configuration can be added in web.config:

<system.webServer>
	<rewrite>
		<rules>
			<rule name="BlockQS" enabled="true" stopProcessing="true">
				<match url=".*" />
				<conditions>
							<add input="{QUERY_STRING}" pattern="P=abc" />
				</conditions>
				<action type="Redirect" url="https://website.com/ErrorMessage.aspx" appendQueryString="false" />
			</rule>
		</rules>
	</rewrite>
</system.webServer>

If you check directly under IIS URLRewrite feature for your website, it would look like this:

URL Rewrite

Now, when you try to access https://website.com/?P=abc, it would redirect to the configured error page.

To install URL Rewrite, follow the link.

Deploy ReactJS App Container to Kubernetes on Mac

In the previous post, we Dockerized the ReactJS App and were able expose it from our Container to our machine to run it in the browser on http://localhost:3000. Now we want our Docker containers to be Orchestrated using Kubernetes.

First, we need to enable Kubernetes from Docker Desktop settings. This will install Kubernetes with Apply and Restart option. Just follow the default options.

Verify the installation with to check Client and Server versions:

>kubectl version

Make sure the context is set to docker-desktop which can also be checked from the docker icon at the top.

>kubectl config current-context

docker-desktop

If the context is different (in case you’re also running MiniKube), we can switch it using:

>kubectl config use-context docker-for-desktop

Switched to context "docker-for-desktop"

Check cluster info:

>kubectl cluster-info

Kubernetes master is running at https://kubernetes.docker.internal:6443
KubeDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

Check the pods running in the cluster:

>kubectl get nodes

NAME             STATUS   ROLES    AGE   VERSION
docker-desktop   Ready    master   23h   v1.19.7

Install the Kubernetes Dashboard with:

>kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc3/aio/deploy/recommended.yaml

The Dashboard application will get deployed as a Pod in the kubernetes-dashboard namespace. We can get a list of all our Pods in all namespaces via the following command:

>kubectl get pods --all-namespaces

kubernetes-dashboard   dashboard-metrics-scraper-c95fcf479-8l8vf   1/1     Running   0          23h
kubernetes-dashboard   kubernetes-dashboard-5bc6d86cfd-6b68m       1/1     Running   1          23h

Just check if your kubernetes-dashboard-* pod is in running state. Now, start the local Proxy server to access your Dashboard:

>kubectl proxy

Starting to serve on 127.0.0.1:8001

Once the proxy server is started, you can access the Dashboard at:

http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy

To Login to the Dashboard, we’ll login using a token. The Token can be generated by running the command:

>kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | awk '/^deployment-controller-token-/{print $1}') | awk '$1=="token:"{print $2}'

Now our basic setup is complete to get Kubernetes running on Mac.

Now we need to get our ReactJS App ready for Deployment on Kubernetes. Create the Deployment and Service manifest to run it on Kubernetes and expose the required port. Create the combined .yml file as below, however these can be created separately as well:

kind: Deployment
apiVersion: apps/v1
metadata:
  name: reactondocker
spec:
  replicas: 2
  selector:
    matchLabels:
      app: reactondocker
  template:
    metadata:
      labels:
        app: reactondocker
    spec:
      containers:
        - name: reactondocker
          image: myimage/reactondocker:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 3000

---

kind: Service
apiVersion: v1
metadata:
  name: reactondocker
spec:
  type: NodePort
  ports:
    - port: 3000
      targetPort: 3000
      protocol: TCP
      nodePort: 30004
  selector:
    app: reactondocker
      

I’m using the NodePort Service to expose it on port 30004. Number of Pods to run is 2.

Note: If you’ve not pushed the image to a registry, then change imagePullPolicy: Never or IfNotPresent so it uses the local image created using Docker on your machine. Also, change the image name accordingly.

Run the below command to create the Deployment and the Service:

>kubectl create -f deployment.yml

deployment.apps/reactondocker created
service/reactondocker created

You can check the Pods running for reactondocker in your Kubernetes dashboard or with the below command:

>kubectl get pods

The status of the Pods should be running. Try accessing the Application in your browser as http://localhost:30004.

Finally, we should delete the ReactJS Application from the Kubernetes cluster:

>kubectl delete service,deployment reactondocker

service "reactondocker" deleted
deployment.apps "reactondocker" deleted

Dependency Injection example C#

Dependency Injection is decoupling the usage of an object from it’s creation. An abstraction is created between a higher level class and it’s dependencies.

In the below example, the Notification service (lower level module) is abstracted from the higher level class User.

Create Interface as below:

public interface INotification
{
    void SendNotification(User user);
}

Create a class that implements the above interface:

public class ConsoleNotification: INotification
{   
    public void SendNotification(User user)
    {
        Console.WriteLine($"Changed name to: {user.username}");
    }
}

There can be other classes like EmailNotification that implements this interface.

Now, create a class called User that would require to send notification to a user:

public class User
{
    public string username { get; private set; }
    INotification _notify;

    public User(string name, INotification notify)
    {
        username = name;
        _notify = notify;
    }

    public void changeName(string name)
    {
        username = name;
        _notify.SendNotification(this);
    }
}

In the above code, if the User class was itself creating the ConsoleNotification object, that would have been tight coupling between the 2 classes. So, if we wanted to change the Notification service to EmailNotification, we would have to change the User class directly.

The client which in our case is the Program.cs code, will pass the ConsoleNotification object to the user object in the Constructor which is called Constructor injection.

class Program
{
    static void Main(string[] args)
    {
        var notify = new ConsoleNotification();

        //Passing ConsoleNotification type, could be any other type implementing INotification..
        var user1 = new User("Tim", notify);

        Console.WriteLine($"User Name: {user1.username}");
        user1.changeName("Jim");
        Console.ReadKey();
    }
}

Output:

User Name: Tim
Changed name to: Jim

So, the User class is not directly creating a dependency on a particular type of notification Service. Rather the client is telling at run-time what instance of notification service the User class should use which is also called inversion of control (IoC). We’re creating the object of the dependency first and then injecting it into the higher level object.

Kubernetes ReplicaSet example on Mac using VS Code

A ReplicaSet helps load balance and scale our Application up or down when the demand for it changes. It makes sure the desired number of pods are always running for high availability.

I’m using VS Code on Mac to create the below yaml file.

Create the following yaml file:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-replicaset
  labels:
    app: myapp
spec:
  selector:
    matchLabels:
      app: myapp
  replicas: 3
  template:
    metadata:
      name: nginx-2
      labels:
        app: myapp
    spec:
      containers:
        - name: nginx
          image: nginx

The above yaml file has 4 main properties which are apiVersion, kind, metadata and spec. The spec contains the definition for the number of replicas and the containers to be created inside the pods.

Run the following command:

$ kubectl create -f ReplicaSetsDemo.yaml 

replicaset.apps/myapp-replicaset created

Now let’s check the status of the replicaSet and then the pods:

$ kubectl get replicaset
NAME               DESIRED   CURRENT   READY   AGE
myapp-replicaset   3         3         3       2m29s


$ kubectl get pods

NAME                     READY   STATUS    RESTARTS   AGE
myapp-replicaset-s7jm7   1/1     Running   0          33s
myapp-replicaset-svqvm   1/1     Running   0          33s
myapp-replicaset-xnbbq   1/1     Running   0          33s

The above command shows 3 pods created for nginx with the name of the replicaset prefixed. A replicaset ensures that sufficient number of replicas or pods are available at all times.

Now, let’s delete a pod:

$ kubectl delete pod myapp-replicaset-s7jm7

pod "myapp-replicaset-s7jm7" deleted

Check the status of the pods again:

$ kubectl get pods
                         
NAME                     READY   STATUS    RESTARTS   AGE
myapp-replicaset-d7f88   1/1     Running   0          36s
myapp-replicaset-svqvm   1/1     Running   0          5m53s
myapp-replicaset-xnbbq   1/1     Running   0          5m53s

Notice that there are still 3 pods running as one more pod was created to maintain the desired state. Also, if you try to create a pod with the same label app=myapp outside of the replicaset, it’ll still come under the purview of the replicaset we created and will terminate that pod to maintain the desired state of 3 replicas. This is where it differs from a Replication Controller.

Now, let’s edit the replicaset to scale it up as below:

$ kubectl edit replicaset myapp-replicaset

The above command let’s you edit the in-memory configuration file that Kubernetes creates. Search for the replicas section and edit it to 4 using the vi editor commands and save it with wq!

Upon saving you’ll see the output as:

replicaset.apps/myapp-replicaset edited

Check the pods status and you’ll see and additional pod created:

$ kubectl get pods
                            
NAME                     READY   STATUS    RESTARTS   AGE
myapp-replicaset-d7f88   1/1     Running   0          14m
myapp-replicaset-pnlvh   1/1     Running   0          55s
myapp-replicaset-svqvm   1/1     Running   0          19m
myapp-replicaset-xnbbq   1/1     Running   0          19m

Another way to scale the replicaset is to use the below command and check the status again:

$ kubectl scale replicaset myapp-replicaset --replicas=2

replicaset.apps/myapp-replicaset scaled


$ kubectl get pods

NAME                     READY   STATUS    RESTARTS   AGE
myapp-replicaset-svqvm   1/1     Running   0          22m
myapp-replicaset-xnbbq   1/1     Running   0          22m

Notice that the pods are scaled down to 2 only as other 2 got terminated.

Kubernetes Deployments demo with update and rollback

We’re going to follow this demo by creating a Deployment for nginx image. Below is the example yaml file that I’m using for creating the Deployment as deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  labels:
    tier: frontend
spec:
  replicas: 6
  template:
    metadata:
      name: nginx-2
      labels:
        app: myapp
    spec:
      containers:
        - name: nginx
          image: nginx
  selector:
    matchLabels:
      app: myapp

First we’ll create the deployment using the below command and record it:

$ kubectl create -f deployment.yaml --record

Now, let’s quickly check the status of the deployment:

$ kubectl rollout status deployment/myapp-deployment

The above command will give output as below:

Waiting for deployment "myapp-deployment" rollout to finish: 0 of 6 updated replicas are available...
Waiting for deployment "myapp-deployment" rollout to finish: 1 of 6 updated replicas are available...
Waiting for deployment "myapp-deployment" rollout to finish: 2 of 6 updated replicas are available...
Waiting for deployment "myapp-deployment" rollout to finish: 3 of 6 updated replicas are available...
Waiting for deployment "myapp-deployment" rollout to finish: 4 of 6 updated replicas are available...
Waiting for deployment "myapp-deployment" rollout to finish: 5 of 6 updated replicas are available...
deployment "myapp-deployment" successfully rolled out

To view the version history, run below command:

$ kubectl rollout history deployment/myapp-deployment

//You can see below output with change-cause..
deployment.apps/myapp-deployment 
REVISION  CHANGE-CAUSE
1         kubectl create --filename=deployment.yaml --record=true

Now let’s edit the deployment and change the nginx version to a lower version:

$ kubectl edit deployment myapp-deployment --record

Search for the image name and change the section as below and save with wq! vi command:

containers:
  image: nginx:1.18


//output after saving with wq!:
deployment.apps/myapp-deployment edited

Run the status command again to see the effect of Rolling updates:

$ kubectl rollout status deployment/myapp-deployment

You can also run the describe deployment command to check the effect as below:

$ kubectl describe deployment myapp-deployment


//Check the events section in the output as below:
Events:
  Type    Reason             Age                    From                   Message
  ----    ------             ----                   ----                   -------
  Normal  ScalingReplicaSet  13m                    deployment-controller  Scaled up replica set myapp-deployment-fd8649446 to 6
  Normal  ScalingReplicaSet  6m9s                   deployment-controller  Scaled up replica set myapp-deployment-ddbb47dd8 to 2
  Normal  ScalingReplicaSet  6m9s                   deployment-controller  Scaled down replica set myapp-deployment-fd8649446 to 5
  Normal  ScalingReplicaSet  6m9s                   deployment-controller  Scaled up replica set myapp-deployment-ddbb47dd8 to 3
  Normal  ScalingReplicaSet  5m40s                  deployment-controller  Scaled down replica set myapp-deployment-fd8649446 to 4
  Normal  ScalingReplicaSet  5m40s                  deployment-controller  Scaled up replica set myapp-deployment-ddbb47dd8 to 4
  Normal  ScalingReplicaSet  5m34s                  deployment-controller  Scaled down replica set myapp-deployment-fd8649446 to 3
  Normal  ScalingReplicaSet  5m34s                  deployment-controller  Scaled up replica set myapp-deployment-ddbb47dd8 to 5
  Normal  ScalingReplicaSet  5m27s                  deployment-controller  Scaled down replica set myapp-deployment-fd8649446 to 2
  Normal  ScalingReplicaSet  5m17s (x3 over 5m26s)  deployment-controller  (combined from similar events): Scaled down replica set myapp-deployment-fd8649446 to 0

The Annotations in the above description from the output after you run the describe command, will show the revision and the image is now nginx:1.18.

Let’s again change the nginx version using a set mage command and record it and check the status:

$ kubectl set image deployment myapp-deployment nginx=nginx:1.18-perl --record

Check the recorded versions again:

$ kubectl rollout history deployment/myapp-deployment


//output as below:
REVISION  CHANGE-CAUSE
1         kubectl create --filename=deployment.yaml --record=true
2         kubectl edit deployment myapp-deployment --record=true
3         kubectl set image deployment myapp-deployment nginx=nginx:1.18-perl --record=true

You can see above how we changed between different nginx versions and the reason for it got recorded. Verify your pods, as all 6 should be running:

$ kubectl get pods

Let’s rollback the last version and see how we move back to the previous version, then check the version again which should show as Revision 4:

$ kubectl rollout undo deployment/myapp-deployment

$ kubectl rollout status deployment/myapp-deployment

$ kubectl describe deployment myapp-deployment

//check the nginx image should be nginx:1.18 in the description..

Check the versions again:

$ kubectl rollout history deployment/myapp-deployment

deployment.apps/myapp-deployment 
REVISION  CHANGE-CAUSE
1         kubectl create --filename=deployment.yaml --record=true
3         kubectl set image deployment myapp-deployment nginx=nginx:1.18-perl --record=true
4         kubectl edit deployment myapp-deployment --record=true

Please note that the Revision 2 earlier now shows as Revision 4 after undo.

Once you’re done, delete the Deployments as below:

$ kubectl delete deployment myapp-deployment

Facade Pattern C#

This is a structural design pattern that only exposes a simplified interface to the outside world while hiding the complex subsystems. The facade keeps the unwanted dependencies to one place. Its own behaviour takes care of the internal complexities of the subsystems.

A car acts like a facade to the outside world which exposes it’s own behaviour like on, accelerate, brake, off etc. But you are not aware of how it all works internally.

Let’s take a small example of an online Shopping cart where you add an item to a cart which is the only behaviour exposed for the Client. Once you add the item to the cart, a few things happen internally which are not directly controlled by the user action like checking availability of item, calculation of tax, locking the item for availability and calculating the cost etc.

Create a Console Application which is the Client in this example that will only add the Item to cart. Also, add a class library Shopping that will do all of the above described operations but only expose the Cart to the Client.

public interface IShoppingCart
    {
        bool addItemToCart(int itemid, int qty);
    }

internal interface IStock
    {
        bool checkStockItemsByID(int itemID);
        bool lockItemsTemp(int itemID, int qty);
    }

internal interface IItemCost
    {
        string getItemCostByID(int itemid);
    }

internal interface ITaxes
    {
        string getStateTax(string statecode);
    }

Now, implement the above behaviours while keeping only the ShoppingCart class public:

internal class Stock : IStock
    {
        public bool checkStockItemsByID(int itemID)
        {
            Console.WriteLine($"Item Id: {itemID} is available");
            return true;
        }

        public bool lockItemsTemp(int itemID, int qty)
        {
            Console.WriteLine($"Item Id: {itemID}, quantity: {qty} is locked");
            return true;
        }
    }


internal class ItemCost : IItemCost
    {
        public string getItemCostByID(int itemid)
        {
            Console.WriteLine($"Item Id: {itemid} cost is $25");
            return "25";
        }
    }


internal class Taxes : ITaxes
    {
        public string getStateTax(string statecode)
        {
            Console.WriteLine($"{statecode} state tax is 3.5%");
            return string.Empty;
        }
    }

//Exposed class
public class ShoppingCart : IShoppingCart
    {
        public bool addItemToCart(int itemid, int qty)
        {
            IStock stock = new Stock();
            stock.checkStockItemsByID(itemid);
            stock.lockItemsTemp(itemid, qty);

            IItemCost itemCost = new ItemCost();
            itemCost.getItemCostByID(itemid);

            ITaxes taxes = new Taxes();
            taxes.getStateTax("NY");

            return true;
        }
    }

The console Application only performs the action to add the Item to the cart:

class Program
    {
        static void Main(string[] args)
        {
            IShoppingCart item = new ShoppingCart();
            item.addItemToCart(123, 2);
            Console.ReadKey();
        }
    }


Output:
Item Id: 123 is available
Item Id: 123, quantity: 2 is locked
Item Id: 123 cost is $25
NY state tax is 3.5%

Singleton pattern C#

A Singleton class in OOP allows only one instance of itself to be created. While a conventional class can have any number of instances created. Below is the thread-safe implementation of Singleton class.

using System;
namespace SingletonPattern
{
    // thread-safe way.
    public sealed class Singleton
    {
        private static Singleton obj = null;
        // mutex lock used for thread-safety.
        private static readonly object mutex = new object();
        private int var = 0;

        private Singleton()
        {
        }

        public static Singleton getObj
        {
            get
            {
                lock (mutex)
                {
                    //lazy loading..
                    if (obj == null)
                    {
                        obj = new Singleton();
                    }
                    return obj;
                }
            }
        }

        public void doSomething()
        {
            var += 1;
            Console.WriteLine($"Current Value: {var}");
        }
    }
}

Now, when we call the Singleton class object using getObj from anywhere, it’ll return the same instance of obj.

using System;

namespace SingletonPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            Singleton instance = Singleton.getObj;
            instance.doSomething();
            instance.doSomething();
        }
    }
}

Output:

Current Value: 1
Current Value: 2

An example of a Singleton class can be a Database which returns only 1 instance of itself from wherever we connect to it.

How is it different from a Static class?

  1. A Singleton class can inherit other classes and can also implement interfaces.
  2. A Singleton can be initialized lazily or asynchronously and loaded automatically by the .NET Framework CLR when the program or namespace containing the class is loaded. While a static class is generally initialized when it is first loaded.

Abstract Factory pattern C#

Let’s begin with a story of 2 brothers Bill and Steve. Both love Pizza and Ice Cream. They go to a Food Court which has Pizza outlets like Pizza Hut and Dominos Pizza and Ice Cream Parlours like Baskin Robbins and Mikey.

Taking this real world pattern into our Software design by creating classes. In this context, Food Court will be our FoodFactory abstract class that creates 2 abstract Products Pizza and Ice Cream. The Food Court has 2 sections Fun and Frolic. Bill goes for Fun and Steve goes to Frolic.

public abstract class FoodFactory
{
    public abstract Pizza BakePizza();
    public abstract IceCream MakeIceCream();
}

Create an Abstract Products folder and add the following 2 classes:

public class Pizza {

}

public class IceCream {

}

Create a Products Folder and add the following classes:

public class DominosPizza: Pizza
{
    public DominosPizza()
    {
    }
}
public class PizzaHut: Pizza
{
    public PizzaHut()
    {
    }
}
public class Mikey: IceCream
{
    public Mikey()
    {
    }
}
public class BaskinRobbins: IceCream
{
    public BaskinRobbins()
    {
    }
}

Now, suppose Fun section has Pizza Hut and Mikey and Frolic has Dominos Pizza and Baskin Robbins. Create a folder Factories and under that add the following classes:

class FunFoodFactory : FoodFactory
{
    public override Pizza BakePizza()
    {
        return new PizzaHut();
    }

    public override IceCream MakeIceCream()
    {
        return new Mikey();
    }
}
public class FrolicFoodFactory : FoodFactory
{
    public override Pizza BakePizza()
    {
        return new DominosPizza();
    }

    public override IceCream MakeIceCream()
    {
        return new BaskinRobbins();
    }
}

Now the 2 Good brothers start having fun. Create a class called GoodBrothers as below:

public class GoodBrothers
{
    private readonly Pizza _pizza;
    private readonly IceCream _icecream;

    public GoodBrothers(FoodFactory factory)
    {
        _pizza = factory.BakePizza();
        _icecream = factory.MakeIceCream();
    }

    public string WhatDidYouHaveToday()
    {
        return $"Today I ate at {_pizza.GetType().Name} and {_icecream.GetType().Name}";
    }
}

From the main class we ask them What did you have today?

class Program
{
    static void Main(string[] args)
    {
        GoodBrothers Bill = new GoodBrothers(new FunFoodFactory());
        Console.WriteLine($"Bill: {Bill.WhatDidYouHaveToday()}");

        GoodBrothers Steve = new GoodBrothers(new FrolicFoodFactory());
        Console.WriteLine($"Steve: {Steve.WhatDidYouHaveToday()}");

        Console.ReadKey();
    }
}

Output:

Bill: Today I ate at PizzaHut and Mikey
Steve: Today I ate at DominosPizza and BaskinRobbins

A similar example for Abstract Factory pattern can be for a Clothes Factory abstract class which creates abstract Products like Shirts and Trousers. A Businessman may buy from ElegantFactory and a student may buy from CasualFactory. Shirts can be of types Formal and PoloTs and Trousers can be of type Suit and Jeans.

The ElegantFactory will return types Formal Shirt and Suit Trousers. CasualFactory will return types PoloTs and Jeans. We can create a Client class like GoodBrothers that receives the ClothesFactory reference in it’s constructor to create a Shirt and Trouser for the Businessman and Student and so on.

Run ReactJS App in Docker on Mac

This post explains how you can run your ReactJS App using Docker containers on your MacBook. Containers let your Application run in an environment isolated from the rest of your machine. It includes all the necessary files and resources to run your Application as is by creating an image that can be run anywhere as a container.

First you need to download and install the latest version of Docker for Desktop on your machine here. You can check whether your Mac has Intel chip or Apple chip from About This Mac menu option. Follow the installation steps and it should be available to open under Applications.

I’m using VS Code to create the basic ReactJS App. Open VS Code and install create-react-app command if not already installed using Terminal:

npm install -g create-react-app

Use sudo with above command if it gives permission denied error.

Switch to an empty folder and run the following command:

create-react-app reactondocker

Once the App is created, open the folder in VS Code and in the terminal install the required packages using and then build:

npm install
npm run-script build

Now, run the App as below to check it works normally on your machine without Docker:

npm start

Your Application should open fine in a browser. Url would like http://localhost:3000. We need to make sure this port is exposed from the container to your machine to run the Application without issues. This will be done in a Dockerfile.

From the Extensions menu, install Docker extension in your VS Code. Then, open the Command Palette under View menu, search for Dockerfile and select Add Dockerfiles to workspace -> Node.js -> package.json -> confirm port to be exposed, in this case it is 3000.

Dockerfile will look something like this:

FROM node:12.18-alpine
ENV NODE_ENV=production
WORKDIR /usr/src/app
COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
RUN npm install --production --silent
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

In short, this configuration is using a base alpine image (linux distribution) with node, sets your working directory to app. Copies the required files, installs packages and exposes the port 3000. The last line is running your Application in the container like you did from VS Code on your machine. You can optionally add a compose file for multi-container Application.

You can create a .dockerignore file to prevent items from getting copied to your image that you don’t want but it is optional.


/node_modules

/build

.git

*.md

.gitignore

Now, time to build the image which can be run as a container. Again, open the command palette and search for Docker Images: build image. It’ll ask for your Dockerfile, select it and observe the command it runs automatically in your VS Code Terminal as below:

docker build --rm --pull -f "/Users/username/Documents/Apps/ReactOnDocker/reactondocker/Dockerfile" --label "com.microsoft.created-by=visual-studio-code" -t "reactondocker:latest" "/Users/username/Documents/Apps/ReactOnDocker/reactondocker"

The above docker build command will create the image which can be found under the Docker menu on the left of your VS Code below extensions.

You can run this image using Docker for Desktop, the image named reactondocker will be available to run. Or right-click on the image name in VS Code and select run interactive. Notice the docker run command as below:

docker run --rm -it  -p 3000:3000/tcp reactondocker:latest

Now you can try running the Application using the same URL http://localhost:3000 this time running inside a container. You can see the running image in the Docker for Desktop as well.