This project is a Dockerized Node.js application that uses Redis for managing visit counts. It demonstrates the integration of Node.js with Redis within a Docker environment, showcasing best practices in containerization and application design.
- Node.js Backend: A lightweight and efficient server using Express.js.
- Redis Integration: Utilizes Redis as a database to store visit counts.
- Docker Support: Containerized with Docker for easy deployment and scaling.
- TypeScript Support: Leveraging TypeScript for improved code reliability and maintainability.
These instructions will get your copy of the project up and running on your local machine for development and testing purposes.
-
Clone the repository
git clone <https://github.com/nawodyaishan/docker-nodejs.git> cd docker-nodejs
-
Build the Docker image
docker build -t docker-nodejs .
-
Run the Docker container
docker run -p 3000:3000 docker-nodejs
This will start the application and expose it on
http://localhost:3000
.
Dockerfile
: Contains the Docker configuration for building the image.package.json
: Lists the project dependencies and scripts.pnpm-lock.yaml
: Lock file to ensure consistent installation of dependencies.src/index.ts
: The main server file written in TypeScript.tsconfig.json
: TypeScript compiler configuration.
# Use a specific version of Node.js on Alpine Linux as the base image
FROM node:18-alpine3.17
# Set environment variables for PNPM's installation location and update PATH
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
# Enable Corepack for managing package managers like pnpm
RUN corepack enable
# Set the working directory in the container
WORKDIR '/app'
# Copy package.json and pnpm-lock.yaml
COPY package.json pnpm-lock.yaml ./
# Install project dependencies using pnpm
RUN pnpm install
# Copy the rest of the application's code
COPY . .
# Command to run the application
CMD ["pnpm","start"]
- Ensure you have Docker installed on your machine.
-
Pull the Redis Image
First, you need to pull the official Redis image from Docker Hub. Open a terminal or command prompt and run the following command:
docker pull redis
This command downloads the latest Redis image to your local machine.
-
Run the Redis Container
After pulling the image, you can start a Redis container. Run the following command:
docker run --name my-redis -p 6379:6379 -d redis
Here’s what each part of this command does:
-name my-redis
: Assigns the namemy-redis
to your Docker container.p 6379:6379
: Maps port 6379 of the container to port 6379 on your host machine. Redis by default runs on port 6379.d
: Runs the container in detached mode, meaning the container runs in the background.redis
: Specifies the Redis image to use.
After running this command, Redis will be up and running in a Docker container.
-
Verify Redis Container is Running
You can check if your Redis container is running by executing:
docker ps
This will list all active containers. Look for the
my-redis
container in the list. -
Stopping the Redis Container
When you're done, you can stop the Redis container by running:
docker stop my-redis
-
Restarting the Redis Container
To restart the container, use:
docker start my-redis
The docker-compose.yml
defines a multi-container Docker application with two services: a Redis server and a Node.js
application. Below is a detailed explanation of the configuration file and how to use it.
version: '3' # Specifies the version of the Docker Compose file format
services: # Defines the services that make up your application
my-redis: # Name of the first service
image: 'redis' # Specifies the Redis image from Docker Hub
node-app: # Name of the second service
build: . # Builds an image using the Dockerfile in the current directory
ports:
- "4001:4001" # Maps port 4001 inside the container to port 4001 on the host
- Redis Server:
- The service
redis-server
uses the officialredis
image from Docker Hub. - No ports are exposed to the host in this configuration, meaning Redis will only be accessible to other services
within the same Docker network (like your
node-app
service).
- The service
- Node Application:
- The
node-app
service is built using theDockerfile
in the current directory (specified bybuild: .
). - The application inside the container listening on port
8081
will be accessible on port4001
of your host machine.
- The
To use this docker-compose.yml
file, follow these steps:
-
Navigate to the Project Directory:
Ensure you are in the directory where your
docker-compose.yml
file and theDockerfile
for your Node.js application are located. -
Running Docker Compose:
Run the following command to build and start your services:
docker-compose up --build
This command builds the Node.js application image (as per your Dockerfile), starts the Redis service, and starts the Node.js application service.
- Building Service Images: Docker Compose looks at the
services
defined in yourdocker-compose.yml
file. For services that specify a build context (using thebuild
key), Docker Compose uses the correspondingDockerfile
to build a Docker image for each of these services. - The
-build
Flag: This flag forces Docker Compose to build (or rebuild) the images for the services, even if an image already exists. It's useful when you have made changes to a service or its Dockerfile and want to ensure that the latest version is used.
- Building Service Images: Docker Compose looks at the
-
Accessing the Application:
Your Node.js application should now be accessible at
http://localhost:4001
. -
Shutting Down:
To stop and remove the containers, networks, and volumes created by
up
, run:docker-compose down
- Data Persistence for Redis: If you need to persist Redis data, consider defining a volume for the Redis service in
your
docker-compose.yml
. - Environment Variables: If your Node.js application requires environment variables, you can define them in
the
docker-compose.yml
file under thenode-app
service using theenvironment
key or using an.env
file. - Custom Redis Configuration: To use a custom Redis configuration, you can mount a configuration file from your host to the Redis container.
Using Docker Compose simplifies the management of multi-container Docker applications, making it easy to start, stop, and rebuild services in a coordinated manner.
- Automatic Network Creation:
- When you run
docker-compose up
, Docker Compose automatically creates a network for your services. By default, this network allows communication between the containers.
- When you run
- Service Name as Hostname:
- In your
docker-compose.yml
, the services are namedredis-server
andnode-app
. These service names can be used as hostnames within the network. For example, your Node.js app can access the Redis server using the hostnameredis-server
.
- In your
In your Node.js application, when configuring the Redis client, you should use redis-server
as the hostname to connect
to the Redis server. Here's an example based on your provided index.ts
file:
// ... other imports
import {createClient} from 'redis';
// Create a Redis client using the service name as the hostname
const client: RedisClientType = createClient({
url: 'redis://my-redis:6379'
});
// ... rest of your code
redis://redis-server:6379
: This tells the Redis client to connect to the Redis server at the hostnameredis-server
, which is the name of your Redis service indocker-compose.yml
, on the default Redis port 6379.
- Isolation: Each service runs in its own container, providing an isolated environment.
- Scalability: Services can be scaled independently.
- Service Discovery: Docker Compose network handles service discovery automatically, allowing services to communicate by their names.
Running the command docker-compose up --build
initiates a multi-step process involving both building Docker images for
your services (if necessary) and then starting those services as defined in your docker-compose.yml
file. Let's break
down what happens when you execute this command:
- Building Service Images:
- Docker Compose looks at the services defined in your
docker-compose.yml
file. - For services that require building an image (like your
node-app
service which has abuild: .
directive), Docker Compose uses the correspondingDockerfile
in the specified context (in your case, the current directory.
) to build a Docker image. - The
-build
flag forces Docker Compose to build (or rebuild) the images for the services even if an image already exists. This ensures that any changes in the service’s Dockerfile or in the service’s build context are included.
- Docker Compose looks at the services defined in your
- Creating Redis Server Container:
- For the
redis-server
service, since it uses an image directly (image: 'redis'
), Docker Compose pulls the Redis image from Docker Hub if it's not already available locally. - Docker Compose then creates a new container instance based on the Redis image.
- For the
- Creating Node App Container:
- For the
node-app
service, Docker Compose uses the image built in the previous step. - A new container instance is created based on this image.
- For the
- Automatic Network Creation:
- Docker Compose sets up a default network for the composed application.
- Each container is attached to this network, and each service can communicate with other services using the service
names as hostnames (e.g.,
redis-server
for the Redis service).
- Port Mapping:
- For your
node-app
service, Docker Compose maps the port8081
inside the container to port4001
on your host machine. This makes your Node.js application accessible viahttp://localhost:4001
.
- For your
- Starting Services:
- Both the
redis-server
andnode-app
containers are started. - If there are any dependencies between the services (not in your case), Docker Compose starts the services in dependency order.
- Both the
- Service Logs:
- By default, Docker Compose streams the output of all the containers to the terminal. This includes logs that would normally output to stdout and stderr.
- Detached Mode (Optional):
- If you want to run the services in the background, you can add the
d
flag (docker-compose up --build -d
). This will start the containers in detached mode, and you will get the command prompt back.
- If you want to run the services in the background, you can add the
Docker provides several restart policies to control the behavior of containers in different situations:
no
: This is the default restart policy. The container will not be restarted if it stops or crashes.always
: The container will always be restarted if it stops. If the container is manually stopped, it is restarted only when the Docker daemon restarts or the container itself is manually restarted.unless-stopped
: The container will always be restarted unless it is explicitly stopped. Even if the Docker daemon restarts, the container will be restarted unless it was manually stopped before.on-failure
: The container will be restarted only if it exits with a non-zero exit status (indicative of an error). Optionally, you can specify a maximum number of retries before giving up.
To implement restart policies, you specify them when you run a container using the docker run
command, or you can
define them in a docker-compose.yml
file for Docker Compose-managed containers.
When you start a container with docker run
, use the --restart
flag to set the restart policy. Here are some
examples:
-
No Restart Policy:
docker run --restart=no my-image
-
Always Restart Policy:
docker run --restart=always my-image
-
Restart on Failure with Maximum Retries:
docker run --restart=on-failure:5 my-image
In a docker-compose.yml
file, you can specify the restart policy for each service. Here's an example of how to set
different restart policies:
version: '3'
services:
webapp:
image: my-webapp
restart: always
redis:
image: redis
restart: on-failure
mysql:
image: mysql
restart: unless-stopped
- Use Wisely: Restart policies are helpful for ensuring high availability, but they should be used wisely. Automatically restarting a container that is failing due to a configuration error or a fundamental issue can lead to a restart loop.
- Logging and Monitoring: Implement logging and monitoring to keep track of container restarts and diagnose any issues leading to repeated restarts.
- Health Checks: Combine restart policies with health checks for more robustness. Docker can check the health of your application and restart the container if it becomes unhealthy.
- Docker Compose in Production: When using Docker Compose in production, ensure that your restart policies are correctly set to handle unexpected failures.
Restart policies are a key feature in Docker for managing container lifecycles, especially in production environments where reliability is critical. By understanding and implementing these policies correctly, you can significantly improve the resilience of your Dockerized applications.
- Node.js Application (Service:
node-app
):- Function: Serves as the web server, handling HTTP requests and responses.
- Technology: Built using Express.js, a popular framework for Node.js.
- Primary Role: Manages the business logic, which in this case, is tracking the number of visits to the homepage.
- Redis Interaction: Connects to the Redis server to get and set the visit count.
- Port Exposure: The service is exposed on port 4001, making it accessible via
http://localhost:4001
.
- Redis Server (Service:
my-redis
):- Function: Acts as a data store for the application.
- Technology: An in-memory data structure store, used as a database, cache, and message broker.
- Role: Stores the number of visits as a key-value pair.
- Persistence: By default, Redis data is ephemeral. To persist data, you'd need to configure Redis with a volume in Docker.
- Internal Network:
- Created and managed by Docker Compose.
- Both the Node.js application and Redis server are attached to this network.
- This setup allows the Node.js application to communicate with the Redis server using the service name
my-redis
as the hostname.
- HTTP Request Handling:
- A user sends an HTTP request to the Node.js application by accessing
http://localhost:4001
. - The Express server in the Node.js application receives the request.
- A user sends an HTTP request to the Node.js application by accessing
- Retrieving and Updating Visits:
- The Node.js application queries the Redis server to retrieve the current number of visits.
- The Redis server responds with the visit count.
- The Node.js application increments this number and sends it back to Redis for updating.
- Response to the User:
- The Node.js application then sends a response back to the user with the number of visits.
- Defined in a
docker-compose.yml
file. - Specifies the services (
node-app
andmy-redis
), their configurations, and how they are networked together. - Ensures that both services can be started, stopped, and managed together, providing a cohesive environment for the application.
- Scalability: The application can be scaled by increasing the number of Node.js instances, assuming the Redis server can handle the increased load.
- Reliability: Implemented through Docker's restart policies. In case of failures, containers can be automatically restarted based on the policy.
- Network Security: Communication between the Node.js application and Redis server is internal and not exposed to the external network.
- Data Security: Redis data security needs consideration, especially if sensitive data is stored.
- Container Security: Regular updates to the Docker images and secure configuration are necessary to mitigate security vulnerabilities.
The architecture provides a solid foundation for a web application with a Redis backend. It leverages Docker and Docker Compose for containerization and orchestration, ensuring ease of deployment and scalability. This setup is ideal for applications requiring high-performance data interactions, with the flexibility to adapt to increasing loads and complexity.