<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Bitovi Blog - UX and UI design, JavaScript and Front-end development

Cloud Ready Series: Dockerize Your JavaScript & Node.js Apps

Connor Graham

Is your app Cloud ready? Containerize your JavaScript/Node.js API with Docker create a consistent experience for everyone and everywhere your app runs.

posted in DevOps on July 29, 2022 by Connor Graham


Cloud Ready Series: Dockerize Your JavaScript & Node.js Apps

Connor Graham by Connor Graham

Docker is a great way to accelerate new developer onboarding and reduce overall operational complexity in production by creating a consistent experience for everyone and everywhere an app needs to run.

Containerizing your JavaScript/Node API with Docker to create a cloud-ready application is accomplished by:

  1. Writing a Dockerfile — a file that defines running instructions on how to build your application.
  2. Creating a docker-compose.yml — a file that defines how to run multiple docker containers and set environment variables. You can use Docker without Docker Compose, but it is far easier to build and run containers with Compose.

If you haven’t already, go ahead and install docker. It’s the last cli tool you’ll ever need.

Create Your JavaScript App

For this example, you’re going to create a two file app. Although this app is only two JavaScript files, this pattern will work for containerizing applications for any programming language of any size! Both of these files exist in the same directory:

├── index.js
└── pacakge.json

index.js

const express = require('express')
const app = express()
const port = process.env.PORT || 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

package.json

{
  "name": "bitovi-blog-app",
  "version": "1.0.0",
  "scripts": {
"start": "node index.js"
  },
  "dependencies": {
"express": "^4.17.1"
  }
}

If you have a compatible version of node and npm installed, you can run this locally with npm install then npm run start, but if not, that’s okay because Docker will handle that for you!

Writing a Dockerfile

Now you’re going to add a Dockerfile to your root directory. A Dockerfile contains a set of instructions on what dependencies, files, config, and packages we need to run an app. 

├── Dockerfile
├── index.js
└── pacakge.json

Dockerfile

# Use node version 15
FROM node:15

# Set an environment variable PORT with a default value of 8000
ARG PORT=8000
ENV PORT=$PORT

# Copy everything (index.js and package.json) from local working dir in to the docker image
COPY . .

# Run npm install within the image to download node dependencies
RUN npm install

# On startup, run npm start
CMD npm run start

This is a simple Dockerfile that will build and run the app on port 8000. You can optimize this process by leveraging multi-stage builds among other neat Docker tricks, but this file is sufficient for demonstrating basic Docker capabilities.

Creating a docker-compose.yml

Without Docker Compose, you could build and run your docker container with docker build -t my-node-app . && docker run my-node-app -p 9000:9000 -e PORT=9000. Instead, you’re going to introduce a .env and docker-compose.yml file to codify these in to a simple docker-compose up.

Add .env and docker-compose.yml to your directory:

├── .env
├── Dockerfile
├── docker-compose.yml
├── index.js
└── package.json

.env

This file defines environment variables that will be automatically read by docker-compose.yml. In this case, it will inject MY_PORT wherever it is referenced.

MY_PORT=9000

docker-compose.yml

# docker compose api version - do not edit
version: "3.8"

# A list of containers we want to run. We're just running 1 here
services:
  # Our service is called "my-app"
  my-app:
# "my-app" is a docker image that will be built on demand
build:
  # The `Dockerfile` exists in the same directory
  context: .
# Will create a Docker image called "my-node-app"
image: my-node-app
# Will expose the running container on localhost:9000 regardless of what port the app is actually listening on (controlled by MY_PORT env var).
ports:
  - "9000:${MY_PORT}"
# Pass in env var PORT to the running container
environment:
  PORT: ${MY_PORT}

Testing

Now that your app is built and Dockerized, it’s time to test! Go ahead and run docker-compose up, then open your browser to http://localhost:9000.

$ docker-compose up
Creating network "blog_default" with the default driver
Creating blog_my-app_1 ... done
Attaching to blog_my-app_1
my-app_1  | 
my-app_1  | > bitovi-blog-app@1.0.0 start
my-app_1  | > node index.js
my-app_1  | 
my-app_1  | Example app listening at http://localhost:9000
$ curl localhost:9000
Hello World!

You can stop your container with ctrl+c or docker-compose down in another tab and rebuild your container after making code changes with docker-compose build. Cool!

Hungry for More?

This blog post has been a condensed version of a free Learn Docker course. Check it out if you want to learn more about what is happening behind the scenes or to optimize your containers for both local development and production deployments.

Need Help?

Bitovi has consultants that can help. Drop into Bitovi's Community Slack, and talk to us in the #devops channel!

Need DevOps Consulting Services? Head over to DevOps Consulting - Bitovi , and book a free consultation.

Create better web applications. We’ll help. Let’s work together.