Docker - Node.js Koa Container

An example of how to create a Docker container application using Koa.js Next generation web framework for Node.js.

In the project root, initialize using Yarn or npm.

yarn init -y

Install dependencies.

yarn add koa
yarn add koa-body
yarn add koa-logger
yarn add koa-router
yarn add koa-views
yarn add swig

Create an app folder in the project root.

In the app folder, create a folder named lib. Then create this render.js module in the new lib folder.

render.js
/**
 * Module dependencies.
 */

const views = require('koa-views');
const path = require('path');

// setup views mapping .html
// to the swig template engine

module.exports = views(path.join(__dirname, '/../views'), {
  map: { html: 'swig' }
});

In the app folder, create a folder for templates named views. Then create this index.html template in the new views folder.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <h1>{{content}}</h1>
</body>
</html>
  • Using Emmet, which is built into VS Code, you can create the index.html content by entering an exclamation mark on the first line then select the tab key.

In the app folder, create this app.js application entrypoint file.

app.js
const render = require('./lib/render');
const logger = require('koa-logger');
const router = require('koa-router')();
const koaBody = require('koa-body');

const Koa = require('koa');
const app = module.exports = new Koa();

// middleware

app.use(logger());

app.use(render);

app.use(koaBody());

// route definitions

router.get('/', index);

app.use(router.routes());

async function index(ctx) {
  await ctx.render('index', { content: 'Hello World' });
}

// listen

if (!module.parent) app.listen(3000);

Project structure

  • project
    • package.json
    • app
      • app.js
      • lib
        • render.js
      • views
        • index.html

Test the application locally in a browser at http://localhost:3000. Use Ctrl + C to kill the app after verifying it works.

cd app
node app.js

Docker

To containerize the application, create a docker-compose.yml file in the project root as follows.

docker-compose.yml
version: '3'

services:
  app:
    image: node:alpine
    user: "node"
    working_dir: /home/node/app
    environment:
      - NODE_ENV=production
    ports:
      - "3000:3000"
    volumes:
      - ./app:/home/node/app
      - ./node_modules:/home/node/node_modules
    expose:
      - "3000"
    command: "node app.js"

Build, (re)create and start, the container in disconnected mode. The app folder is attached as a volume and mapped to the working directory, /home/node/app in the container. The node app.js command is executed in the containers working directory.

docker-compose up -d

Test the application locally in a browser at http://localhost:3000. Use Ctrl + C to kill the app after verifying it works.

Stop and remove the container and volumes created by docker-compose up.

docker-compose down

Build a Docker image for better performance and deployment once initial development is completed. Instead of mapping the local app and node_modules folders to the container, copy files and folders into the container, set working directories and run commands as needed.

Create this Dockerfile in the project root

Dockerfile
FROM node:alpine
WORKDIR /home/node

# using wildcard (*) to copy both package.json and package-lock.json
COPY package*.json /home/node/
RUN yarn install --production

# create and set app directory as current dir
WORKDIR /home/node/app
COPY app/ /home/node/app/
EXPOSE 3000
CMD ["node", "app.js"]

Build the image and tag it. In the project root run the following command.

docker build -t myapp-node .

Test the new myapp-node Docker image using docker run. Same URL as before, http://localhost:3000.

docker run -u node -w /home/node/app -e NODE_ENV=production -p 3000:3000 --expose 3000 myapp-node node "app.js"

Stop the container using docker stop followed by the container ID. To get a list of all running containers, use docker ps --filter status=running.

That’s it!