Skip to main content

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 IEC 104 Inbound Connector reading data from remote IEC 104 servers
  • The MQTT Connector ingesting data from IoT devices
  • The REST Outbound Connector exposing data via HTTP API and webhooks
  • An Eclipse Mosquitto MQTT broker

Prerequisites

RequirementMinimum
ArchitectureARM64 (e.g. Raspberry Pi 4/5, NVIDIA Jetson, AWS Graviton)
Docker Engine24.0 or newer
Docker ComposeV2 (included with Docker Engine)
RAM2 GB free
Disk5 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_IEC104_IN=x.y.z
TAG_MQTT=x.y.z
TAG_REST_OUT=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
IEC104_IN_CONFIG_ID=00000000-0000-0000-0000-000000000000
MQTT_CONFIG_ID=00000000-0000-0000-0000-000000000000
REST_OUT_CONFIG_ID=00000000-0000-0000-0000-000000000000

# ──────────────────────────────────────────
# MQTT Broker Authentication (optional)
# ──────────────────────────────────────────
# If your MQTT broker requires authentication, set the credentials here.
# MQTT_BROKER_USERNAME=
# MQTT_BROKER_PASSWORD=

Important: Replace HOST_IP with your actual machine IP and POSTGRES_PASSWORD with 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
# If your MQTT broker requires authentication, add to the mqtt subcommand:
# mqtt --broker mosquitto --port 1883 --username <user> --password <pass>
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"

# ═══════════════════════════════════════════
# IEC 104 Inbound Connector
# ═══════════════════════════════════════════
iec104-client:
image: registry.alpscale.io/alp-connex/alp-connex-iec104-in:${TAG_IEC104_IN}
command: >-
--id ${IEC104_IN_CONFIG_ID}
--log-level info
client --host <REMOTE_IEC104_HOST> --port 2404
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"

# ═══════════════════════════════════════════
# REST Outbound Connector
# ═══════════════════════════════════════════
rest-out:
image: registry.alpscale.io/alp-connex/alp-connex-rest-out:${TAG_REST_OUT}
ports:
- "5232:8080"
environment:
Id: ${REST_OUT_CONFIG_ID}
ConnectionStrings__Redis: valkey:6379
depends_on:
valkey:
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
iec104-client Up
mqtt-client Up
rest-out Up 0.0.0.0:5232->8080/tcp
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}'

Test the REST API

If the REST Outbound Connector is deployed, query its endpoint:

curl http://<HOST_IP>:5232/api/datapoints

Check Connector Logs

# IEC 104 Connector (outbound server)
docker compose logs iec104-server

# IEC 104 Inbound Connector (client)
docker compose logs iec104-client

# MQTT Connector
docker compose logs mqtt-client

# REST Outbound Connector
docker compose logs rest-out

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

SymptomCauseSolution
image not foundWrong image tag or not logged in to registryVerify TAG_* values in .env and run docker login registry.alpscale.io
Port already in useAnother service occupies the portCheck with ss -tlnp (Linux) or netstat -an and stop the conflicting service
Database health check failsPostgreSQL not readyWait 30 seconds and check again; verify POSTGRES_* variables

MQTT Connector not receiving data

  1. Check that the Mosquitto broker is healthy: docker compose ps mosquitto
  2. Verify the mapping file is correctly mounted: docker compose exec mqtt-client ls /app/mapping.json
  3. Check connector logs for errors: docker compose logs mqtt-client
  4. Ensure the IoT device publishes to a topic that matches a mapping entry

IEC 104 Connector not reachable

  1. Verify port 2404 is published: docker compose ps iec104-server
  2. Check firewall rules on the host machine
  3. Ensure the SCADA client is configured to connect to <HOST_IP>:2404
  4. Check connector logs: docker compose logs iec104-server

IEC 104 Inbound Connector not connecting

  1. Verify the remote IEC 104 server host and port are correct in the command or Management UI
  2. Check that the remote server is reachable from the Docker network: docker compose exec iec104-client ping <REMOTE_HOST>
  3. The connector retries every 2 seconds automatically — check logs for connection errors: docker compose logs iec104-client

REST Outbound Connector not reachable

  1. Verify port 5232 is published: docker compose ps rest-out
  2. Test the API: curl http://<HOST_IP>:5232/api/datapoints
  3. If webhooks are not delivering, check the webhook URLs and connector logs: docker compose logs rest-out

Frontend cannot reach the API

  1. Verify HOST_IP in .env is correct and reachable from your browser
  2. Ensure API_URL and Cors__AllowedOrigins both use the same IP
  3. Check that port 8080 is not blocked by a firewall

For connector-specific configuration, see the IEC 104 Connector, IEC 104 Inbound Connector, MQTT Connector, and REST Outbound Connector documentation.