How to run Express.js app with docker following simple steps?

Prerequisites

1. Create a simple Express.js app

Initiate NPM:

mkdir node-docker-example && cd node-docker-example
npm init -y

Install Express, Morgan and Esm packages. Morgan is a request logger library and esm will let you use es6 syntax while including modules.

yarn add express morgan esm

Create files:

mkdir src
touch src/index.html src/server.js

Create an Express server:

// Server.js
import express from "express"
import path from "path"
import morgan from "morgan"

const PORT = 8082
const HOST = "0.0.0.0"

// App
const app = express()

// Middlewares
app.use(morgan("combined"))

// Routes
app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname + "/index.html"))
})

app.listen(PORT, HOST)
console.log(`Running on http://${HOST}:${PORT}`)

The server will listen to requests on 8082 port and it will return index.html for GET http://0.0.0.0:8082 request.

Create index.html file. I've included Bulma library here to make it look a bit more interesting.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css"
    />
  </head>
  <body>
    <div class="container">
      <section class="section">
        <section class="hero is-info">
          <div class="hero-body">
            <div class="container">
              <h1 class="title">
                Hello ExpressJS šŸ„³šŸš€šŸš€šŸš€
              </h1>
              <h2 class="subtitle">
                with Docker
              </h2>
            </div>
          </div>
        </section>
      </section>
      <div class="tile is-ancestor">
        <div class="tile is-4 is-vertical is-parent">
          <div class="tile is-child box">
            <p class="title">One</p>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin
              ornare magna eros, eu pellentesque tortor vestibulum ut. Maecenas
              non massa sem. Etiam finibus odio quis feugiat facilisis.
            </p>
          </div>
          <div class="tile is-child box">
            <p class="title">Two</p>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin
              ornare magna eros, eu pellentesque tortor vestibulum ut. Maecenas
              non massa sem. Etiam finibus odio quis feugiat facilisis.
            </p>
          </div>
        </div>
        <div class="tile is-parent">
          <div class="tile is-child box">
            <p class="title">Three</p>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam
              semper diam at erat pulvinar, at pulvinar felis blandit.
              Vestibulum volutpat tellus diam, consequat gravida libero rhoncus
              ut. Morbi maximus, leo sit amet vehicula eleifend, nunc dui porta
              orci, quis semper odio felis ut quam.
            </p>
            <p>
              Suspendisse varius ligula in molestie lacinia. Maecenas varius
              eget ligula a sagittis. Pellentesque interdum, nisl nec interdum
              maximus, augue diam porttitor lorem, et sollicitudin felis neque
              sit amet erat. Maecenas imperdiet felis nisi, fringilla luctus
              felis hendrerit sit amet. Aenean vitae gravida diam, finibus
              dignissim turpis. Sed eget varius ligula, at volutpat tortor.
            </p>
            <p>
              Integer sollicitudin, tortor a mattis commodo, velit urna rhoncus
              erat, vitae congue lectus dolor consequat libero. Donec leo
              ligula, maximus et pellentesque sed, gravida a metus. Cras
              ullamcorper a nunc ac porta. Aliquam ut aliquet lacus, quis
              faucibus libero. Quisque non semper leo.
            </p>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

Let's confirm, everything works fine.

Add "start": "node -r esm ./src/server.js" to package.json and run npm start.

~/punched-card-examples/01-express-docker-example į… yarn start
yarn run v1.16.0
$ node -r esm ./src/server.js
Running on http://0.0.0.0:8082

Go to http://0.0.0.0:8082 on your browser to check it out.

express app on browser

2. Setup Docker

Create a file named Dockerfile. Dockerfile will contains instructions to run to create a docker image. The image is like a blueprint of application. It will be used the create a docker container.

Docker Hub has images many images for open source projects. So the first step is to pull existing node 12 alpine image. After that, you set the working directory to app but you can name this anything you like.

Next step is copying package.json file across. This is quick and it will allow you to install packages in the image. Copy the rest of the code into the image with COPY . .. And lastly, set yarn start as a command to run when container boots up.

FROM node:12.16.1-alpine3.11

WORKDIR /app

COPY package.json ./

RUN yarn install

COPY . .

CMD [ "yarn", "start" ]

Build the image:

docker build -t node-docker-example .

Run the container:

docker run -d -p 8082:8082 node-docker-example

This command will start the container and bind the container port 8082 to host port 8082. You can go to the browser and type http://localhost:8082/ but this time you are seeing application run in docker.

Check running docker containers:

docker ps
CONTAINER ID  IMAGE                COMMAND                 CREATED       STATUS        PORTS                   NAMES
a39d1c3f4323  node-docker-example  "docker-entrypoint.sā€¦"  2 minutes ago Up 2 minutes  0.0.0.0:8082->8082/tcp  angry_beaver

To stop container use the printed container ID:

docker stop <container-id>

3. Add watch tasks for the applications

While you are developing your code, stopping and restarting the server is tedious. Instead we will add Nodemon to watch your files and do this automatically.

Install nodemon:

yarn add nodemon -D

Add watch task to package.json

 "watch": "nodemon -r esm ./src/server.js"

This task will let you watch changes when you are running application in local. But you can also make Docker use it. Watching for file changes are only needed in the development environment. When you run your Docker container in the server, you don't need to run it with watch task. In fact, it will make it less performant. So it makes more sense to add watch task in docker compose rather than dockerfile.

Docker compose allows you to run multiple containers. This is usually only used in the development environment. Cloud providers such as AWS provide their own container orchestration tools such as ECS.

Create a docker-compose.yml file:

version: "3.7"
services:
  node-docker-example:
    build: .
    ports:
      - "8082:8082"
    volumes: 
      - ./src:/app/src
    command: yarn watch

Src folder included as volumes so that when it changes in the host environment, it also updates in the docker container.

Run the container:

docker-compose up

This command will create the image using the DockerFile and then start a container. Add console.log("alive"); to server.js. You should see this console log on bash. Change Hello ExpressJS to Hello Docker in index.html and refresh the page. You should see that it has updated.

Stop container:

docker-compose down

The code examples can be also found at Github.



#docker, #nodejs, #express-js


← Back home