Sign inSign up

mhdhaidarah/samm

By mhdhaidarah

Updated about 9 hours ago

SAMM for MikroTik ISPs — install via the compose bundle at github.com/mhdhaidarah/samm-docker (don

Image
Networking
Security
Monitoring & observability
0

10K+

mhdhaidarah/samm repository overview

SAMM — Docker distribution

The official Docker image for SAMM (SecuryTik Active MikroTik Manager) — a FreeRADIUS + PostgreSQL + FastAPI AAA stack for MikroTik ISPs: PPPoE / Hotspot / DHCP-IPoE access, IPv6 dual-stack, plans & limits, double-entry billing with online payments (Stripe / Binance / PayPal) + QuickBooks/Xero export, REST API + webhooks, admin & user portals, Email/Telegram/SMS/WhatsApp notifications, and an HA replication runbook.

▶ Start here → github.com/mhdhaidarah/samm-docker Don't wire these images up by hand — use the compose bundle from the samm-docker releases. It pins the release version tags (app, freeradius, wa-bridge) and wires the whole stack together, including the optional WhatsApp QR bridge. One-line install below, MikroTik RouterOS container instructions in the docs.

One file, every platform

There's a single docker-compose.yml. It runs the same everywhere — a Linux server (x86 or ARM), Windows Docker Desktop, and MikroTik RouterOS 7.23+ containers (arm64 or x86). The images are multi-arch and pulled by tag, so each machine gets its own CPU architecture automatically. Nothing to choose, no .env, no per-arch file.

Quick install (Ubuntu / Debian)

curl -fsSL https://github.com/mhdhaidarah/samm-docker/releases/latest/download/install.sh \
    | sudo bash

The installer detects Docker, installs it if missing, drops a docker-compose.yml into /opt/samm-docker/ with a strong random database password already filled in, and brings the stack up. No .env — the compose file is the single source of truth (re-runs and auto-updates keep your password).

After ~30 seconds:

  • Admin portal http://<host>:8000/admin
  • User portal http://<host>:8000/
  • RADIUS auth UDP 1812 on the host's LAN IP
  • RADIUS accounting UDP 1813 on the host's LAN IP

Default credentials: admin / adminchange on first login.

Windows (Docker Desktop) — evaluation only

You can run SAMM on Windows via Docker Desktop + WSL2. Not recommended for production because:

  • Windows sleep / hibernate / lid-close stops the containers
  • Boot-restart only fires when WSL boots (not on Windows boot)
  • Daily auto-update cron only runs while WSL is alive
  • No 24/7 reliability for RADIUS auth and accounting

Use it for evaluation, then deploy production on a Linux VM or small physical box.

Evaluation steps from PowerShell (no WSL terminal needed; Docker Desktop's WSL backend handles it):

mkdir C:\samm-docker
cd C:\samm-docker
curl.exe -fLO https://github.com/mhdhaidarah/samm-docker/releases/latest/download/docker-compose.yml
# optional: notepad docker-compose.yml and find & replace
#           change-me-strong-random-string with your own password
docker compose pull
docker compose up -d

Watch it boot in Docker Desktop → Containers → the samm stack expands into 8 services. Open http://localhost:8000/admin (login admin / admin).

If MikroTik will hit this Windows host, allow UDP 1812/1813 through Windows Firewall:

New-NetFirewallRule -DisplayName "SAMM RADIUS" -Direction Inbound -Protocol UDP -LocalPort 1812,1813 -Action Allow

Tear down: docker compose down -v (the -v wipes the postgres + Fernet key volumes).

MikroTik (RouterOS 7.23+ container feature)

Run SAMM directly on a MikroTik router — the same docker-compose.yml, no special file. RouterOS 7.23+ pulls the correct architecture from the multi-arch image tags automatically (arm64 on router hardware, amd64 on x86 RouterOS/CHR).

Steps — in WebFig/WinBox:

  1. Install the container package, enable it (/system device-mode update container=yes, reboot), and format a USB/microSD/NVMe disk as ext4.
  2. Container → Apps → + New → YAML, paste docker-compose.yml (from the latest release), set the formatted disk as storage, submit.
  3. RouterOS pulls each image (right arch) and wires the services up.

Sitting at "waiting for reverse proxy"? On RouterOS 7.23+, untick Use HTTPS in the Apps dialog and re-deploy. That option controls how RouterOS serves the app's web UI through its reverse proxy — not how images are pulled — so unticking it is safe.

Full walkthrough (prep, disk formatting, RADIUS wiring) lives at https://samm.securytik.com/docs#doc-install under Option D.

Note for maintainers. MikroTik's compose parser drops the command: field on YAML import. Every SAMM app service therefore carries BOTH command: ["<role>"] and environment: SAMM_ROLE: <role> for the same role; entrypoint.sh reads ${SAMM_ROLE:-${1:-api}} so either source works. Keep both fields in lockstep when adding app services. (wa-bridge needs neither — its role is baked into the image's CMD, which is exactly what makes it RouterOS-proof.)

What it ships

ContainerRole
postgresPostgres 16 on the private compose bridge
samm-apiFastAPI admin + user portal (exposes :8000)
samm-radiusTime-driven AAA + CoA sender
samm-workerMikroTik inventory + ICMP ping sweep (NET_RAW)
samm-notificationEmail/Telegram/SMS/WhatsApp notification outbox drain
samm-telegramLong-polling Telegram bot
freeradiusStock freeradius/freeradius-server:3, SAMM config mounted
wa-bridgeWhatsApp QR bridge (idles until the QR provider is used) — self-contained multi-arch image

The app + freeradius + wa-bridge images are all mhdhaidarah/samm (tags <ver> and latest, plus the freeradius- and wa-bridge- prefixes). Every user-facing tag is a clean 2-entry multi-arch index — amd64 and arm64, no attestation sub-manifests — so one compose file serves x86 hosts and arm64 alike, RouterOS included: the router picks its own architecture from the tag. The compose file references the rolling :latest tags and records the version it shipped with in its header comment. App source stays closed: it's built from Cython-compiled binaries — no .py source.

WhatsApp QR bridge (optional)

WhatsApp has two providers under Notifications → Channels → WhatsApp:

  • Meta WhatsApp Cloud API (official, recommended) — token + Phone-Number-ID, nothing extra to run; works out of the box.

  • Unofficial QR link (at your own risk) — a small Baileys sidecar that already runs as part of the stack (the wa-bridge service — nothing to enable). Just open the WhatsApp channel, pick Unofficial QR link, and scan the QR. It idles (no linked number, no traffic) until you do.

    The bridge is a self-contained image (mhdhaidarah/samm:wa-bridge-<ver>, multi-arch) with code + deps baked in — so it starts instantly and runs on normal Docker and on MikroTik's RouterOS container feature alike (no bind-mount, no command:). The linked session persists in the wabridge_auth volume; WA_BRIDGE_TOKEN is auto-generated into the compose file. Don't want it running? docker compose stop wa-bridge.

Boot startup

install.sh installs samm-docker.service (systemd, enabled). The stack comes back up after a host reboot even if you had run docker compose down before shutdown. Belt-and-suspenders alongside compose's restart: unless-stopped: the restart policy survives Docker daemon restarts; the systemd unit covers explicit-down + reboot.

Disable: sudo systemctl disable samm-docker.service.

Network

  • Host needs UDP 1812 + 1813 free. Coexisting with a bare-OS SAMM install is not supported — if you previously ran install.sh from mhdhaidarah/samm on this host, apt purge freeradius postgresql before installing the docker variant.
  • The freeradius container exposes 1812 + 1813 via standard compose port mapping (no host networking).
  • MikroTik NAS points to the host's LAN IP on 1812/1813. The shared secret is set in the SAMM admin portal under System → RADIUS.

Upgrading

install.sh sets up auto-update for you:

  • /opt/samm-docker/host-updater.sh — the upgrade script
  • /etc/cron.d/samm-docker — runs host-updater.sh daily at 04:00
  • /var/log/samm-update.log — captures stdout + stderr each run

Each run: query the latest release tag, download the matching docker-compose.yml (carrying your password over), then docker compose pull && docker compose up -d. No-op when nothing's new.

Disable auto-update: sudo rm /etc/cron.d/samm-docker. Change the schedule by editing that file.

Manual upgrade:

sudo curl -fsSL https://github.com/mhdhaidarah/samm-docker/releases/latest/download/install.sh \
    | sudo bash

Re-running install.sh preserves your password and WhatsApp-bridge token; it only refreshes docker-compose.yml and pulls the new image.

Which version am I running?

The compose file pulls :latest on purpose — one file that never goes stale, and docker compose pull && docker compose up -d is always the update. The tag therefore cannot tell you what you got; the image carries its own version:

docker inspect --format '{{index .Config.Labels "org.opencontainers.image.version"}}' \
  mhdhaidarah/samm:latest

The admin panel footer reports the same number.

Images are multi-arch (amd64 + arm64) and pulled by tag, so an Ubuntu server, Docker Desktop and a MikroTik router each get the build for their own CPU automatically. There is no architecture to choose and no separate file.

Why the compose file looks like that

The bundle is deliberately terse. The reasoning that used to live in its comments, kept here so it is not lost:

  • SAMM_ROLE duplicates command:. MikroTik's container feature drops the command: field on YAML import, so every service also carries a role env var. entrypoint.sh prefers SAMM_ROLE; the two always agree, so behaviour is identical on Docker and RouterOS.
  • Everything waits on samm-api: service_healthy, not service_started. samm-api applies the migrations and only reports healthy once uvicorn is serving, which happens after the last one. started merely means the container exists, so on an upgrade the daemons would run against the previous schema.
  • freeradius waits on samm-api too. It used to depend on postgres alone and would boot with the new queries.conf while migrations were still running; rlm_sql then errored on a function that did not exist yet, and sites-available/samm turns an rlm_sql error into Access-Reject — every user offline until migrations finished. RouterOS ignores depends_on entirely, so the image entrypoint gates on the schema as well.
  • Plain values only — no env interpolation, no YAML merge keys. RouterOS passes values literally and strict YAML 1.2 parsers choke on the rest.
  • wa-bridge ships in the standard stack but idles until an operator picks the "qr" WhatsApp provider. docker compose stop wa-bridge if unwanted.
  • The custom FreeRADIUS image exists because the stock one reads /etc/raddb (not /etc/freeradius/3.0) and ships without libpq.

Backup

Three volumes carry state worth keeping:

  • samm_pgdata — Postgres data
  • samm_etcsamm — Fernet key (secret.key) and the rendered samm.yaml
  • samm_wabridge_auth — the WhatsApp QR bridge's linked session (losing it just means re-scanning the QR — include it if you use the QR provider)

Losing samm_etcsamm means losing the Fernet key, which means you can no longer decrypt MikroTik API passwords stored in the DB. Back them up together:

sudo docker run --rm \
    -v samm_pgdata:/src/pgdata:ro \
    -v samm_etcsamm:/src/etcsamm:ro \
    -v samm_wabridge_auth:/src/wabridge_auth:ro \
    -v "$(pwd):/out" \
    alpine tar czf "/out/samm-backup-$(date +%F).tar.gz" -C /src .

Docker-variant limitations

These work in the bare-OS install but not in the Docker variant:

  • Staged license lockdown (soft/hard stops on samm-worker etc.) — the in-process license check still throttles the data plane and the reactivation wall in the admin portal still shows, but the lockdown enforcer doesn't stop containers.
  • Dynamic FreeRADIUS config reload — changes via the admin UI require docker compose restart freeradius to take effect.

The WireGuard and Cloudflare Tunnel admin pages are automatically hidden in the docker variant. They manage host-level systemd services that aren't reachable from inside the container; the admin sidebar hides them and the routes redirect with a "configure on the host" notice. Use the bare-OS install if you need built-in VPN / tunnel management.

Multi-arch

The published images are multi-archlinux/amd64 and linux/arm64 under the same tag. Every consumer resolves the right variant from the tag on its own: docker pull / Compose on a normal host (including Apple-silicon Macs, x86 and arm servers), and MikroTik RouterOS 7.23+ on the router itself — RB5009, hAP ax², CCR2004/2116/2216 and similar (arm64), or x86 RouterOS/CHR (amd64). One docker-compose.yml, nothing to choose. The file is plain YAML 1.2 (no merge keys) so RouterOS's parser reads it cleanly.

armv7 devices and the smaller mipsbe/smips MikroTik boards are not supported — the Python/FastAPI/Postgres/freeradius stack needs more headroom than those SKUs provide. The bare-OS install on a small Linux box remains the recommended production path for larger deployments; MikroTik-on-container suits small/branch sites.

Uninstall

cd /opt/samm-docker
docker compose down -v   # -v wipes the postgres volume AND the Fernet key
sudo rm -rf /opt/samm-docker

Support

Built by SecuryTik.

Tag summary

Content type

Image

Digest

sha256:10e4c9cc3

Size

267.6 MB

Last updated

about 9 hours ago

docker pull mhdhaidarah/samm