Dockerized edge gateway with MQTT broker, tile server, and local dashboards. Part of the TrailCurrent open-source vehicle platform.
| I want to... | Go here |
|---|---|
| Set up a development environment | Development Setup below |
| Build a deployment package | Building Deployment Packages below |
| Set up a new Raspberry Pi | DOCS/RaspberryPiOneTimeSetup.md |
| Deploy or update a Pi | PI_DEPLOYMENT.md |
| Set up Node-RED flows | Node-RED Setup below |
| Understand cloud OTA updates | OTA_DEPLOYMENT_IMPLEMENTATION.md |
- Hardware: Raspberry Pi 5 — selected for its industry-standard form factor and well-documented ecosystem, with plenty of headroom for additional expansion in the future
- Docker Engine (or Docker Desktop) with the
composeplugin andbuildx - Git
This gets your local development environment running with hot-reload and debugging enabled. All services build from local Dockerfiles for the host platform.
git clone <REPO_URL>
cd TrailCurrentInVehicleCompute
git config core.hooksPath .githooks
docker buildx use default
cp .env.example .env# Generate ENCRYPTION_KEY (64 character hex string)
openssl rand -hex 32
# Generate NODE_RED_CREDENTIAL_SECRET (128 character hex string)
openssl rand -hex 64Copy these values into the corresponding fields in .env.
NODE_RED_ADMIN_USER- Your Node-RED admin usernameNODE_RED_ADMIN_PASSWORD- Your desired admin password (plain text - auto-hashed at startup)ENCRYPTION_KEY- Paste the value from step 2NODE_RED_CREDENTIAL_SECRET- Paste the value from step 2ADMIN_PASSWORD- Strong password for system admin accessMQTT_USERNAME- Username for MQTT brokerMQTT_PASSWORD- Password for MQTT broker (plain text - auto-added to broker at startup)TLS_CERT_HOSTNAME- Your device's hostname (e.g.,trailcurrent01.local)
./scripts/generate-certs.sh
# Select option 1 for Development or option 2 for ProductionSee SSL Certificate Generation for details.
The tileserver requires a pre-generated mbtiles file to serve map data.
mkdir -p data/tileserver
# Place your tiles file at: data/tileserver/map.mbtilesTo generate tiles from OpenStreetMap data, use the PbfTileConverter utility: ../../Utilities/PbfTileConverter
Or copy map.mbtiles from an existing team member's machine.
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --buildThe --build flag ensures all images are built from local Dockerfiles for your host platform. Development mode enables:
- Hot-reload for frontend and backend code changes
- Node.js debug port (9229) for VSCode debugger attachment
- MongoDB accessible on localhost:27017
- Node-RED accessible on localhost:1880
- Tileserver styles hot-reload
Containers will automatically:
- Generate the bcrypt hash for Node-RED from your plain password
- Create the mosquitto password file from your credentials
- Initialize all services with consistent credentials
- Mount the SSL certificates for HTTPS/MQTTS communication
- Load map tiles from the mbtiles file
After startup, verify all services are healthy:
# All 7 containers should be running
docker compose ps
# Frontend loads
curl -k https://localhost/
# Tileserver healthy
curl http://localhost:8080/health
# Map style served
curl -s http://localhost:8080/styles/3d-dark/style.json | head -5
# Font glyphs available
curl -s -o /dev/null -w "%{http_code}" "http://localhost:8080/fonts/Noto%20Sans%20Regular/0-255.pbf"
# Expected: 200Access the web UI: https://localhost (accept the self-signed certificate warning)
.envis in.gitignore- will never be committed- Use strong, randomly generated passwords for all credentials
- All values in
.env.exampleare placeholders only - Passwords are auto-hashed/auto-configured at container startup
For creating the offline zip that gets deployed to Raspberry Pis. The deployment pipeline builds Docker images for ARM64, packages them as tar files, and creates a self-contained zip.
# Build everything and create the zip
./create-deployment-package.sh --version=1.0.0This will:
- Build all 6 service images for
linux/arm64(plus pullmongo:7) - Save images as
.tarfiles inimages/ - Fetch MCU firmware from GitHub releases (if available)
- Package everything into
trailcurrent-deployment-1.0.0.zip
Transfer and deploy to Pi:
scp trailcurrent-deployment-1.0.0.zip trailcurrent@pi.local:~
# On the Pi:
unzip trailcurrent-deployment-1.0.0.zip && chmod +x deploy.sh && ./deploy.shSee PI_DEPLOYMENT.md for detailed deployment instructions.
containers/ Dockerfiles for each service
frontend/ nginx + MapLibre GL web UI
backend/ Node.js Express API
mosquitto/ Eclipse Mosquitto MQTT broker
node-red/ Node-RED flow engine
noderedproxy/ nginx HTTPS proxy for Node-RED
tileserver/ Custom tile server (styles, fonts, sprites)
config/ Version-controlled service configurations
mosquitto/ mosquitto.conf
node-red/ settings.js, starter-flow.json, cloud-workflow.json
data/ Runtime data (gitignored)
keys/ TLS certificates
tileserver/ map.mbtiles
node-red/ Node-RED flows
local_code/ Python host services (CAN-to-MQTT bridge, deployment watcher, OTA helpers)
scripts/ Utility scripts (cert generation)
Docker Compose files:
docker-compose.yml— Production orchestration (7 services)docker-compose.dev.yml— Development overrides (hot-reload, debug ports)
The application uses TLS/SSL for secure communication. Certificates must be generated before running docker compose up. The scripts/generate-certs.sh script supports two modes:
# Interactive mode (prompts for selection)
./scripts/generate-certs.sh
# Non-interactive mode (development)
./scripts/generate-certs.sh 1
# Non-interactive mode (production)
./scripts/generate-certs.sh 2
# Using environment variable
CERT_MODE=2 ./scripts/generate-certs.sh
# Show help
./scripts/generate-certs.sh --helpFor local development with localhost or 127.0.0.1:
./scripts/generate-certs.sh 1Development certificates include:
- DNS names:
localhost, plus yourTLS_CERT_HOSTNAMEfrom.env - IP addresses:
127.0.0.1,::1(IPv6 localhost)
Access locally:
https://localhost - Frontend (HTTPS)
https://localhost:8443 - Node-RED (HTTPS)
Accept the self-signed certificate warning in your browser (one-time).
For deployed devices accessed from other machines on the network:
-
Set your device's hostname in
.env:TLS_CERT_HOSTNAME=trailcurrent01.local -
Generate production certificates:
./scripts/generate-certs.sh 2
-
Install the CA certificate (
data/keys/ca.crt) on devices that will access the web UI. -
Access from the network:
https://trailcurrent01.local - Web UI mqtts://trailcurrent01.local:8883 - MQTT broker
Run the script anytime to regenerate. It will prompt before overwriting existing certificates and create backups.
./scripts/generate-certs.sh
docker compose restartCertificates are automatically protected by .gitignore. Never commit them to version control.
# Development mode (hot-reload, debug ports) — always use --build to ensure local images
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
# Production mode (after verification)
docker compose up -d --buildMongoDB Access:
- Services communicate via Docker's internal network:
mongodb:27017 - MongoDB is NOT exposed to localhost (127.0.0.1)
- To access MongoDB from your host:
docker compose exec mongodb mongosh
Docker containers use service names for inter-container communication.
The data/ directory contains all persistent application data:
- SSL certificates (
data/keys/) — Generated once, valid for 10 years - Map tiles (
data/tileserver/map.mbtiles) — Set up once, rarely updated - Node-RED flows (
data/node-red/) — User-created flows and settings - MongoDB — Named volume
mongodb-data, persists across rebuilds
Never delete data/ during updates unless performing a complete reset. All data persists across container rebuilds.
Updating the Application (No Data Loss):
git pull
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
# Certificates, .env, map tiles, and Node-RED flows are preservedNode-RED bridges CAN bus messages to local MQTT topics that the PWA consumes. Flow templates are stored in config/node-red/.
- Inbound: Subscribes to
can/inbound, routes by CAN identifier, and publishes parsed data tolocal/*topics:- Light status (8 channels) →
local/lights/{1-8}/status - Energy (battery voltage, SOC, solar watts, charge status, power consumption, time remaining) →
local/energy/status - Temperature & humidity →
local/airquality/temphumid - GPS coordinates, altitude, details, time →
local/gps/*
- Light status (8 channels) →
- Outbound: Subscribes to
local/lights/{1-8}/commandandlocal/lights/{1-8}/brightness(from PWA light controls) and sends CAN messages tocan/outbound - Test controls: Manual inject nodes for toggling lights, setting brightness, and all-on/all-off
On first startup, the backend automatically detects that Node-RED has no flows and injects the starter flow via the Node-RED Admin API. Existing installations with flows already present are not affected.
When cloud synchronization is enabled in Settings, the backend automatically injects a "Cloud Workflow" tab into Node-RED that bridges messages between the local and cloud MQTT brokers:
- Cloud → Local (Commands):
rv/lights/N/command→ CAN toggle,rv/thermostat/command→ local passthrough - Local → Cloud (Status): Light status, air quality, GPS, energy, thermostat — each rate-limited to 30 msg/sec
The cloud workflow is automatically removed when cloud is disabled or the system config is reset. The template is at config/node-red/cloud-workflow.json.
- Open Node-RED at
https://<hostname>:8443 - Click the hamburger menu (top right) → Import
- Select Upload and choose
config/node-red/starter-flow.json - Click Import
After importing, Node-RED needs MQTT credentials to connect to Mosquitto:
- Double-click any MQTT node (e.g. "CAN Inbound")
- Click the pencil icon next to Local MQTT Broker
- Go to the Security tab
- Enter your
MQTT_USERNAMEandMQTT_PASSWORD(same values from.env) - Click Update, then click the red Deploy button
The broker is pre-configured to connect to mosquitto:8883 via Docker internal DNS with TLS.
The backend Node.js server can be debugged using VSCode's built-in debugger. The setup uses --inspect-brk to pause the application at startup, waiting for the debugger to attach before continuing execution.
-
Open the project in VSCode (if not already open)
-
Start debugging:
- Open the Debug panel (Ctrl+Shift+D / Cmd+Shift+D)
- Select "Backend Debug" from the dropdown
- Click the green "Start Debugging" button (or press F5)
-
What happens automatically:
- Docker containers start in development mode (
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up) - Backend container starts with
--inspect-brk=0.0.0.0:9229(pauses at startup) - VSCode automatically attaches the debugger to port 9229
- Application resumes and you can now set breakpoints
- Docker containers start in development mode (
-
Stop debugging:
- Press Shift+F5 or click the stop button in the debug panel
- Docker containers are automatically stopped (
docker-compose down)
# Start containers in development mode
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# In VSCode, attach manually:
# - Open Debug panel (Ctrl+Shift+D)
# - Select "Backend Debug"
# - Click "Start Debugging" (F5)The debugging setup is configured in .vscode/launch.json:
{
"name": "Backend Debug",
"type": "node",
"request": "attach",
"port": 9229,
"address": "localhost",
"restart": true,
"skipFiles": ["<node_internals>/**"],
"outFiles": ["${workspaceFolder}/containers/backend/src/**/*.js"]
}"Connection refused" error:
- Ensure containers are running:
docker compose ps - Check backend container logs:
docker compose logs backend - Verify port 9229 is exposed:
docker compose port backend 9229
Breakpoints not being hit:
- Verify the file path matches exactly (case-sensitive on Linux)
- Check development mode:
docker compose config | grep "NODE_ENV"
Backend container exits immediately:
- Check logs:
docker compose logs backend - Ensure
.envfile is set up correctly - Verify MongoDB:
docker compose logs mongodb
Mosquitto or other containers failing to start:
- Check logs:
docker compose logs mosquitto - Certificate permissions:
chmod 644 data/keys/*.key data/keys/*.crt data/keys/*.pem
Port conflicts:
- Clean up:
docker system prune -f --volumes - Verify no containers running:
docker ps -a - Restart Docker:
sudo systemctl restart docker
- Breakpoint in startServer() in
src/index.jsto debug initialization - Route breakpoints in route handlers (e.g.,
src/routes/thermostat.js) - MQTT debugging via breakpoints in
src/mqtt.js - Database debugging via breakpoints in
src/db/init.js - Conditional breakpoints: Right-click a breakpoint to set conditions
- Logpoints: Right-click line number to log values without pausing