Tutorial

Deploying an Express Application on a Kubernetes Cluster

Deploying an Express Application on a Kubernetes Cluster

The author selected Girst Who Code to receive a donation as part of the Write for DOnations program.

Introduction

Express.js is a minimal and flexible Node.js web application framework for building robust and scalable server-side applications. It is a favorite among developers for its ease of use and adaptability, which makes it easy to build APIs, websites, or complete stack applications.

The efficient and reliable deployment of applications has become a key pillar to success in an ever-evolving web development landscape. Traditional deployment methods may not meet the requirements of modern development as applications become more complex and scale. A flexible and reliable deployment solution is becoming more important as applications and user demands increase.

Kubernetes is an open-source container orchestration platform that has changed how applications are deployed, scaled, and managed. Kubernetes provides a broad range of tools to simplify the deployment process while ensuring excellent availability and reliability.

In this tutorial, you will build and test an example Express application locally on your development machine. Then, you will containerize your application with Docker, deploy it to a Kubernetes cluster, and create a load balancer that serves as a public access point to your application.

Prerequisites

Before you begin this tutorial, you will need the following:

  • A development server or local machine from which you will deploy the application. Although the instructions in this guide will largely work for most operating systems, this tutorial assumes that you have access to an Ubuntu 22.04 system configured with a non-root user with sudo privileges, as described in our Initial Server Setup for Ubuntu 22.04 tutorial.

  • The docker command-line tool installed on your development machine. To install this, follow Steps 1 and 2 of our tutorial on How to Install and Use Docker on Ubuntu 22.04.

  • The kubectl command-line tool installed on your development machine. To install this, follow this guide from the official Kubernetes documentation.

  • A free account on Docker Hub to which you will push your Docker image. To set this up, visit the Docker Hub website, click the Get Started button at the top-right of the page, and follow the registration instructions.

  • A Kubernetes cluster. You can provision a DigitalOcean Kubernetes cluster by following our Kubernetes Quickstart guide. Wherever you procure your cluster, set up a configuration file and ensure you can connect to the cluster from your development server.

Step 1 - Setting Up a Sample Express Application

In this step, you will build a sample Express application using Node.js. Once you dockerize this app with Docker, it will serve the Express Hello World Application in response to requests to your server’s IP address at port 8080.

Get started by updating your server’s package lists if you haven’t done so recently:

$ sudo apt update

Then, download the Node.js GPG key to the /etc/apt/keyrings directory.

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg

Next, add the NodeSource repo to the APT source list.

$ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list

After that, update the repository index and install the Node.js by running the below commands on your terminal/command line.

sudo apt update -y
sudo apt install -y nodejs

Next, make sure you’re in your home directory and create a new directory which will contain all of your project files:

$ cd && mkdir express-app

Then navigate to this new directory:

$ cd express-app

Use nano or your preferred text editor to create a file named package.json to define metadata about the project and its dependencies:

$ nano package.json

Add the following lines:

{
  "name": "simple-express-app",
  "version": "1.0.0",
  "description": "Express Hello World Application",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "4.17.1"
  }
}

A brief explanation of the above code is shown below:

  • name: “simple-express-app” - This field specifies the name of the Node.js application. In this case, it’s named “simple-express-app.”

  • version: “1.0.0” - This field indicates the version number of the application.

  • description: “Express Hello World Application” - This field provides a brief description of the application. In this case, it’s described as an “Express Hello World Application.”

  • main: “index.js” - The main field specifies the entry point of the application, i.e., the main file that will be executed when the application is started. In this example, the entry point is set to “index.js.”

  • scripts: - This section defines custom scripts that can be executed using npm. In this case, there is a script named “start” defined under the “scripts” section.

  • “start”: “node index.js”: - This script tells npm to execute the command node index.js when the npm start command is run. It essentially starts the application by running the “index.js” file.

  • dependencies: - This section lists the external packages (dependencies) that the project relies on.

  • “express”: “4.17.1”: - This specifies that the application depends on Express.js version 4.17.1. Express.js is a popular web application framework for Node.js.

