Skip to content

Docker Image

MOAS-Server is published as a container image on GitHub Container Registry (GHCR):

  • ghcr.io/m1dst/moas-server

This image packages the native moas-server-cli binary and a Docker entrypoint that maps environment variables to CLI arguments.

  • Linux runtime image based on Ubuntu 24.04.
  • moas-server-cli binary at /app/moas-server-cli.
  • Built-in example configs:
    • /app/config/sample-config.moas
    • /app/config/sample-network-config.moas
  • Default exposed ports:
    • 12059/tcp for client command and control
    • 12059/udp for client discovery

The publish workflow pushes multiple tags to GHCR:

  • latest
  • Version tags from Git tags, for example v2.1.0
  • Commit SHA tags (sha-*)

Use version tags in production for reproducible deployments.

Published GHCR images are multi-architecture:

  • linux/amd64
  • linux/arm64

So on ARM64 hosts (for example Raspberry Pi 64-bit or Apple Silicon Macs), docker pull ghcr.io/m1dst/moas-server:<tag> will automatically fetch the ARM64 variant.

Notes:

  • Multi-arch images are created by the GitHub publish workflow using Buildx.
  • On macOS, run with Docker Desktop. Network-based setups work normally; direct USB serial passthrough is typically not supported in Docker Desktop.
Terminal window
docker pull ghcr.io/m1dst/moas-server:latest

Or pin to a release:

Terminal window
docker pull ghcr.io/m1dst/moas-server:v2.0.0

Simulation mode is the safest first run because no physical switching occurs.

Terminal window
docker run --rm -it \
-e MOAS_SIM=true \
-e MOAS_LOG_LEVEL=info \
-p 12059:12059/tcp \
-p 12059:12059/udp \
ghcr.io/m1dst/moas-server:latest

Mount your config directory and point MOAS_CONFIG at a file in the mount.

Terminal window
docker run --rm -it \
-v "$PWD/moas:/config:ro" \
-e MOAS_CONFIG=/config/2x6.moas \
-e MOAS_LOG_LEVEL=info \
-p 12059:12059/tcp \
-p 12059:12059/udp \
ghcr.io/m1dst/moas-server:latest

Pass through the serial device to the container.

Terminal window
docker run --rm -it \
--device=/dev/ttyUSB0:/dev/ttyUSB0 \
-e MOAS_CONFIG=/app/config/sample-config.moas \
-e MOAS_LOG_LEVEL=info \
-p 12059:12059/tcp \
-p 12059:12059/udp \
ghcr.io/m1dst/moas-server:latest

If your config references a different serial device, update the .moas file accordingly.

No device passthrough is needed when using TCP transport.

Terminal window
docker run --rm -it \
-v "$PWD/moas:/config:ro" \
-e MOAS_CONFIG=/config/2x6-network.moas \
-e MOAS_LOG_LEVEL=info \
-p 12059:12059/tcp \
-p 12059:12059/udp \
ghcr.io/m1dst/moas-server:latest

Or use the bundled template:

Terminal window
docker run --rm -it \
-e MOAS_CONFIG=/app/config/sample-network-config.moas \
-e MOAS_LOG_LEVEL=info \
-p 12059:12059/tcp \
-p 12059:12059/udp \
ghcr.io/m1dst/moas-server:latest

Example Switches block for TCP transport:

"Switches" : [
{
"ID" : 1,
"Transport" : "TCP",
"Address" : "192.168.1.50",
"TCPPort" : 12058
}
]

Address and TCPPort are the hardware switch endpoint (commonly 12058), not the MOAS server listen port.

The Docker entrypoint maps these variables to CLI arguments:

  • MOAS_MODE default run
  • MOAS_CONFIG default /app/config/sample-config.moas
  • MOAS_LOG_LEVEL default info
  • MOAS_SIM default false (true or 1 enables --sim)
  • MOAS_DURATION unset by default (if set, adds --duration <value>)
  • 12059/udp: MOAS client discovery
  • 12059/tcp: MOAS client command and control
  • 12058/tcp: MOAS hardware switch control port when using TCP switch transport. This is outbound from the container to the switch, so you normally do not publish it with -p.
  • 12060/udp: optional - N1MM logger (only required when UseN1MMLogger=true)

