Docker Compose Deployment (ARM64)
This guide walks you through deploying the complete ALP-CONNEX stack on an ARM64 system using Docker Compose. After completing these steps, you will have:
- The Management UI for configuring connectors and mappings
- The Management API backend with a PostgreSQL database
- The Valkey data store (process image)
- The IEC 104 Connector serving data to SCADA clients
- The MQTT Connector ingesting data from IoT devices
- An Eclipse Mosquitto MQTT broker
Prerequisites
| Requirement | Minimum |
|---|---|
| Architecture | ARM64 (e.g. Raspberry Pi 4/5, NVIDIA Jetson, AWS Graviton) |
| Docker Engine | 24.0 or newer |
| Docker Compose | V2 (included with Docker Engine) |
| RAM | 2 GB free |
| Disk | 5 GB free |
Verify your Docker installation:
docker --version
docker compose version
Folder Structure
Create the following directory layout on your host system:
alp-connex/
├── .env # Environment variables
├── docker-compose.yaml # Service definitions
├── mosquitto/
│ └── config/
│ └── mosquitto.conf # MQTT broker configuration
└── mqtt/
└── config/
└── mapping.json # MQTT mapping file (optional)
mkdir -p alp-connex/mosquitto/config alp-connex/mqtt/config
cd alp-connex
Step 1 — Environment Variables (.env)
Create a .env file in the project root. Docker Compose reads this file automatically and substitutes the variables into docker-compose.yaml.
# ──────────────────────────────────────────
# Host Configuration
# ──────────────────────────────────────────
# Replace with the IP address of the machine running this stack.
# This is used by the browser to reach the API — it cannot be "localhost"
# if you access the UI from another machine on the network.
HOST_IP=192.168.1.100
# ──────────────────────────────────────────
# Database
# ─ ─────────────────────────────────────────
POSTGRES_USER=postgres
POSTGRES_PASSWORD=change-me-to-a-secure-password
POSTGRES_DB=alpconnex
# ──────────────────────────────────────────
# Image Versions
# ──────────────────────────────────────────
# Replace with the versions provided to you.
TAG_MANAGEMENT=x.y.z
TAG_IEC104=x.y.z
TAG_MQTT=x.y.z
# ──────────────────────────────────────────
# Connector Configuration IDs
# ──────────────────────────────────────────
# These are the unique identifiers for each connector configuration,
# generated by the Management UI when you create a new configuration.
IEC104_CONFIG_ID=00000000-0000-0000-0000-000000000000
MQTT_CONFIG_ID=00000000-0000-0000-0000-000000000000
Important: Replace
HOST_IPwith your actual machine IP andPOSTGRES_PASSWORDwith a secure password before starting the stack.
Step 2 — Docker Compose File
Create the docker-compose.yaml with the following content:
services:
# ═══════════════════════════════════════════
# PostgreSQL — Management Database
# ═══════════════════════════════════════════
database:
image: postgres:15
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
# ports:
# - "5432:5432" # Uncomment for external database access
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- connector-net
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ═══════════════════════════════════════════
# Management UI — Frontend
# ═══════════════════════════════════════════
frontend:
image: registry.alpscale.io/alp-connex/alp-connex-management-frontend:${TAG_MANAGEMENT}
ports:
- "4200:8080"
environment:
# Replace with the IP address of your host machine
API_URL: "http://${HOST_IP}:8080"
depends_on:
backend:
condition: service_started
restart: unless-stopped
networks:
- connector-net
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ═══════════════════════════════════════════
# Management API — Backend
# ═══════════════════════════════════════════
backend:
image: registry.alpscale.io/alp-connex/alp-connex-management-api:${TAG_MANAGEMENT}
environment:
ASPNETCORE_URLS: http://+:8080
ASPNETCORE_ENVIRONMENT: Production
ConnectionStrings__alpconnexdb: >-
Host=database;Port=5432;Database=${POSTGRES_DB};
Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}
ConnectionStrings__redis: valkey:6379
# Replace with the IP address of your host machine
Cors__AllowedOrigins: "http://${HOST_IP}:4200"
ports:
- "8080:8080"
depends_on:
database:
condition: service_healthy
valkey:
condition: service_healthy
restart: unless-stopped
networks:
- connector-net
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ═══════════════════════════════════════════
# Valkey — Process Image Data Store
# ═══════════════════════════════════════════
valkey:
image: valkey/valkey:8.0
# ports:
# - "6379:6379" # Uncomment for external Valkey access
volumes:
- valkey-data:/data
command: valkey-server --appendonly yes --appendfsync everysec
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
restart: unless-stopped
networks:
- connector-net
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ═══════════════════════════════════════════
# IEC 104 Connector
# ═══════════════════════════════════════════
iec104-server:
image: registry.alpscale.io/alp-connex/alp-connex-iec104:${TAG_IEC104}
ports:
- "2404:2404"
command: >-
--id ${IEC104_CONFIG_ID}
--log-level info
redis --host valkey --port 6379
depends_on:
valkey:
condition: service_healthy
restart: unless-stopped
networks:
- connector-net
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ═══════════════════════════════════════════
# MQTT Connector
# ═══════════════════════════════════════════
mqtt-client:
image: registry.alpscale.io/alp-connex/alp-connex-mqtt:${TAG_MQTT}
command: >-
--id ${MQTT_CONFIG_ID}
--log-level info
--mapping mapping.json
mqtt --broker mosquitto --port 1883
redis --host valkey --port 6379
volumes:
- ./mqtt/config/mapping.json:/app/mapping.json:ro
depends_on:
valkey:
condition: service_healthy
mosquitto:
condition: service_healthy
restart: unless-stopped
networks:
- connector-net
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ═══════════════════════════════════════════
# Eclipse Mosquitto — MQTT Broker
# ═══════════════════════════════════════════
mosquitto:
image: eclipse-mosquitto:2
ports:
- "1883:1883"
volumes:
- ./mosquitto/config:/mosquitto/config:ro
- mosquitto-data:/mosquitto/data
healthcheck:
test: ["CMD-SHELL", "mosquitto_sub -t '$$SYS/#' -C 1 -W 3"]
interval: 10s
timeout: 5s
retries: 3
start_period: 5s
restart: unless-stopped
networks:
- connector-net
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ═══════════════════════════════════════════
# Networks & Volumes
# ═══════════════════════════════════════════
networks:
connector-net:
driver: bridge
volumes:
db-data:
valkey-data:
mosquitto-data:
Step 3 — Mosquitto Configuration
Create the Mosquitto configuration file at mosquitto/config/mosquitto.conf:
listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
This is a minimal configuration for getting started. For production, consider enabling authentication and TLS. See the Eclipse Mosquitto documentation.
Step 4 — Mapping File (Optional)
If you want to use a file-based mapping instead of (or in addition to) the Management UI, create a mapping file at mqtt/config/mapping.json. See the MQTT Connector documentation for the file format and examples.
If you are using the Management UI to manage mappings, you can create an empty mapping file as a placeholder:
{
"version": "1.0.0",
"mappings": []
}
Starting the Stack
From the alp-connex/ directory:
# Start all services in the background
docker compose up -d
# Watch the startup logs
docker compose logs -f
The first start will pull all images, which may take a few minutes depending on your internet connection.
Verifying the Deployment
Check Service Health
docker compose ps
All services should show Up and healthy (where applicable):
NAME STATUS PORTS
database Up (healthy)
frontend Up 0.0.0.0:4200->8080/tcp
backend Up 0.0.0.0:8080->8080/tcp
valkey Up (healthy)
iec104-server Up 0.0.0.0:2404->2404/tcp
mqtt-client Up
mosquitto Up (healthy) 0.0.0.0:1883->1883/tcp
Access the Management UI
Open a browser and navigate to:
http://<HOST_IP>:4200
Test the MQTT Broker
From the host or another machine on the network, publish a test message:
mosquitto_pub -h <HOST_IP> -p 1883 -t "test/topic" -m '{"value": 42}'
Check Connector Logs
# IEC 104 Connector
docker compose logs iec104-server
# MQTT Connector
docker compose logs mqtt-client
Stopping the Stack
# Stop all services (data is preserved in volumes)
docker compose down
# Stop and remove all data (fresh start)
docker compose down -v
Troubleshooting
Services fail to start
| Symptom | Cause | Solution |
|---|---|---|
image not found | Wrong image tag or not logged in to registry | Verify TAG_* values in .env and run docker login registry.alpscale.io |
| Port already in use | Another service occupies the port | Check with ss -tlnp (Linux) or netstat -an and stop the conflicting service |
| Database health check fails | PostgreSQL not ready | Wait 30 seconds and check again; verify POSTGRES_* variables |
MQTT Connector not receiving data
- Check that the Mosquitto broker is healthy:
docker compose ps mosquitto - Verify the mapping file is correctly mounted:
docker compose exec mqtt-client ls /app/mapping.json - Check connector logs for errors:
docker compose logs mqtt-client - Ensure the IoT device publishes to a topic that matches a mapping entry
IEC 104 Connector not reachable
- Verify port 2404 is published:
docker compose ps iec104-server - Check firewall rules on the host machine
- Ensure the SCADA client is configured to connect to
<HOST_IP>:2404 - Check connector logs:
docker compose logs iec104-server
Frontend cannot reach the API
- Verify
HOST_IPin.envis correct and reachable from your browser - Ensure
API_URLandCors__AllowedOriginsboth use the same IP - Check that port 8080 is not blocked by a firewall
For connector-specific configuration, see the IEC 104 Connector and MQTT Connector documentation.