Deploying a Node API to Docker & Kubernetes with a CRA Frontend

Creating an application is a lot of work! Deploying your application shouldn't be.

Every developer has said at one time or another, "it works locally, but I can't get it to work on the server!"; it can take you hours, or even days, to figure out what went wrong and to put a fix in place...

What would you say if I told you you could simply run the command docker run after creating your application and, boom have your app deployed, allowing the world to see your awesomeness!?

To make this magic happen, all you need to do is implement the container platform Docker, in conjunction with the container orchestration platform Kubernetes, and you'll be shipping like a pro! Using Docker/Kubernetes, you can deploy your code and sleep well at night, without the fear of something breaking because of incompatible libraries, commands, etc. (especially if you add a CI/CD pipeline)!

In this article, we’ll deploy a Node.js-based API for our chat application to Kubernetes and our React-based chat application frontend.

The best part about this little project is that we are going to use the Stream Chat API to build the chat application! With Stream, you can create fault-tolerant real-time applications quickly, with minimal effort and without bothering with the technicalities behind the real-time architecture/infrastructure, or even maintaining the infrastructure. What's more, you can plug Stream into your existing application or build on top of it; it’s easy to use and has nicely written documentation.

One of the best ways to learn is to practice, so... let’s get started!

Getting Started

Sign Up with Stream

The first step on our journey is to create an account on Stream. To do this, head on over to the Stream website and click on the SIGNUP button in the top right corner of the website:

Fill in your details and create your account, and then log in with your username and password. Alternatively, if you have a GitHub account, you can log in to Stream using the Single Sign-On option on the Sign In page!

Don't Forget Your Keys!

Once you create your account, head on over to your Dashboard by clicking the DASHBOARD button at the top right corner of the page:

Next, grab your API_KEY and API_SECRET from the Dashboard and stick them somewhere safe; we will be using both of them later on, in our application.

At this point, we have a Stream account and everything is looking good!

The Code!

Check out our Node.js-based API on Github; we'll use this as the basis for our app.

To prepare the API for deployment, let’s modify the generic API code to better fit our needs. We'll start by cloning the repo:

git clone https://github.com/ezesundayeze/stream-chat-api

Note: If you don’t have git installed, install it! (or just go to the repo and download the source code...)

Once you've got the repo locally, you can run:

cd stream-chat-api && yarn

(or "npm install", if you prefer npm over yarn)

Next, create a .env file and add the following:

NODE_ENV=development
PORT=8080

STREAM_API_KEY=YOUR_STREAM_API_KEY
STREAM_API_SECRET=YOUR_STREAM_API_SECRET

MONGODB_URI=YOUR_MONGODB_URI

Note: You can also reference the .env.example file at any time to make sure you've got the correct configuration.

You may have noticed that the code we just pasted into our .env file is missing a few values... To find your MONGODB_URI, register on the MongoDB Atlas website and create a cluster. The URI you seek should look something like this: "mongodb+srv://user:password@cluster0-ycunq.mongodb.net/db?retryWrites=true&w=majority"

Next, remember the YOUR_STREAM_API_KEY and YOUR_STREAM_API_SECRET we got from your Stream dashboard?? This is where you need it, for authentication!

Spin It Up!

At this point, you can run yarn start to start the development server for your new and improved API!

Our React App will use this API for authorization, but you can extend the API to do anything you desire!

To deploy to Kubernetes, we need to create a Docker image, which we’ll deploy to our Kubernetes cluster.

Installing Docker and Kubernetes

Before we get ahead of ourselves, let's install Docker and Kubernetes. For our experimental app, we'll only need the Minikube or Docker Desktop installation of Kubernetes; click through to their site to set this up. Once you've installed Kubernetes, head on over to Docker website to create an account there.

Putting Them In Action

For the purpose of this tutorial, we’ll use Minikube with VirtualBox as a hypervisor.

Install Minikube and VirtualBox by running the following commands (on MacOS):

brew install virtualbox
brew install minikube

Note: If you are on a different OS, you can check this link for the appropriate installation methods.

Once you've got Minikube and VirtualBox installed, run the command below to start Minikube:

minikube start

And run this to check that Minikube was started successfully:

minikube status

If all has gone well, the response you receive should look like this:

host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

So far, everything looks incredible; great work!

Now that our infrastructure is all set up, let’s prepare our application for deployment...

Deployment!

To kick off the deployment process, we'll need to create a "Dockerfile" and a Kubernetes "yaml" file. A "Dockerfile" is a text file that contains the commands you'll typically call on the terminal to create a Docker image. Similarly, a Kubernetes "yaml" file is a configuration file that defines all the resources that will be used in a Kubernetes cluster, like deployment, pods and services.

