The author selected Girst Who Code to receive a donation as part of the Write for DOnations program.
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.
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.
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.
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.
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.
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.
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
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
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.
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!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.