Expose only the ports your setup needs.

If you pass arguments after the image name, the entrypoint executes moas-server-cli directly and ignores env-var defaults.

Terminal window
docker run --rm -it \
ghcr.io/m1dst/moas-server:latest \
validate /app/config/sample-config.moas info --sim

Run in detached mode:

Terminal window
docker run -d \
--name moas-server \
-v "$PWD/moas:/config:ro" \
-e MOAS_CONFIG=/config/m1dst_tcp.moas \
-e MOAS_LOG_LEVEL=info \
-p 12059:12059/tcp \
-p 12059:12059/udp \
ghcr.io/m1dst/moas-server:latest

Then inspect logs:

Terminal window
docker logs moas-server

Or follow logs live:

Terminal window
docker logs -f moas-server

A typical startup log looks like:

__ __ ___ _ ____ ___ ___
| \/ | / _ \ / \ / ___| |_ _| |_ _|
| |\/| | | | | | / _ \ \___ \ | | | |
| | | | | |_| | / ___ \ ___) | | | | |
|_| |_| \___/ /_/ \_\ |____/ |___| |___|
Master Of Antenna Switching (MOAS)
Developed by James Patterson, M1DST and Paul Young, K1XM.
Version: v2.0.0
Loading configuration: /config/m1dst_tcp.moas
Configuration loaded.
MOAS core running. Press Ctrl+C to stop.
Stopped.
Read 3905 lines from configuration file
Antenna 1 has fast transition with 17 but the reverse is not true
Antenna 2 has fast transition with 17 but the reverse is not true
Antenna 3 has fast transition with 17 but the reverse is not true
Antenna 18 conflicts with 2 but the reverse is not true
Antenna 18 has fast transition with 3 but the reverse is not true
Antenna 18 has fast transition with 17 but the reverse is not true
Linux TCP listener started on port 12059
Switch 1 port 192.168.10.94:12058 is not connected
Switch 1 port 192.168.10.94:12058 is connected
Switch 1 port 192.168.10.94:12058 is operational

If you are using Portainer and want to use a Stack, you can find an example below.

version: "3.8"

services:
  moas-server:
    # Override IMAGE in Portainer if needed.
    image: ${IMAGE:-ghcr.io/m1dst/moas-server:latest}
    container_name: moas-server
    restart: unless-stopped
    environment:
      MOAS_MODE: "run"
      # Network-switch default template.
      MOAS_CONFIG: "/config/2x6-network.moas"
      MOAS_LOG_LEVEL: "info"
      MOAS_SIM: "false"
      # Optional finite run for testing, for example "10".
      MOAS_DURATION: ""
    ports:
      # MOAS client command/control
      - "12059:12059/tcp"
      # MOAS client discovery
      - "12059:12059/udp"
      # Optional N1MM logger UDP input (enable only if UseN1MMLogger=true)
      # - "12060:12060/udp"
    volumes:
      # Host path containing your .moas files.
      - /opt/moas/config:/config:ro

    # Optional serial passthrough (uncomment if using USB serial hardware).
    # devices:
    #   - "/dev/ttyUSB0:/dev/ttyUSB0"

Default behavior of the stack:

  • Uses ghcr.io/m1dst/moas-server:latest (override with IMAGE if needed)
  • Mounts /opt/moas/config:/config:ro
  • Exposes 12059/tcp and 12059/udp
  • Includes optional serial passthrough lines you can uncomment
  • Container exits immediately:
    • Check MOAS_MODE and MOAS_CONFIG values.
    • Run docker logs <container> for startup errors.
  • Clients cannot discover server:
    • Ensure 12059/udp is published and allowed through firewall.
  • Clients cannot connect:
    • Ensure 12059/tcp is published and reachable.
  • USB serial hardware not detected:
    • Confirm --device mapping and permissions on /dev/ttyUSB*.
  • Config path errors:
    • Verify host volume mount path and container path in MOAS_CONFIG.
  • Prefer pinned tags (vX.Y.Z) over latest in production.
  • Keep .moas files mounted read-only when possible.
  • Use simulation mode before switching live hardware.
  • Limit network exposure to trusted station networks.