Save and close this file. If you created this file using nano, press CTRL + X, Y, then ENTER.

Next, create a file named index.js, which will contain the code for your Express application:

$ nano index.js

Add the following code:

'use strict';

const express = require('express');

const PORT = process.env.PORT || 8080;

const app = express();

app.get('/', function(req, res) {

    res.send('Express Hello World Application!\n');

});

app.listen(PORT);

console.log('Running on http://localhost:' + PORT);

A brief explanation of the above code is shown below:

  • ‘use strict’; - This statement activates strict mode in JavaScript. Strict mode helps catch common coding errors and prevents the use of certain error-prone features. It is a good practice to use strict mode in modern JavaScript applications.

  • const express = require(‘express’); - This line imports the Express.js framework into the application. The required function is a common Node.js function used to import modules.

  • const PORT = process.env.PORT || 8080; - This line sets up the port on which the Express.js application will listen. It uses the process.env.PORT environment variable if available, which is commonly used in cloud environments. If the environment variable is not set, it defaults to port 8080.

  • const app = express(); - This line creates an instance of the Express application. The express() function is used to create an Express application object, which can then be used to define routes and handle HTTP requests.

  • app.get(‘/’, function (req, res) { res.send(‘Express Hello World Application!\n’); }); - This block defines a route for handling HTTP GET requests to the root path (‘/’). When a user accesses the root path of the application, the provided callback function is executed. In this case, it sends the string “Express Hello World Application!\n” as the response.

  • app.listen(PORT); - This line starts the Express.js application and makes it listen for incoming requests on the specified port (PORT). The application will keep running and handle incoming HTTP requests until it is manually terminated.

  • console.log(‘Running on http://localhost:’ + PORT); - This line prints a message to the console indicating that the server is running. It includes the URL (http://localhost:) and the port on which the server is listening.

Save and close this file.

Next, install all required dependencies by running:

$ npm install

Next, run the application using the following command:

$ npm start

Output.

simple-express-app@1.0.0 start
node index.js
Running on http://localhost:8080

This output confirms that the application is working as expected. It will run indefinitely, however, so close it by pressing CTRL + C.

Step 2 - Dockerizing Your Express Application

Your Express application has been created and runs only on your development server. Now, you will port this application using Docker. So that it can run on any machine that supports Docker containers.

The first step to dockerizing your Express application is to create a Dockerfile. A Dockerfile is a script that specifies a series of commands and settings that define how the Docker image should be constructed.

Let’s create a new file named Dockerfile:

$ nano Dockerfile

Add the following configuration:

FROM node:18-alpine as builder
ENV NODE_ENV="production"

Copy the app’s source code to the /app directory

COPY . /app

The application’s directory will be the working directory

WORKDIR /app

Install Node.js dependencies defined in ‘/app/package.json’ :

RUN npm install
FROM node:18-alpine
ENV NODE_ENV="production"
COPY --from=builder /app /app
WORKDIR /app
ENV PORT 8080
EXPOSE 8080

Start the application:

CMD ["npm", "start"]

A brief explanation of the above configuration is shown below:

  • FROM node:18-alpine as builder - This sets the base image for the first stage of the build process. It uses the official Node.js 18 image based on Alpine Linux as the starting point.

  • ENV NODE_ENV=“production” - This sets the Node environment variable to “production.”

  • COPY . /app - This copies the application’s source code from the local directory (where the Dockerfile is located) to the /app directory inside the image. WORKDIR /app - This sets the working directory for the subsequent commands to /app.

  • RUN npm install - This installs the Node.js dependencies specified in the package.json file. This step is crucial for preparing the application to run in a production environment.

  • FROM node:18-alpine - This sets the base image for the second stage, which is a lightweight Node.js 18 image based on Alpine Linux. This stage will be the final image.

  • COPY --from=builder /app /app - This copies the entire /app directory from the builder stage to the /app directory in the final image. This ensures that only the necessary artifacts from the builder stage are included in the final image.

  • WORKDIR /app - Sets the working directory to /app.

  • ENV PORT 8080 - Sets the environment variable PORT to 8080.

  • EXPOSE 8080 - Informs Docker that the application inside the container will use port 8080. This is more of a documentation feature, and it doesn’t actually publish the port.

  • CMD [“npm”, “start”] - Specifies the command that will be executed when a container is run from this image. In this case, it starts the application using the npm start command.

Save and close the file after adding these lines.

At this point, you have this Dockerfile at the root of your project. Next, you will create a Docker image using the docker build command. This command includes the -t flag, which, when passed the value express-app, will name the Docker image express-app and tag it.

The final argument you’ll pass is the path: .. This specifies that you wish to build the Docker image from the contents of the current working directory. Also, be sure to update sammy to your Docker Hub username:

$ docker build -t sammy/express-app .

Output.

Successfully built 131274ef59g1
Successfully tagged express-app:latest

Next, run the docker images command to see your newly created image.

$ docker images

Output.

REPOSITORY              TAG         IMAGE ID       CREATED         SIZE
sammy/express-app   latest      780373988b65   3 minutes ago   179MB

Next, create and run a container using the image you just built.

$ docker run -it --name express-container -p 8080:8080 sammy/express-app

You can now see the application in action by opening up a browser and navigating to the following URL:

http://your_server_ip:8080

After verifying your application in your browser, stop it by pressing CTRL + C in your terminal.

Step 3 - Pushing Docker Image to Docker Hub Registry

Next, you must push your Express application Docker image to your Docker Hub registry so that you can pull the image from a centralized location.

Run the following command to log in to the Docker Hub image repository.

$ docker login

You will be asked to enter your Docker Hub username and password. After entering them correctly, you will see Login Succeeded in the command’s output.

Login Succeeded

After the successful login, push your new image to the Docker Hub using the docker push command:

$ docker push sammy/express-app:latest

After the successful command execution, you will be able to see your Docker image in your Docker Hub registry account.

At this point, you have pushed your Express application to the Docker Hub registry. You are now ready to deploy your application to the Kubernetes cluster.

Step 4 - Configuring kubectl

Before deploying your application to the Kubernetes cluster, you will need to download the Kubernetes configuration file and configure Kubectl to use it. By default, when you run commands with the kubectl command-line tool, you have to specify the path of the Kubernetes cluster configuration file using the –kubeconfig flag. In this case, you will need to download and store your Kubernetes configuration file at ~/.kube/config so you can run the kubectl command without the –kubeconfig flag pointing to it.

First, create a new directory called ~/.kube:

$ mkdir ~/.kube

Then move your cluster configuration file to this directory and rename it config in the process:

$ mv clusterconfig.yaml ~/.kube/config

Now, run the kubectl command to verify Kubernetes cluster connectivity.

$ kubectl get nodes

This command will display all the nodes residing within your Kubernetes cluster.

NAME                           STATUS   ROLES    AGE   VERSION
express-cluster-02ffa7d2b32a   Ready    <none>   25m   v1.28.3
express-cluster-537ffda09c7d   Ready    <none>   25m   v1.28.3
express-cluster-cfc99499b42b   Ready    <none>   25m   v1.28.3

After configuring the kubectl, you are now ready to deploy your application to the Kubernetes cluster.

Step 5 - Creating Kubernetes Deployment

In this step, you’ll create a Kubernetes manifest file for a deployment. This manifest file will contain all of the configuration details needed to deploy your Express app to your cluster.

Create a new file called deployment.yaml in the root directory of your project: express-app/.

$ nano deployment.yaml

Add the following configuration.

kind: Deployment
apiVersion: apps/v1
metadata:
  name: express-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: express-app
  template:
    metadata:
      labels:
        app: express-app
    spec:
      containers:
        - name: express-app
          image: 'sammy/express-app:latest'
          ports:
            - containerPort: 80

A brief explanation of the above file is shown below:

  • kind and apiVersion: Specifies the Kubernetes resource type and API version. In this case, it’s a Deployment from the apps/v1 API.

  • metadata: Contains metadata about the Deployment, including the name.

  • spec: Describes the desired state of the Deployment.

  • replicas: 3: Specifies that the desired number of replicas (pods) for this Deployment is 3. Kubernetes will ensure that three pods are running at all times.

  • selector: Defines how the Deployment identifies which pods it manages.

  • matchLabels:: Specifies that the pods managed by this Deployment should have the label app: express-app.

  • template: Describes the pod template, which serves as a blueprint for creating new pods controlled by the Deployment.

  • metadata: Specifies labels for pods created from this template. In this case, the label is app: express-app.

  • spec: Describes the specification of the pod.

  • containers: Specifies the containers that should run in the pod.

  • name: express-app: Sets the container’s name to “express-app.”

  • image: sammy/express-app:latest: Specifies the Docker image (sammy/express-app:latest) for the container. This image likely contains an Express.js application.

  • ports: Specifies the ports to open in the container.

  • containerPort: 80: Sets the container port to 80. This indicates that the application inside the container is listening on port 80.

Save and close this file.

Next, apply your new deployment with the following command:

$ kubectl apply -f deployment.yaml

Next, verify your deployment using the following command:

$ kubectl get deployment

Output.

NAME          READY   UP-TO-DATE   AVAILABLE   AGE
express-app   3/3     3            3           12s

Step 6 - Creating Kubernetes Service

After the successful Kubernetes deployment, you will need to expose your application to the outside world. In this case, you will need to create a Kubernetes service file. This file will expose the application port on all of your cluster’s nodes. Your nodes will then forward any incoming traffic on that port to the pods running your application.

Let’s create a new file called service.yaml:

$ nano service.yaml

Add the following configurations:

apiVersion: v1
kind: Service
metadata:
  name: load-balancer
  labels:
    app: express-app
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: express-app

A brief explanation of the above service configuration file is shown below:

  • apiVersion and kind: Specifies the Kubernetes resource type and API version. In this case, it’s a Service from the v1 API.

  • metadata: Contains metadata about the Service, including the name.

  • labels:: Assigns labels to the Service. The label app: express-app is assigned, which aligns with the labels used in the associated pods.

  • spec: Describes the desired state of the Service.

  • type: LoadBalancer: Specifies that the Service should be of type LoadBalancer. This is typically used when running in cloud environments to provision an external load balancer that directs traffic to the service.

  • ports:: Specifies an array of port mappings.

  • port: 80: Defines that the Service should be accessible on port 80 externally.

  • targetPort: 8080: Specifies that the Service should forward incoming traffic on port 80 to the pods’ port 8080. This is the port where the application inside the pods is listening. selector:: Specifies a set of labels used to identify the pods to which the Service will forward traffic.

  • app: express-app: This selector indicates that the Service should forward traffic to pods with the label app: express-app. This aligns with the label used in the associated Deployment.

Save and close this file.

Next, apply this service configuration to your Kubernetes cluster:

$ kubectl apply -f service.yaml

This command will create a new load balancer and serve as the public-facing entry point for your Express application running within the cluster.

To access your application, you must find the new load balancer’s IP address. You can find it by running the following command:

$ kubectl get svc

Output.

NAME            TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)        AGE
kubernetes      ClusterIP      10.96.0.1       <none>         443/TCP        32m
load-balancer   LoadBalancer   10.105.40.232   203.0.113.21   80:32244/TCP   2m55s

From the above output, find the EXTERNAL-IP column and copy the IP address associated with the load balancer. In this example output, this IP address is 203.0.113.21. Then, paste the IP address into your browser’s URL bar to view the application running on your Kubernetes cluster.

http://203.0.113.21

Conclusion

In this tutorial, you have created an Express application, containerized it with Docker, and then deployed it to a Kubernetes cluster. You have also created a load balancer to expose your application to the outside world. You can now use this tutorial to deploy your Express application to a Kubernetes cluster.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
hitjethva

author


Default avatar

Sr Technical Writer

Sr. Technical Writer@ DigitalOcean | Medium Top Writers(AI & ChatGPT) | 2M+ monthly views & 34K Subscribers | Ex Cloud Consultant @ AMEX | Ex SRE(DevOps) @ NUTANIX


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more