Day 6 of 90 Days of DevOps: Dockerizing a Node.js App
Tags: docker, node, devops, containers
Published: 2024-06-25 19:37:29

Today, I tackled the process of Dockerizing a Node.js application, an essential skill in the DevOps toolkit.
 Dockerization allows for consistent environments across various stages of development, testing, and production, 
ensuring that applications run smoothly regardless of where they are deployed. However, this journey wasn't without its challenges!

#### Steps Taken

**1. Setting Up the Project Directory:**
First, I created a new directory for my Node.js application.

```sh
mkdir day6docker
cd day6docker
```

**2. Initializing the Node.js Project:**
Next, I initialized a new Node.js project and set up a basic Express server.

```sh
npm init -y
```

Created a `server.js` file:

```js
const express = require("express");
const path = require("path");
const axios = require("axios");
const morgan = require("morgan");

const app = express();

const morganMiddleware = morgan(
  ":method :url :status :res[content-length] - :response-time ms",
  {
    stream: {
      write: message => console.log(message.trim()),
    },
  }
);

app.use(morganMiddleware);

app.use(express.static(path.join(__dirname, "public")));

app.set("view engine", "pug");

async function getRandomJoke() {
  const response = await axios.get("https://api.chucknorris.io/jokes/random");

  return response.data;
}

app.get("/", async (req, res, next) => {
  try {
    const response = await getRandomJoke();

    res.render("home", {
      title: "Random Chuck Norris Jokes",
      joke: response,
    });
  } catch (err) {
    next(err);
  }
});

app.get("/joke", async (req, res, next) => {
  try {
    const response = await getRandomJoke();
    res.json(response);
  } catch (err) {
    next(err);
  }
});

app.use(function (err, req, res, next) {
  console.error(err);
  res.set("Content-Type", "text/html");
  res.status(500).send("<h1>Internal Server Error</h1>");
});

const server = app.listen(process.env.PORT || 4000, () => {
  console.log(`chucknorris server started on port: ${server.address().port}`);
});

function cleanupAndExit() {
  server.close(() => {
    console.log("chucknorris server closed");
    process.exit(0);
  });
}

process.on("SIGTERM", cleanupAndExit);
process.on("SIGINT", cleanupAndExit);

```

Installed Express:

```sh
npm install express
```

**3. Creating a Dockerfile:**
I then created a Dockerfile to define the image.

```Dockerfile
# Use the official Node.js image.
FROM node:14

# Create and change to the app directory.
WORKDIR /app

# Copy application dependency manifests to the container image.
COPY package*.json ./

# Install dependencies.
RUN npm install

# Copy local code to the container image.
COPY . .

# Run the web service on container startup.
CMD [ "node", "server.js" ]

# Inform Docker that the container listens on the specified network ports at runtime.
EXPOSE 3000
```

**4. Building the Docker Image:**
With the Dockerfile set up, I built the Docker image.

```sh
docker build -t my-node-app .
```

**5. Running the Docker Container:**
Finally, I ran the Docker container.

```sh
docker run -d -p 3000:3000 my-node-app
```

#### Challenges and Solutions

**Challenge 1: Dependency Issues**
Initially, I faced issues with my Node modules. My PowerShell commands weren't recognized properly. To resolve this, I used the following commands to clean up my environment and reinstall dependencies:

```powershell
Remove-Item -Recurse -Force .\node_modules
Remove-Item -Force .\package-lock.json
npm install
```

When faced with vulnerabilities, I used:

```powershell
npm audit fix
npm audit fix --force
```

**Challenge 2: Image Not Found and Authorization Errors**
When running the Docker container, I encountered errors indicating that the image wasn't found locally and pull access was denied.

To fix this, I either had to build the image from the Dockerfile or pull it from a repository:

**Option A: Build the Image**
```sh
docker build -t my-node-app .
```

**Option B: Pull the Image**
```sh
docker pull <username>/my-node-app:latest
```

**Challenge 3: Docker Container Issues**
On running the container, it initially failed to start. Checking the logs showed a missing module error. This was solved by ensuring all necessary files were included in the Docker context and correctly referenced.

#### Conclusion

Dockerizing an application, while challenging at times, is incredibly rewarding. It ensures that your application can run anywhere, from a local machine to a production server, with the same environment and dependencies. Mastering this skill is a significant step in the journey towards becoming a proficient DevOps engineer.

See you on Day 7 in a bit – it's not easy, but we move!