SAMM for MikroTik ISPs — install via the compose bundle at github.com/mhdhaidarah/samm-docker (don
10K+
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.
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.
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:
http://<host>:8000/adminhttp://<host>:8000/Default credentials: admin / admin — change on first login.
You can run SAMM on Windows via Docker Desktop + WSL2. Not recommended for production because:
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).
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:
container package, enable it (/system device-mode update container=yes, reboot), and format a USB/microSD/NVMe disk as ext4.docker-compose.yml (from the
latest release), set the formatted disk as storage, submit.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 BOTHcommand: ["<role>"]andenvironment: SAMM_ROLE: <role>for the same role;entrypoint.shreads${SAMM_ROLE:-${1:-api}}so either source works. Keep both fields in lockstep when adding app services. (wa-bridgeneeds neither — its role is baked into the image's CMD, which is exactly what makes it RouterOS-proof.)
| Container | Role |
|---|---|
postgres | Postgres 16 on the private compose bridge |
samm-api | FastAPI admin + user portal (exposes :8000) |
samm-radius | Time-driven AAA + CoA sender |
samm-worker | MikroTik inventory + ICMP ping sweep (NET_RAW) |
samm-notification | Email/Telegram/SMS/WhatsApp notification outbox drain |
samm-telegram | Long-polling Telegram bot |
freeradius | Stock freeradius/freeradius-server:3, SAMM config mounted |
wa-bridge | WhatsApp 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 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.
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.
install.sh from
mhdhaidarah/samm on this host, apt purge freeradius postgresql before
installing the docker variant.freeradius container exposes 1812 + 1813 via standard compose port
mapping (no host networking).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 runEach 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.
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.
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.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.wa-bridge ships in the standard stack but idles until an operator picks
the "qr" WhatsApp provider. docker compose stop wa-bridge if unwanted./etc/raddb (not /etc/freeradius/3.0) and ships without libpq.Three volumes carry state worth keeping:
samm_pgdata — Postgres datasamm_etcsamm — Fernet key (secret.key) and the rendered samm.yamlsamm_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 .
These work in the bare-OS install but not in the Docker variant:
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.
The published images are multi-arch — linux/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.
cd /opt/samm-docker
docker compose down -v # -v wipes the postgres volume AND the Fernet key
sudo rm -rf /opt/samm-docker
Built by SecuryTik.
Content type
Image
Digest
sha256:10e4c9cc3…
Size
267.6 MB
Last updated
about 9 hours ago
docker pull mhdhaidarah/samm