Containerization, particularly with Docker, has revolutionized the way we develop, test, and deploy software. Docker Compose, an essential companion to Docker, simplifies the process of managing multi-container applications. In this article, we embark on a journey into the world of containerization, focusing on containerizing a full-stack Node.js application using Docker Compose.
Whether you're a seasoned developer or just stepping into the world of containerization, this article will guide you through the process of Dockerizing a comprehensive application, from setting up your development environment to orchestrating multi-container setups effortlessly. By the end of this journey, you'll have a deep understanding of Docker Compose and the confidence to containerize your own full-stack Node.js applications, paving the way for efficient, scalable, and reliable software deployment. So, let's dive in and explore the transformative power of containerization in modern software development.
Prerequisite:
This article assumes that you have:
- Docker Desktop
- Node and Npm
- Basic knowledge of react
- Basic knowledge of tailwind css
To follow along, you will need the following already installed on your computer:
- Docker Desktop
- Node js
- And a code editor
You will need tailwind CSS to get the styles I have used in this tutorial. Visit the Tailwind CSS documentation to get Tailwind up and running.
Let’s create a folder for our project.
Open the command line on Windows and terminal on Mac, navigate to your desktop or your desired directory, and create a folder with
mkdir Docker_Compose
Inside this folder
navigate inside the folder you just created with
cd Docker_Compose
inside the Docker_Compose folder, create two folders named frontend and backend with
mkdir frontend
and mkdir backend
Getting our backend ready
navigate inside the backend folder with
cd backend
Initialize your express js backend package.json file with
npm init -y
The above command will create a package.json file in the root of your server and this file contains details about your project and its dependencies
Install Express.js using the following command.
npm install express
This will download and install the Express.js package for your project.
Before we get into creating our backend code, let’s create a script that starts up our server. In the root of our backend folder, open the package.json file and paste this command under scripts
"start": "node server.js",
Next, to avoid,cross-origin request issues from our backend, we’ll install cors npm package by running the following command
npm i cors
Open the Docker_Compose folder in your text editor and navigate to the backend folder you created.
Inside the backend folder, create a file called server.js, and paste the following code:
const express = require("express");
const cors = require('cors')
const app = express();
const port = 8000;
app.use(cors())
app.get("/", (req, res) => {
res.json([
{
id: 1,
joke: "Why don't scientists trust atoms? Because they make up everything!",
},
{ id: 2, joke: "I'm on a seafood diet. I see food and I eat it" },
{ id: 3, joke: "I used to be a baker because I kneaded dough" },
{
id: 4,
joke: "Why don’t some couples go to the gym? Because some relationships don’t work out!",
},
{
id: 5,
joke: "I'm reading a book on anti-gravity. It's impossible to put down!",
},
{
id: 6,
joke: "Parallel lines have so much in common. It’s a shame they’ll never meet",
},
{
id: 7,
joke: "Why did the scarecrow win an award? Because he was outstanding in his field!",
},
{
id: 8,
joke: "Why don't oysters donate to charity? Because they are shellfish!",
},
{ id: 9, joke: "I'm on a whiskey diet. I've lost three days already" },
{ id: 10, joke: "I used to play piano by ear, but now I use my hands" },
]);
});
app.listen(port, () => console.log(`Listening on port ${port}`));
Start your Express server using the following command:
npm start
The above command will run your server on port 8000 on localhost, which you can access on http://localhost:8000 in your browser.
Getting our frontend ready
On our front end, we will be using Vite to initialize a React application.
Navigate to the frontend folder and input the following command
npm create vite@latest
After you are done initializing the react app, delete the content of the App.js file and paste the following code
import { useState, useEffect } from "react";
function App() {
const [error, setError] = useState(false);
const [jokes, setJokes] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("http://localhost:8000");
if (response.ok) {
const result = await response.json();
setJokes(result);
setError(true);
} else {
console.error("Error fetching data:", response.statusText);
}
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, []);
return (
<>
<nav className='bg-red-50 py-4'>
<p className='font-light text-4xl text-red-400 px-20 '>jokesVille</p>
</nav>
<main className='flex justify-center items-center px-6 py-10 bg-red-100'>
{!error ? (
<div
className={`${
error ? "flex" : "hidden"
} h-full justify-center items-center text-center`}
>
<p className='text-xl font-medium'>
So Sorry we could not find you jokes
<span role='img' aria-label='dissapointed' className='text-4xl'>
😞
</span>
</p>
</div>
) : (
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 justify-center items-center gap-x-4 gap-y-3 text-center my-10 py-5'>
{jokes.map((joke) => (
<div
key={joke.id}
className=' h-40 flex flex-col justify-center items-center bg-gray-50 rounded-md py-16 px-20'
>
<span role='img' aria-label='laugh'>
😂
</span>
<p className='text-gray-500'>{joke.joke}</p>
</div>
))}
</div>
)}
</main>
</>
);
}
export default App;
Now fire up the jokesVille app frontend by running
npm run dev
There we have our app running nice and shiny in our browser
Frontend view in chrome
To reap the benefits of docker we will need to go beyond directly running our app on the browser but through docker containers.
Then comes Docker:
What is Docker?
Docker is an open-source platform that enables developers to automate the deployment of applications in lightweight, portable containers. These containers are a form of virtualization that packages an application and its dependencies together, creating a consistent and isolated environment for running software.
Why do we need docker?
1 Consistency Across Environments:
Developers often face the challenge of "it works on my machine" issues when deploying applications to different environments. Docker containers encapsulate applications and their dependencies, ensuring consistency across development, testing, and production environments. This consistency eliminates discrepancies between environments, leading to more reliable deployments.
2 Isolation and Resource Efficiency:
Docker containers offer both process and file system separation, enabling applications to operate autonomously from the host system. This separation guarantees that applications operate in their own space, preventing any interference and creating a stable, secure environment. Moreover, Docker's lightweight design demands fewer resources in comparison to conventional virtual machines, enhancing efficiency in terms of both memory and processing capabilities.
3 Microservices Architecture:
In contemporary software development, the microservices architecture is extensively utilized. Docker simplifies the creation and supervision of microservices by enabling each service to operate within its dedicated container. This method of development, which emphasizes modularity, improves scalability, fault tolerance, and simplifies maintenance.
What is Docker Compose?
Docker Compose is a utility designed to define and oversee complex Docker applications with multiple containers. It allows the comprehensive description of an application setup, encompassing services, networks, and storage allocations, within a unified docker-compose.yml file. This file serves as a plan for your application, outlining the construction, configuration, and interconnections of diverse containers.
To reap the above benefits, let’s get into running our app in Docker
To ensure our front-end displays in the browser when we run it in a container, go into vite.config file and replace the contents of the file with the following
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
base: "/",
plugins: [react()],
server: {
port:3000,
strictPort: true,
host: true,
},
});
You may choose to keep the default port: 5173, in my case I went with port: 3000. If you decide to go with a different port, do remember to change it accordingly in the frontend Dockerfile.
Containerizing our Application
To start, we have to create 2 docker files,1 in our frontend folder and 1 in our backend folder.
First, navigate into the frontend folder and create a file named Dockerfile, then paste the following command into the file:
FROM node:20.9.0-alpine3.18
WORKDIR /Docker_Compose/frontend
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Before we move on to creating our backend docker file, let’s take some time to explain the content of the above docker file and what they do:
FROM node:20.9.0-alpine3.18
This line defines the foundational image for the Docker container. It begins with a minimal Alpine Linux system (version 3.18) and then adds Node.js version 20.9.0 onto this base system.
WORKDIR /Docker_Compose/frontend
This sets the working directory within the container to /Docker_Compose/frontend
.
COPY packag.json ./
This line copies package.json
from the host machine to the current working directory in the container (/Docker_Compose/frontend
).
RUN npm install
`
This command installs the Node.js dependencies specified in package.json
COPY . .
This command copies all files from the directory on the host machine where the Dockerfile is located to the active working directory within the container. This encompasses various items such as the application code and extra configuration files.
EXPOSE 3000
This informs Docker that the container will listen on port 3000 at runtime.
CMD ["npm","run","dev"]
This specifies the default command to run when the container starts.
To create our backend docker file, navigate to the backend folder, create a file named Dockerfile, and paste in the following commands
docker
FROM node:20.9.0-alpine3.18
WORKDIR /Docker_Compose/backend
COPY package.json .
RUN npm install
COPY . .
EXPOSE 8000
CMD ["npm", "start"]
Next, navigate to the root folder (Docker_Compose) and create a file named docker-compose.yaml, then paste the following text
yaml
version: "3.8"
services:
frontend:
build: ./frontend
container_name: jokesVille_client
ports:
- "3000:3000"
backend:
build: ./backend
container_name: jokesVille_server
ports:
- "8000:8000"
In the root folder (Docker_Compose), and with Docker Desktop running, run the below command
docker-compose up --build
If you have been following the tutorial correctly, you should be greeted with a screen similar to this
Open http://localhost:8000/ and view the jokesville backend
Open http://localhost:3000/ on your browser and you’ll have our jokesville frontend nicely displayed and connected to the backend.
To shut down the docker containers, in the root folder run
docker-compose down
Conclusion
In this article, you've learned the process of containerizing your full-stack web application with Docker Compose. Initially, we set up a backend Express server with Node.js and developed a front-end application using React.js. Following these stages, Dockerfiles were generated for both applications, alongside a docker-compose.yaml file.
Using this docker-compose.yaml file, we built and launched the application containers.
Top comments (0)