DEV Community

Cover image for Immersive Web, Docker and problem with symbolic links for SSL certificates.
Sylwester Mielniczuk for Samsung Internet

Posted on • Updated on

Immersive Web, Docker and problem with symbolic links for SSL certificates.

Interactive Wall at GAM in Turin

As an Immersive Web Developer from time to time, there is a need to play the role of DevOps engineer. Mostly because the prototype of my research project needed to be deployed to production cloud infrastructure (5G EVE).

Despite the complexity of the overall 5G research, my web application is relatively weak, called "Interactive Wall" is designed for a multi-device experience with blazing fast animation purposes WebGL canvas (PixiJS). Depending on the provided parameters there are two types of UIs:

  1. For students (kids or other pupils) - (multiplayer)
  2. The teacher(s) (or other guardians) - (single user)

The first one is displayed on the wall via projector, the second can be opened via QR code on the mobile device, wearable or any other desktop running Chromium-based browsers. Realtime communication between both interfaces is established via WebSocket server.

Immersive Web

The GIF above showcases a WebSocket connection between 2 tabs of the browser, mobile and wearable device. The latency between them is about and above 30ms but depends on the hardware and network characteristics. We were aiming to measure all the advantages of having 20 times faster connections than 4G.

Connecting to uncommon HID devices.

Paired and connected via Bluetooth thanks to WebHID API - old good and forgotten almighty Nintendo Wiimote. Its camera for direction reports is calibrated by IR Bar, and all the motion, acceleration and input data from 11 buttons are sent via Bluetooth to the browser.

What is actually WebHID?

"HID consists of two fundamental concepts: reports and report descriptors. Reports are the data that is exchanged between a device and a software client. The report descriptor describes the format and meaning of data that the device supports.
An HID (Human Interface Device) is a type of device that takes input from or provides output to humans. It also refers to the HID protocol, a standard for bi-directional communication between a host and a device that is designed to simplify the installation procedure. The HID protocol was originally developed for USB devices, but has since been implemented over many other protocols, including Bluetooth." -

I was able to use simultaneously more than 4 controllers. Originally developed for one Wiimote by a former colleague at Samsung Kevin Picchi. I have developed a multiuser version for 5G Tours consortium. It's a really long story.

Portable Deployment

In general, the browser UI can be statically provided separately from the WebSocket server but for the sake of portability, decided to pack everything all together as Node.JS project, perfect for scalable web applications. Portable deployment should be easy with Docker. There is super simple tutorial how to do bake nodejs-docker-webapp. Basic use of Docker is free and you can install it on Mac, Windows and Linux. You need to write a relatively simple Dockerfile.

# The name of the image for your container
FROM node:16

# Create app directory, copy configuration assets
WORKDIR /usr/src/app
COPY package*.json ./

# Install npm
RUN npm install

# Bundle app source
COPY . .

# Optional exposed app ports

# Available commands from CLI
CMD [ "node", "index-wall.js" ]

Enter fullscreen mode Exit fullscreen mode

To build the image run this command from the folder where your Dockerfile and assets are located.
$ docker build . -t flaboy/node-wall
flaboy/node-wall is the given name of image, when it's created you can push and pull this like git repo to the repository.

$ docker push flaboy/node-wall

If you need to share image file this is the way you can export tarball:
$ docker save flaboy/node-wall > node-vr.tar

Later someone can load the archived image as follows:
$ docker load < node-vr.tar

Deploying such an image seems to be super simple. On the server with a fully qualified domain and web server and reverse proxy Nginx).

The only little problem is those files required by server web application via HTTPS, ie SSL certificates. Where they are located? It depends on how you managed such certificates. I use letsEncrypt Certbot.

Your browser lock at the location bar says:

This is what you see

These chains of trust hierarchy technically have this structure:

Chain of Trust

Let's Encrypt Certbot hints

$ sudo apt install certbot python3-certbot-nginx

Get your domain SSL certificates:
$ sudo certbot --nginx -d

The architecture is organized by certbot in this folder: /etc/letsencrypt


And the domain name specific files are saved to /etc/letsencrypt/archive/

So these files are archived under the following numbers (because certbot already renewed cert files so far several times). The latest ones are exposed to the /etc/letsencrypt/live/ folder. as symbolic links (symlinks) without numbers in their names.

- Symbolic links are interpreted at run time as if the contents of the link had been substituted into the path being followed to find a file or directory. - Symbolic links may contain .. path components, which (if used at the start of the link) refer to the parent directories of that in which the link resides. - A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent one; the latter case is known as a dangling link..

root [letsencrypt] $ more /etc/letsencrypt/live/ 
This directory contains your keys and certificates.

