Docker Swarm deployment stack for the android-sms-gateway SMS gateway platform. This repository contains the compose stacks, service configuration, and monitoring setup that run the production gateway: reverse proxy, database, cache, application services, alerting, and dashboards.
- android-sms-gateway Infrastructure
The platform delivers SMS messages through mobile devices and third-party channels. This repository packages all server-side infrastructure for a single- or multi-node deployment:
- Problem: The gateway needs a production-grade runtime: TLS termination, persistent storage, scheduled backups, monitoring, and alerting.
- Solution: Four self-contained Docker Swarm stacks, declarative service configuration, and secrets management through Swarm secrets.
- Intended users: Operators and developers who deploy or maintain the gateway infrastructure.
Everything here is configuration-as-code: no application source code lives in this repository, only the stacks that run it.
flowchart LR
WAN[Internet] -->|80/443, 587/465, 2775/2776| TR[Traefik v3]
subgraph swarm[Swarm cluster]
TR -->|HTTPS| API[backend + worker]
TR -->|SMPP/SMPPS| SMPP[smpp-server]
TR -->|SMTP/SMTPS| SMTP[email-to-sms]
TR -->|HTTPS| WH[webhook-tester]
TR -->|HTTPS| WD[web-dashboard]
TR -->|HTTPS| CA[ca-backend]
TR -->|HTTPS| ADMIN[phpMyAdmin / Redis Commander]
API --> DB[(MariaDB)]
API --> RD[(Redis)]
SMPP --> API
SMTP --> API
WD --> API
CA --> RD
DB --> BK[db-backup: S3]
BK -. cron .-> CRJ[swarm-cronjob]
PROM[Prometheus] -->|scrape| TR & API & CA & SMPP & SMTP & WD
PROM --> AM[Alertmanager] -->|Telegram / email| OPS[Operators]
GRAF[Grafana] --> PROM
end
POR[Portainer] -. agent .-> swarm
| File | Stack name | Contents |
|---|---|---|
compose.infra.yml |
infra |
Traefik v3 reverse proxy, MariaDB, Redis, phpMyAdmin, Redis Commander, S3 backup job, swarm-cronjob. Creates the internal and public overlay networks. |
compose.app.yml |
app |
Backend API + worker, ca-backend, smpp-server, email-to-sms, webhook-tester, web-dashboard. |
compose.monitoring.yml |
monitoring |
Prometheus, Alertmanager, cAdvisor, Grafana. |
compose.portainer.yml |
portainer |
Portainer CE and its agent (optional management UI). |
The infra stack must be deployed first: the app and monitoring stacks consume the internal and public networks and Swarm secrets it requires.
- TLS by default: Traefik terminates HTTPS with Let's Encrypt certificates; the
cloudflarecert resolver uses DNS-01 challenges for SMPPS, SMTPS, and public HTTPS routes. - HTTP, SMTP, and SMPP gateways: traffic routing on ports 80/443 (HTTPS), 587/465 (SMTP/SMTPS), and 2775/2776 (SMPP/SMPPS).
- Mobile push: backend sends Firebase Cloud Messaging (FCM) notifications from a Google service-account credential.
- Rate limiting and access control: per-route rate limits for the API and webhook endpoints; basic auth and IP allowlists for dashboards and metrics.
- Scheduled database backups: MariaDB dumps uploaded to S3 on a cron schedule via swarm-cronjob.
- Certificate authority:
ca-backendissues short-lived device certificates signed by a root CA stored in Swarm secrets. - Monitoring and alerting: Prometheus scrapes services labeled
prometheus.io/scrape=true; Alertmanager routescriticalalerts to Telegram andwarning/infoto email; Grafana for visualization. - Container metrics: cAdvisor runs globally on every node.
- Multi-node placement: services pin to labeled nodes (
redis,db,traefik,cronjob,prometheus,grafana,portainer). - Dashboard access: phpMyAdmin, Redis Commander, and the Traefik dashboard behind basic auth; Prometheus and Alertmanager behind IP allowlists; Grafana with login.
- CI hygiene: GitHub Actions close stale issues/PRs and strip
ready/deployedlabels on new pushes.
- Docker Engine with Swarm mode enabled (one or more Linux nodes; the
portaineragent is global on Linux nodes only). - A public domain with DNS records pointing at the cluster (see Usage for the hostnames to expose).
- A Cloudflare API token with DNS edit permission (used for ACME DNS-01 challenges).
- Access to a terminal on a Swarm manager node.
-
Initialize or join the Swarm (run on the first manager):
docker swarm init
-
Label nodes for service placement. Each service pins to a node label; labels that do not exist simply stay empty until their services are deployed:
docker node update --label-add redis=true <manager-node> docker node update --label-add db=true <db-node> docker node update --label-add traefik=true <manager-node> docker node update --label-add cronjob=true <manager-node> docker node update --label-add prometheus=true <monitoring-node> docker node update --label-add grafana=true <monitoring-node> docker node update --label-add portainer=true <manager-node>
-
Create the external secrets referenced by the stacks (values are operator-provided):
docker secret create mariadb_root_password - docker secret create users.htpasswd - docker secret create root-ca.crt - docker secret create root-ca.key - docker secret create telegram_bot_token - docker secret create email_password - docker secret create grafana_admin_password -
All secrets are declared
external: truein the compose files, so they must exist before stack deployment. -
Create the environment file from the template and fill in your values:
cp .env.example .env
See Configuration for every variable.
.envis gitignored.docker stack deployinterpolates variables from the shell environment, not automatically from.env. Load it into the shell before each deployment (for example,set -a; source .env; set +a). Required variables such asROOT_DOMAINmust be set or deployment will fail or substitute empty values.
Deploy the stacks in order: infra first (it creates the internal and public overlay networks), then app, monitoring, and optionally portainer.
docker stack deploy -c compose.infra.yml infraThis creates the internal and public overlay networks, the redis-data, mariadb-data, and letsencrypt volumes, and starts Traefik, MariaDB, Redis, Redis Commander, phpMyAdmin, and the cron scheduler.
docker stack deploy -c compose.app.yml appDeploys the backend API, worker, CA backend, SMPP server, email-to-SMS gateway, webhook tester, and web dashboard. The worker service starts with zero replicas; scale it if background tasks are needed.
docker stack deploy -c compose.monitoring.yml monitoringDeploys Prometheus, Alertmanager, cAdvisor (global), and Grafana.
docker stack deploy -c compose.portainer.yml portainerDeploys Portainer CE (published through the Swarm routing mesh on port 9443; the service task runs on a manager node) and a global agent on all Linux nodes.
Compose configs are versioned with STACK_VERSION. After editing a config file, bump STACK_VERSION in .env and re-deploy the stack that uses it to push the updated config:
- Traefik (
traefik.yml,dynamic.yml) and MariaDB configs →docker stack deploy -c compose.infra.yml infra - Backend
backend/config.yml→docker stack deploy -c compose.app.yml app - Prometheus and Alertmanager configs →
docker stack deploy -c compose.monitoring.yml monitoring
All configuration lives in .env (copy of .env.example) plus Swarm secrets. Variable names use __ as a section separator (for example, DB_BACKUP__SCHEDULE).
| Variable | Purpose | Default | Required |
|---|---|---|---|
TIMEZONE |
Container timezone (TZ) for all services; also used as backend DATABASE__TIMEZONE |
UTC |
no |
ROOT_DOMAIN |
Base domain for all public hostnames routed by Traefik | - | yes |
STACK_VERSION |
Version suffix for Swarm config names; bump to force config re-deployment | 0 |
no |
| Variable | Purpose | Default | Required |
|---|---|---|---|
DB_BACKUP__AWS_REGION |
AWS region of the backup S3 bucket | us-east-1 |
no |
DB_BACKUP__AWS_ACCESS_KEY_ID |
IAM access key with write access to the backup bucket | - | yes |
DB_BACKUP__AWS_SECRET_ACCESS_KEY |
IAM secret key paired with the access key ID | - | yes |
DB_BACKUP__PASSWORD |
Password of the MariaDB backup user |
- | yes |
DB_BACKUP__OPTIONS |
Extra mysqldump options for the backup job | --skip-ssl |
no |
DB_BACKUP__STORAGE_URL |
S3 destination of the dump (s3://bucket/prefix) |
- | yes |
DB_BACKUP__SCHEDULE |
Cron expression that triggers the backup job (single quotes required) | '@daily' |
no |
| Variable | Purpose | Default | Required |
|---|---|---|---|
DB_ADMIN_AUTH |
htpasswd pair protecting phpMyAdmin (pma.<domain>); bcrypt hash, $ escaped as $$ |
- | yes |
DASHBOARD_AUTH |
htpasswd pair protecting the Traefik dashboard (admin.<domain>) |
- | yes |
TRAEFIK__METRICS_IP_WHITELIST |
IP/CIDR ranges allowed to reach the Traefik metrics middleware | 127.0.0.1,192.168.0.0/16 |
no |
TRAEFIK__CF_DNS_API_TOKEN |
Cloudflare API token (DNS edit) for the ACME DNS-01 cert resolver | - | yes |
| Variable | Purpose | Default | Required |
|---|---|---|---|
PROMETHEUS__IP_ALLOWLIST |
IP/CIDR ranges allowed to open the Prometheus UI | 127.0.0.1,192.168.0.0/16 |
no |
ALERTMANAGER__IP_ALLOWLIST |
IP/CIDR ranges allowed to open the Alertmanager UI | 127.0.0.1,192.168.0.0/16 |
no |
| Variable | Purpose | Default | Required |
|---|---|---|---|
HTTP__PROXY_HEADER |
Header carrying the real client IP, read by backend, ca-backend, email-to-sms, and web-dashboard | X-Forwarded-For |
no |
HTTP__PROXIES |
Comma-separated trusted proxy IP ranges | - | yes |
API_SUBDOMAIN |
Subdomain serving the backend API (<value>.<ROOT_DOMAIN>) |
api |
no |
| Variable | Purpose | Default | Required |
|---|---|---|---|
BACKEND__DATABASE__HOST |
MariaDB hostname reachable by backend and worker | db |
no |
BACKEND__DATABASE__USER |
MariaDB login user for backend and worker | backend |
no |
BACKEND__DATABASE__PASSWORD |
MariaDB password for the user above | - | yes |
BACKEND__DATABASE__DATABASE |
Default database name used by backend and worker | backend |
no |
BACKEND__DATABASE__MAX_OPEN_CONNS |
Max open database connections (0 = unlimited) |
0 |
no |
BACKEND__DATABASE__MAX_IDLE_CONNS |
Max idle database connections (0 = unlimited) |
0 |
no |
| Variable | Purpose | Default | Required |
|---|---|---|---|
FCM__CREDENTIALS_JSON |
Google service-account key JSON (single-line string) used for push notifications | - | yes |
| Variable | Purpose | Default | Required |
|---|---|---|---|
CSR__TTL |
Validity of certificates issued by ca-backend |
24h |
no |
CSR__CA_CERT_PATH |
Path to the root CA certificate inside ca-backend (Swarm secret root-ca.crt) |
/run/secrets/ca.crt |
no |
CSR__CA_KEY_PATH |
Path to the root CA private key inside ca-backend (Swarm secret root-ca.key) |
/run/secrets/ca.key |
no |
| Variable | Consumed by | Default | Required |
|---|---|---|---|
SMPP__GATEWAY__API_BASE_URL |
smpp-server (backend 3rd-party API base URL) |
http://backend:3000/api/3rdparty/v1 |
no |
EMAIL_TO_SMS__GATEWAY__API_BASE_URL |
email-to-sms (backend 3rd-party API base URL) |
http://backend:3000/api/3rdparty/v1 |
no |
WEBHOOK__REDIS_DSN |
webhook-tester Redis connection DSN |
redis://redis:6379/1 |
no |
WEB_DASHBOARD__GATEWAY__URL |
web-dashboard (backend 3rd-party API base URL) |
http://backend:3000/api/3rdparty/v1 |
no |
Swarm secrets (all declared external: true):
| Secret | Used by | Purpose |
|---|---|---|
mariadb_root_password |
db |
MariaDB root password |
users.htpasswd |
Traefik file middleware | Basic auth for Redis Commander and metrics endpoints |
root-ca.crt, root-ca.key |
ca-backend |
Root CA certificate and key for device certificate issuance |
telegram_bot_token |
Alertmanager | Telegram bot token for critical alerts |
email_password |
Alertmanager | SMTP password for email alerts |
grafana_admin_password |
Grafana | Admin password (read from file at startup) |
All hostnames use ROOT_DOMAIN as the suffix and TLS is terminated by Traefik.
| Host | Service | Access control |
|---|---|---|
api.<domain> |
Backend API: /3rdparty, /upstream, /mobile paths |
Route-specific rate limits |
ca.<domain> |
Certificate issuance API | POST limited to 1 per minute |
smpp.<domain> |
SMPP server (TCP 2775/2776) + /metrics |
Metrics behind auth + IP allowlist |
smtp.<domain> |
Email-to-SMS gateway (SMTP 587 / SMTPS 465) + /metrics |
Metrics behind auth + IP allowlist |
webhook.<domain> |
Webhook testing tool (capture + inspect) | Rate limited (10 req/s general, 5 req/s capture) |
dashboard.<domain> |
Web dashboard | - |
admin.<domain> |
Traefik dashboard | Basic auth (DASHBOARD_AUTH) |
pma.<domain> |
phpMyAdmin | Basic auth (DB_ADMIN_AUTH) |
redis.<domain> |
Redis Commander | Basic auth (users.htpasswd) |
prometheus.<domain> |
Prometheus UI | IP allowlist |
alerts.<domain> |
Alertmanager UI | IP allowlist |
mon.<domain> |
Grafana | Login (grafana_admin_password) |
https://<node>:9443 |
Portainer CE | Login (first-run admin setup) |
The Traefik http entrypoint (port 80) redirects all traffic to HTTPS.
- Backups: the
db-backupservice runs on theDB_BACKUP__SCHEDULEcron and uploads a MariaDB dump toDB_BACKUP__STORAGE_URL; it runs as a cron job, sodocker service psshows no running tasks between runs. - Config updates: bump
STACK_VERSIONand re-deploy the stack to roll out changed config files (see Installation). - Scaling: the backend
workerservice is deployed withreplicas: 0; scale it explicitly when background processing is required. - Metrics: services labeled
prometheus.io/scrape=truepublish Prometheus metrics; scrape targets are discovered via those labels on theinternalnetwork.
This repository follows a standard GitHub flow:
- Fork the repository and create a feature branch.
- Keep changes scoped to a single concern (a compose file, a config file, or docs).
- Open a pull request with a clear description of the change and any verification performed.
- Add the
readylabel once the PR passes review, anddeployedonce the change is live.
Note the automation in .github/workflows/:
close-issues-prs.ymlmarks issues/PRs stale after 7 days of inactivity and closes them after 7 more days; assigned items are exempt.pr-labels.ymlremoves thereadyanddeployedlabels whenever new commits are pushed to a PR.
Distributed under the Apache License 2.0. See LICENSE for details.
Project repository: https://github.com/android-sms-gateway/infra
- Traefik - reverse proxy and TLS termination
- Prometheus and Alertmanager - metrics and alert routing
- Grafana - dashboards
- cAdvisor - container metrics
- MariaDB - primary database
- Redis - cache, pub/sub, and webhook session storage
- phpMyAdmin - database administration
- Portainer - Swarm management UI
- swarm-cronjob - scheduled job execution
- webhook-tester - webhook testing