Start off by adding a Dockerfile to the root of the application you cloned and add the following content to your Dockerfile:

FROM mhart/alpine-node:latest
RUN apk update && apk upgrade
RUN apk add --no-cache make gcc g++ python git
WORKDIR /usr/src/api
COPY . /usr/src/api
RUN yarn --build-from-source install bcrypt
RUN yarn add bcrypt -f --build-from-source
EXPOSE 8080
CMD ["yarn", "start"]

In this file, we are pulling the latest alpine-node image from Docker Hub using the command "FROM mhart/alpine-node:latest". alpine-node can be thought of as our "base image", with all the configuration we need to run a Node.js application.

On the second line, we are updating our OS packages with the command
"RUN apk update && apk upgrade", and, on the third line, we're installing some system-level dependencies, to allow our application to work properly, using the command "RUN apk add --no-cache make gcc g++ python git".

Furthermore, on the next line, we create a "work directory" (where our application will live), using the command "WORKDIR /usr/src/api".

Finally, we copy all our files to the work directory, using the command "COPY . /usr/src/api". We use "." because we are in the directory where the files are (and, thus, don't need to navigate to another directory). With that said, you should be sure to insert the full, qualified file path of the location where your data is, if you are not in the directory where all your application data is.

Finally, "EXPOSE 8081" exposes your app to the outside world on that port (8081) and "CMD ["yarn", "start"]" is the reliable little command that starts up our application in our Docker image, in exactly the same way, each time we spin up a container from that image!

Building the Image

Let’s build the image and push it to our Docker Hub repository:

docker build -t {duckerhub-username}/stream-chat-app

Then, log in:

docker login

and push the image to Docker Hub:

docker push {dockerhub-username}/stream-chat-app

Setting Up Kubernetes for Deployment

We'll start by creating our Kubernetes .yaml file...

Create a directory called "kube" and add deployment.yaml and service.yaml files inside of it by running the command:

mkdir kube && touch service.yaml deployment.yaml

Next, add the following content to your service.yaml file:

apiVersion: v1
kind: Service
metadata:
 name: stream-chat-app
spec:
 selector:
 app: stream-chat-app
 ports:
 - port: 8000
 targetPort: 8000
 type: LoadBalancer

and in your deployment.yaml file add the following content:

apiVersion: apps/v1
kind: Deployment
metadata:
 name: stream-chat-app
spec: 
 replicas: 1
 selector:
 matchLabels:
 app: stream-chat-app
 template:
 metadata:
 labels:
 app: stream-chat-app
 spec: 
 containers:
 - name: app
 image: {dockerhub-username}/stream-chat-app
 ports:
 - containerPort: 8000
 imagePullPolicy: Always

Next, let’s use the kubectl utility (command line tool for controlling Kubernetes clusters) to access the Kubernetes API and apply the configurations:

kubectl -f apply kube/

Note: Ensure you have a strong internet connection while this is running, as Kubernetes will need to pull the image from Docker Hub in the above step.

The above process will apply the configuration and trigger Kubernetes runtime to start the services and deployments, as specified; it might take a while, but you can stay on top of things by running the following to see all your services:

kubectl get services

or this command to see the status of your pods:

kubectl get pods

Note: (Kubernetes pods are analogus containers in Docker)

To get even more details about what is going on, you can run:

kubectl describe deployment stream-chat-app

Finally, run:

minikube service stream-chat-app --url

to get the url and port for your pod.

The response should look like this:

http://192.168.93.104:31354

This is the url you can use to access your API.

Setting Up the React App

Next, lets set up the React application that will work with our API...

Start by cloning the React application:

git clone https://github.com/ezesundayeze/stream-react-app.git && cd stream-react-app

In your React application .env file add your API endpoint, for authentication with the Stream API REACT_APP_ENDPOINT='http://192.168.93.104:31354/v1/auth/init/' and your API KEY, from your Dashboard.

That’s all you need to do to set up your React application to work with the Stream API and your backend!

You can now run yarn and yarn start to run your application... (Woooo!)

Wrapping Up

Viola, we are done! Using Stream to develop a real-time application is pretty simple...

For example, using the Stream API, we only needed to add the following lines of code after installing Stream Chat API to get a functional chat application with all this goodness, and more:

<React.Fragment>
 <Chat client={client} theme={'messaging light'}>
 <Channel channel={channel}>
 <Window>
 <ChannelHeader />
 <MessageList />
 <MessageInput />
 </Window>
 <Thread />
 </Channel>
 </Chat>
</React.Fragment>

For more information on all the fun you can have, check out the Stream API docs. Using these, you can try adding attachment sharing, or read receipts (among many other things):

And yes, you can check out our completed chat application, here!

Happy Coding!