`privkey.pem`  : the private key for your certificate.
`fullchain.pem`: the certificate file used in most server software.
`chain.pem`    : used for OCSP stapling in Nginx >=1.3.7.
`cert.pem`     : will break many server configurations, and should not be used
                 without reading further documentation (see link below).

         Certbot expects these files to remain in this location in order
         to function properly!

We recommend not moving these files. For more information, see the Certbot
User Guide at
Enter fullscreen mode Exit fullscreen mode

Coming back to the Docker. We have pulled the image and we would love to run the web app. The problem is that the container needs to read SSL certificates (exposed as symbolic links) from the outside of the Docker virtual container. They are required to run the web app securely via HTTPS. The paths to the key and certificate are provided in the main Node.js app file: index-wall.js

const server = https.createServer({
    key: fs.readFileSync('/ssl/privkey.pem'),
    cert: fs.readFileSync('/ssl/fullchain.pem')
}, app);
Enter fullscreen mode Exit fullscreen mode

Here is how it can not be run with mounted volumes -v ( --volume).

$ docker run -v /etc/letsencrypt/live/ -d -p 4444:8080 flaboy/node-wall node index-wall.js

Docker unfortunately is not able to read symlinks from provided volumes (-v). You can check the error logs this way:
$ docker logs containerid

root [letsencrypt] $ docker logs 8d2b81a25359
    throw err;

Error: ENOENT: no such file or directory, open '/ssl/privkey.pem'
    at Object.openSync (node:fs:585:3)
    at Object.readFileSync (node:fs:453:35)
    at Object.<anonymous> (/usr/src/app/index-wall.js:18:13)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
    at node:internal/main/run_main_module:17:47 {
  errno: -2,
  syscall: 'open',
  code: 'ENOENT',
  path: '/ssl/privkey.pem'
Enter fullscreen mode Exit fullscreen mode

You need to use bind mounts

What are bind mounts? As per Docker documentation, we can read that:

[...] Bind mounts have limited functionality compared to volumes. When you use a bind mount, a file or directory on the host machine is mounted into a container. The file or directory is referenced by its absolute path on the host machine. By contrast, when you use a volume, a new directory is created within Docker’s storage directory on the host machine, and Docker manages that directory’s contents.

Detailed instruction on the bind mounts syntax:

--mount: Consists of multiple key-value pairs, separated by commas and each consisting of a <key>=<value> tuple. - The --mount syntax is more verbose than -v or --volume, but the order of the keys is not significant, and the value of the flag is easier to understand.

  • The type of the mount, which can be bind, volume, or tmpfs. [...] The type is always bind.
  • The source of the mount. For bind mounts, this is the path to the file or directory on the Docker daemon host. May be specified as source or src.
  • The destination takes as its value the path where the file or directory is mounted in the container. May be specified as destination, dst, or target.
  • The readonly option, if present, causes the bind mount to be mounted into the container as read-only.
  • The bind-propagation option, if present, changes the bind propagation. May be one of rprivate, private, rshared, shared, rslave, slave.
  • The --mount flag does not support z or Z options for modifying selinux labels.

So let's try to mount 2 cert files (sources):
and /etc/letsencrypt/live/ as they would exist inside docker container inside /ssl/ folder (target).

$ docker run --mount type=bind,source=/etc/letsencrypt/live/,target=/ssl/privkey.pem --mount type=bind,source=/etc/letsencrypt/live/,target=/ssl/fullchain.pem -d -p 4444:8080 flaboy/node-wall node index-wall.js

Is the app running?
$ docker ps -a

9990433c624d   flaboy/node-wall   "docker-entrypoint.s…"   4 minutes ago   Up 4 minutes       >8080/tcp, :::4444->8080/tcp   hardcore_nash
Enter fullscreen mode Exit fullscreen mode

Yes! That's all people!

Here is my video on how it did not work properly with -v
Here is how it did not work with -v

And here is working properly with the latest SSL
And here is working properly SSL

If you would love to play with Interactive Wall, it's deployed to:

The app works on Mac, and Windows (you might need also Nintendo
emulator Dolphin). You just need a Chromium-based browser (Edge, Brave, Chrome), also get your original Wiimote(s) Plus (cheap, the third party rather does not work). Do not forget to charge your batteries. Infrared direction calibrator Mayflash W010 Dolphin Bar is necessary, get it from the Amazon. The WebHID API and amount of Bluetooth devices might be limited on Windows but Bluetooth standard might allow connecting lots of devices so having multiple input devices connected to the web browser interface at the same time is an amazing opportunity for multiplayer experiences without any game-server involved. Just think about this a little bit!

The Students UI

The Teachers UI (open from provided QR code)

Image description

More about 5GTours project you can find in D4.4 public document on

Top comments (0)