Bundle a Node.js app into a rock¶
This tutorial describes the steps needed to bundle a typical Node.js application into a rock.
It should take around 10 minutes to complete.
Setup your environment¶
We recommend starting from a clean Ubuntu installation. If we don’t have one available, we can create one using Multipass:
Is Multipass already installed and active? Check by running
snapservicesmultipass
If we see the multipass service but it isn’t "active", then we’ll
need to run sudo snap start multipass. On the other hand, if we get
an error saying snap "multipass" not found, then we must install
Multipass:
sudosnapinstallmultipass
See Multipass installation instructions, switch to Windows in the drop down.
See Multipass installation instructions, switch to macOS in the drop down.
Then we can create the VM with the following command:
multipass launch --memory 2G --disk 10G --name rock-dev 26.04
Finally, once the VM is up, open a shell into it:
multipassshellrock-dev
Unless stated otherwise, we will work entirely within the VM from now on.
LXD will be required for building the rock. Make sure it is installed and initialised:
sudosnapinstalllxd lxdinit--auto
There is a known connectivity issue with LXD and Docker. To avoid this issue, enable IPv4 forwarding before installing Docker:
echo"net.ipv4.conf.all.forwarding=1"|sudotee-a/etc/sysctl.d/99-forwarding.conf sudosystemctlrestartsystemd-sysctl
Warning
In a production environment, make sure to use a hardened firewall configuration.
In order to create the rock, we’ll install Rockcraft with classic confinement, which grants it access to the whole file system:
sudosnapinstallrockcraft--classic
We’ll use Docker to run the rock. We can install it as a snap:
sudosnapinstalldocker
By default, Docker is only accessible with root privileges (sudo). We want
to be able to use Docker commands as a regular user:
sudoaddgroup--systemdocker
sudoadduser$USERdocker
newgrpdocker
Restart Docker:
sudosnapdisabledocker
sudosnapenabledocker
Note that we’ll also need a text editor. We can either install one of our
choice or simply use one of the already existing editors in the Ubuntu
environment (like nano).
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:
{ "name":"node_web_app", "version":"1.0.0", "description":"Node.js on a rock", "author":"First Last <first.last@example.com>", "main":"server.js", "scripts":{ "start":"node server.js" }, "dependencies":{ "express":"^5.0.0" } }
The second file is our sample app, a simple "hello world" server. Still inside
src/, add the following contents to server.js:
'use strict'; constexpress=require('express') constapp=express() constport=8080 consthost='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 license:
name:my-node-app base:ubuntu@26.04 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:
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:
parts: app: plugin:npm npm-include-node:True npm-node-version:"24.15.0" source:src/
The whole file then looks like this:
name:my-node-app base:ubuntu@26.04 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:"24.15.0" source:src/ organize: bin:usr/bin lib:usr/lib build-environment: # NOTE: There's currently a bad interaction between the nodejs version in the # archives and the kernel, causing an infinite hang during 'npm install': # - https://github.com/npm/cli/issues/4028 # - https://github.com/amazonlinux/amazon-linux-2023/issues/856 # For now we need to disable libuv's use of io_uring; this should be able to # be reverted in a few months (as of April 2025). -UV_USE_IO_URING:"0"
Pack the rock with Rockcraft¶
To build the rock, run:
rockcraftpack
At the end of the process, a new rock file should be present in the current directory:
lsmy-node-app_1.0_amd64.rock
Run the rock in Docker¶
First, import the recently created rock into Docker:
sudorockcraft.skopeo--insecure-policycopyoci-archive:my-node-app_1.0_amd64.rockdocker-daemon:my-node-app:1.0
Since the rock bundles a web-app, we’ll first start serving that app on local port 8000:
dockerrun--namemy-node-app-p8000:8080my-node-app:1.0-v
The output will look similar to this, indicating that Pebble started the app
service:
2023年10月30日T12:37:33.654Z [pebble] Started daemon. 2023年10月30日T12:37:33.659Z [pebble] POST /v1/services 3.878846ms 202 2023年10月30日T12:37:33.663Z [pebble] Service "app" starting: node server.js 2023年10月30日T12:37:33.864Z [app] Running on http://0.0.0.0:8080 2023年10月30日T12:37:33.865Z [pebble] Started default services with change 1.
Next we’ll verify that the Node.js app is up and running. If you’re working on
a regular Ubuntu system, open your web browser and go to
http://localhost:8000. You should see a blank page with a
"Hello World from inside the rock!" message. Success!
If, instead, you’re working in a Multipass VM, you can open another shell into the VM and access the app with curl:
multipassshellrock-dev curlhttp://localhost:8000
This should also print "Hello World from inside the rock!" to the terminal.
You can now stop the running container by either interrupting it with Ctrl + C or by running the following in another terminal:
dockerstopmy-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.