Bundle a Node.js app within a rock

This tutorial describes the steps needed to bundle a typical Node.js application into a rock.

Prerequisites

Install Rockcraft

Install Rockcraft on your host:

sudo snap install rockcraft --classic

Project Setup

Starting in an empty folder, create a src/ subdirectory. Inside it, add two files:

The first one is the package.json listing of dependencies, with the following contents:

package.json
{
    "name": "node_web_app",
    "version": "1.0.0",
    "description": "Node.js on a rock",
    "author": "First Last <[email protected]>",
    "main": "server.js",
    "scripts": {
      "start": "node server.js"
    },
    "dependencies": {
      "express": "^4.18.2"
    }
}

The second file is our sample app, a simple “hello world” server. Still inside src/, add the following contents to server.js:

server.js
'use strict';

const express = require('express')
const app = express()
const port = 8080
const host = '0.0.0.0'

app.get('/', (req, res) => {
  res.send('Hello World from inside the rock!');
});

app.listen(port, host, () => {
  console.log(`Running on http://${host}:${port}`);
});

Next, we’ll setup the Rockcraft project. In the original empty folder, create an empty file called rockcraft.yaml. Then add the following snippets, one after the other:

Add the metadata that describes your rock, such as its name and licence:

rockcraft.yaml
name: my-node-app
base: [email protected]
version: '1.0'
summary: A rock that bundles a simple nodejs app
description: |
  This rock bundles a recent node runtime to serve a simple "hello-world" app.
license: GPL-3.0
platforms:
  amd64:

Add the container entrypoint, as a Pebble service:

rockcraft.yaml
services:
    app:
        override: replace
        command: node server.js
        startup: enabled
        on-success: shutdown
        on-failure: shutdown
        working-dir: /lib/node_modules/node_web_app

Finally, add a part that describes how to build the app created in the src/ directory using the npm plugin:

rockcraft.yaml
parts:
    app:
        plugin: npm
        npm-include-node: True
        npm-node-version: "21.1.0"
        source: src/

The whole file then looks like this:

rockcraft.yaml
name: my-node-app
base: [email protected]
version: '1.0'
summary: A rock that bundles a simple nodejs app
description: |
  This rock bundles a recent node runtime to serve a simple "hello-world" app.
license: GPL-3.0
platforms:
  amd64:

services:
    app:
        override: replace
        command: node server.js
        startup: enabled
        on-success: shutdown
        on-failure: shutdown
        working-dir: /lib/node_modules/node_web_app

parts:
    app:
        plugin: npm
        npm-include-node: True
        npm-node-version: "21.1.0"
        source: src/

Pack the rock with Rockcraft

To build the rock, run:

rockcraft pack

At the end of the process, a new rock file should be present in the current directory:

ls my-node-app_1.0_amd64.rock

Run the rock in Docker

First, import the recently created rock into Docker:

sudo /snap/rockcraft/current/bin/skopeo --insecure-policy copy oci-archive:my-node-app_1.0_amd64.rock docker-daemon:my-node-app:1.0

Since the rock bundles a web-app, we’ll first start serving that app on local port 8000:

docker run --name my-node-app -p 8000:8080 my-node-app:1.0

The output will look similar to this, indicating that Pebble started the app service:

2023-10-30T12:37:33.654Z [pebble] Started daemon.
2023-10-30T12:37:33.659Z [pebble] POST /v1/services 3.878846ms 202
2023-10-30T12:37:33.659Z [pebble] Started default services with change 1.
2023-10-30T12:37:33.663Z [pebble] Service "app" starting: node server.js
2023-10-30T12:37:33.864Z [app] Running on http://0.0.0.0:8080

Next, open your web browser and navigate to http://localhost:8000. You should see a blank page with a “Hello World from inside the rock!” message. Success!

You can now stop the running container by either interrupting it with CTRL+C or by running the following in another terminal:

docker stop my-node-app

References

The sample app code comes from the “Hello world example” Express tutorial, available at https://expressjs.com/en/starter/hello-world.html.