My project uses a Verdin Development Board iMX8 Mini with HDMI board as a host.
My goal is to have a React application running in Electron, so I’ve started from the Electron example project at Toradex IDE Extension in Visual Code.
I could be able to build and run and the demo application - the screen appeared and worked fine at the monitor.
From that base project I started changes to use Reacjs. Some files changed and others added.
Here is the result:
Dockerfile - As original from demo project - no changes
# ARGUMENTS --------------------------------------------------------------------
##
# Board architecture
##
ARG IMAGE_ARCH=
##
# Base container version
##
ARG BASE_VERSION=3.3.1
##
# Directory of the application inside container
##
ARG APP_ROOT=
##
# Board GPU vendor prefix
##
ARG GPU=
# ARGUMENTS --------------------------------------------------------------------
# BUILD ------------------------------------------------------------------------
FROM --platform=linux/${IMAGE_ARCH} \
commontorizon/wayland-base${GPU}:${BASE_VERSION} AS build
ARG IMAGE_ARCH
ARG GPU
ARG APP_ROOT
# stick to bookworm on /etc/apt/sources.list.d
RUN sed -i 's/sid/bookworm/g' /etc/apt/sources.list.d/debian.sources
# for vivante GPU we need some "special" sauce
RUN apt-get -q -y update && \
if [ "${GPU}" = "-vivante" ] || [ "${GPU}" = "-imx8" ]; then \
apt-get -q -y install \
imx-gpu-viv-wayland-dev \
; else \
apt-get -q -y install \
libgl1 \
; fi \
&& \
apt-get clean && apt-get autoremove && \
rm -rf /var/lib/apt/lists/*
# Install required packages
RUN apt-get -q -y update && \
apt-get -q -y install \
gcc \
g++ \
make \
git \
libatspi2.0-0 \
libgconf-2-4 \
libglib2.0-bin \
libgtk2.0-0 \
libgtk-3-0 \
libnotify4 \
libnss3 \
libuuid1 \
libxcb-dri3-0 \
xdg-utils \
libxss1 \
libxtst6 \
libasound2 \
curl \
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
# __torizon_packages_build_start__
# __torizon_packages_build_end__
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
&& \
apt-get clean && apt-get autoremove && \
rm -rf /var/lib/apt/lists/*
# install the latest node js
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs
RUN npm config set registry https://registry.npmjs.org/
COPY . ${APP_ROOT}
WORKDIR ${APP_ROOT}
# Remove the code from the debug builds, inside this container, to build the
# release version from a clean build
RUN rm -rf ${APP_ROOT}/out
RUN npm install
# build the application
RUN if [ "${IMAGE_ARCH}" = "arm64" ]; then \
npx electron-forge package -a arm64 -p linux; \
elif [ "${IMAGE_ARCH}" = "arm" ]; then \
npx electron-forge package -a armv7l -p linux; \
else \
npx electron-forge package -a x86 -p linux; \
fi;
# Copy the EGL lib inside the project's directory for vivante GPU
RUN if [ "${GPU}" = "-vivante" ] || [ "${GPU}" = "-imx8" ]; then \
cp /usr/lib/aarch64-linux-gnu/libEGL.so ${APP_ROOT}/out/testui-linux-${IMAGE_ARCH} && \
cp /usr/lib/aarch64-linux-gnu/libGLESv2.so ${APP_ROOT}/out/testui-linux-${IMAGE_ARCH}; \
# If it is a armv7l, this renames the output directory with the name expected by the Deploy fase
elif [ "${IMAGE_ARCH}" = "arm" ]; then \
mv ${APP_ROOT}/out/testui-linux-armv7l ${APP_ROOT}/out/testui-linux-${IMAGE_ARCH}; \
fi;
# BUILD ------------------------------------------------------------------------
# DEPLOY -----------------------------------------------------------------------
FROM --platform=linux/${IMAGE_ARCH} \
commontorizon/wayland-base${GPU}:${BASE_VERSION} AS deploy
ARG IMAGE_ARCH
ARG GPU
ARG APP_ROOT
# stick to bookworm on /etc/apt/sources.list.d
RUN sed -i 's/sid/bookworm/g' /etc/apt/sources.list.d/debian.sources
# for vivante GPU we need some "special" sauce
RUN apt-get -q -y update && \
if [ "${GPU}" = "-vivante" ] || [ "${GPU}" = "-imx8" ]; then \
apt-get -q -y install \
imx-gpu-viv-wayland-dev \
; else \
apt-get -q -y install \
libgl1 \
; fi \
&& \
apt-get clean && apt-get autoremove && \
rm -rf /var/lib/apt/lists/*
# your regular RUN statements here
# Install required packages
RUN apt-get -q -y update && \
apt-get -q -y install \
libatspi2.0-0 \
libgconf-2-4 \
libglib2.0-bin \
libgtk2.0-0 \
libgtk-3-0 \
libnotify4 \
libnss3 \
libuuid1 \
libxcb-dri3-0 \
xdg-utils \
libxss1 \
libxtst6 \
libasound2 \
curl \
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
# __torizon_packages_prod_start__
# __torizon_packages_prod_end__
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
&& \
apt-get clean && apt-get autoremove && \
rm -rf /var/lib/apt/lists/*
# install the latest node js
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs
# Copy the application compiled in the build step to the $APP_ROOT directory
# path inside the container, where $APP_ROOT is the torizon_app_root
# configuration defined in settings.json
COPY --from=build ${APP_ROOT}/out/testui-linux-${IMAGE_ARCH} ${APP_ROOT}
# "cd" (enter) into the APP_ROOT directory
WORKDIR ${APP_ROOT}
# Command executed in runtime when the container starts
# FIX: In this template arm32 is not working properly with egl, so if you are
# using an arm32 remove the "--use-gl=egl" and "--in-process-gpu" arguments.
CMD [ "./testui", "--no-sandbox", "--ozone-platform=wayland", "--use-gl=egl", "--in-process-gpu" ]
# DEPLOY -----------------------------------------------------------------------
Dockerfile.debug - As original from demo project - no changes
# ARGUMENTS --------------------------------------------------------------------
##
# Board architecture
##
ARG IMAGE_ARCH=
##
# Base container version
##
ARG BASE_VERSION=3.3.1
##
# Directory of the application inside container
##
ARG APP_ROOT=
##
# Debug port
##
ARG DEBUG_SSH_PORT=
##
# Run as
##
ARG SSHUSERNAME=
##
# Board GPU vendor prefix
##
ARG GPU=
# BUILD ------------------------------------------------------------------------
FROM --platform=linux/${IMAGE_ARCH} \
commontorizon/wayland-base${GPU}:${BASE_VERSION} AS debug
ARG IMAGE_ARCH
ARG GPU
ARG DEBUG_SSH_PORT
ARG APP_ROOT
ARG SSHUSERNAME
# SSH for remote debug
EXPOSE ${DEBUG_SSH_PORT}
EXPOSE 9229
# Make sure we don't get notifications we can't answer during building.
ENV DEBIAN_FRONTEND="noninteractive"
# stick to bookworm on /etc/apt/sources.list.d
RUN sed -i 's/sid/bookworm/g' /etc/apt/sources.list.d/debian.sources
# for vivante GPU we need some "special" sauce
RUN apt-get -q -y update && \
if [ "${GPU}" = "-vivante" ] || [ "${GPU}" = "-imx8" ]; then \
apt-get -q -y install \
imx-gpu-viv-wayland-dev \
; else \
apt-get -q -y install \
libgl1 \
; fi \
&& \
apt-get clean && apt-get autoremove && \
rm -rf /var/lib/apt/lists/*
# your regular RUN statements here
# Install required packages
RUN apt-get -q -y update && \
apt-get -q -y install \
gcc \
g++ \
make \
git \
openssl \
openssh-server \
rsync \
file \
curl \
libatspi2.0-0 \
libgconf-2-4 \
libglib2.0-bin \
libgtk2.0-0 \
libgtk-3-0 \
libnotify4 \
libnss3 \
libuuid1 \
libxcb-dri3-0 \
xdg-utils \
libxss1 \
libxtst6 \
libasound2 && \
apt-get clean && apt-get autoremove && \
rm -rf /var/lib/apt/lists/*
# automate for torizonPackages.json
RUN apt-get -q -y update && \
apt-get -q -y install \
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
# __torizon_packages_dev_start__
# __torizon_packages_dev_end__
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
&& \
apt-get clean && apt-get autoremove && \
rm -rf /var/lib/apt/lists/*
# install the latest node js
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs
RUN npm config set registry https://registry.npmjs.org/
# ⚠️ DEBUG PURPOSES ONLY!!
# THIS CONFIGURES AN INSECURE SSH CONNECTION
# create folders needed for the different components
# configures SSH access to the container and sets environment by default
RUN mkdir /var/run/sshd && \
sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' \
-i /etc/pam.d/sshd && \
if test $SSHUSERNAME != root ; \
then mkdir -p /home/$SSHUSERNAME/.ssh ; \
else mkdir -p /root/.ssh ; fi && \
echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config && \
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \
echo "Port ${DEBUG_SSH_PORT}" >> /etc/ssh/sshd_config && \
echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \
echo "PermitEmptyPasswords yes" >> /etc/ssh/sshd_config && \
su -c "env" $SSHUSERNAME > /etc/environment && \
echo "$SSHUSERNAME:" | chpasswd -e
RUN rm -r /etc/ssh/ssh*key && \
dpkg-reconfigure openssh-server
# Copy the application compiled in the build step to the $APP_ROOT directory
# path inside the container, where $APP_ROOT is the torizon_app_root
# configuration defined in settings.json
COPY --chown=$SSHUSERNAME:$SSHUSERNAME ./out/testui-linux-${IMAGE_ARCH} ${APP_ROOT}
# Copy the EGL lib inside the project's directory for vivante GPU
RUN if [ "${GPU}" = "-vivante" ] || [ "${GPU}" = "-imx8" ]; then \
cp /usr/lib/aarch64-linux-gnu/libEGL.so ${APP_ROOT} && \
cp /usr/lib/aarch64-linux-gnu/libGLESv2.so ${APP_ROOT}; \
fi
CMD [ "/usr/sbin/sshd", "-D" ]
forge.config.js
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
module.exports = {
packagerConfig: {
asar: true,
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
"config": {
"name": "electron_quick_start"
}
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-deb',
config: {},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
{
name: '@electron-forge/plugin-webpack',
config: {
mainConfig: './webpack.main.config.js',
renderer: {
config: './webpack.renderer.config.js',
entryPoints: [
{
html: './src/index.html',
js: './src/renderer.js',
name: 'main_window',
preload: {
js: './src/preload.js',
},
},
],
},
},
},
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};
package.json
{
"name": "testui",
"version": "1.0.0",
"description": "User Interface Test",
"main": ".webpack/main",
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make"
},
"repository": "",
"keywords": [
"Electron",
"quick",
"start",
"tutorial",
"demo"
],
"author": "GitHub",
"license": "CC0-1.0",
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-react": "^7.24.7",
"@electron-forge/cli": "^7.4.0",
"@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/maker-rpm": "^7.4.0",
"@electron-forge/maker-squirrel": "^7.4.0",
"@electron-forge/maker-zip": "^7.4.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
"@electron-forge/plugin-fuses": "^7.4.0",
"@electron-forge/plugin-webpack": "^7.4.0",
"@electron/fuses": "^1.8.0",
"@vercel/webpack-asset-relocator-loader": "1.7.3",
"babel-loader": "^9.1.3",
"css-loader": "^6.0.0",
"electron": "^32.0.2",
"node-loader": "^2.0.0",
"style-loader": "^3.0.0"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"d3": "^7.9.0",
"electron-squirrel-startup": "^1.0.1",
"moment": "^2.30.1",
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"socket.io-client": "^4.7.5"
}
}
torizonPackages.json
{
"prodRuntimeDeps": [],
"devRuntimeDeps": [],
"buildDeps": []
}
webpack.main.config.js
module.exports = {
/**
* This is the main entry point for your application, it's the first file
* that runs in the main process.
*/
entry: './src/main.js',
// Put your normal webpack config below here
module: {
rules: require('./webpack.rules'),
},
};
webpack.renderer.config.js
const rules = require('./webpack.rules');
rules.push({
test: /\.css$/,
use: [{
loader: 'style-loader',
options: {
esModule: false,
}
},
{
loader: 'css-loader',
options: {
esModule: false,
modules: {}
}
}],
});
module.exports = {
// Put your normal webpack config below here
module: {
rules,
},
};
webpack.rules.js
module.exports = [
// Add support for native node modules
{
// We're specifying native_modules in the test because the asset relocator loader generates a
// "fake" .node file which is really a cjs file.
test: /native_modules[/\\].+\.node$/,
use: 'node-loader',
},
{
test: /[/\\]node_modules[/\\].+\.(m?js|node)$/,
parser: { amd: false },
use: {
loader: '@vercel/webpack-asset-relocator-loader',
options: {
outputAssetBase: 'native_modules',
},
},
},
{
test: /\.(js|jsx)$/,
use: {
loader: 'babel-loader',
options: {
exclude: /node_modules/,
presets: ['@babel/preset-react']
}
}
},
// Put your webpack loader rules in this array. This is where you would put
// your ts-loader configuration for instance:
/**
* Typescript Example:
*
* {
* test: /\.tsx?$/,
* exclude: /(node_modules|.webpack)/,
* loaders: [{
* loader: 'ts-loader',
* options: {
* transpileOnly: true
* }
* }]
* }
*/
];
src/app.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import Home from "./Home"
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>ocker
<Home />
</React.StrictMode>
);
src/Home.jsx
import React from 'react';
import styles from "./Home.css";
const Home = () => {
return (
<div className={styles.container}>
<h1>INSIDE REACT</h1>
</div>
);
};
export default Home;
src/Home.css*
.container {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
font-size: 36px;
}
src/index.css
* {
box-sizing: border-box;
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
html {
box-sizing: border-box;
width: 100vw;
height: 100vh;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
font-family: 'Arial'; font-size: 22px;
margin: 0;
padding: 0;
font-size: 14px;
color: white;
background-color: red;
margin: 0 auto;
width: 100%;
height: 100%;
overflow-y: hidden;
overflow-x: hidden;
}
src/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>UI Tester</title>
</head>
<body>
<h1>UI LOADED!!!!</h1>
<div id="root"></div>
</body>
</html>
src/main.js
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 640,
height: 480,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY
}
})
// and load the index.html of the app.
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
// if embedded Linux set fullscreen
if (process.arch.includes("arm")) {
mainWindow.setFullScreen(true);
}
// Open the DevTools.
mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
src/preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
src/renderer.js
// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// No Node.js APIs are available in this process because
// `nodeIntegration` is turned off. Use `preload.js` to
// selectively enable features needed in the rendering
// process.
import "./app.jsx";
import './index.css';
console.log('👋 This message is being logged by "renderer.js", included via webpack');
So, what is the problem I’m facing:
The build process works fine until the end - final logs:
[+] Running 2/2
✔ Container torizon-weston-1 Started 3.2s
✔ Container torizon-testui-debug-1 Started 4.8s
* Terminal will be reused by tasks, press any key to close it.
* Executing task in folder testui: ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p 2228 torizon@192.168.0.118 /home/torizon/app/testui --no-sandbox --remote-debugging-port=9222 --inspect-brk=0.0.0.0:9229 --ozone-platform=wayland --use-gl=egl --in-process-gpu >out.log 2>err.log &
Warning: Permanently added '[192.168.0.118]:2228' (ED25519) to the list of known hosts.
* Terminal will be reused by tasks, press any key to close it.
From that moment, the blue color at the VS Code bottom status bar does not appear (showing that container is running).
The Torizon Building/Deploying with the circle spinner keeps running forever, and the screen gets a blue color and stucks.
Seens that the process freezes.
ssh`ed to Verdin board and docker logs from container shows nothing.
Entered in the container:
docker run -i -t 192.168.0.115:5002/testui:arm64 bash
Went to /home/torizon/app - the application is there:
root@de96398bf44a:/home/torizon/app# ls
LICENSE icudtl.dat resources
LICENSES.chromium.html libEGL.so resources.pak
chrome-sandbox libGLESv2.so snapshot_blob.bin
chrome_100_percent.pak libffmpeg.so v8_context_snapshot.bin
chrome_200_percent.pak libvk_swiftshader.so version
chrome_crashpad_handler libvulkan.so.1 vk_swiftshader_icd.json
core.10 locales
core.11 testui
No log and no outputs found.
Need help to find out what’s going on.
Edit: I have checked the electron bundle and it runs fine on my host machine (Ubuntu 22.04):
npx electron-forge start
And the application runs fine.