§ ShopBot — Telegram commerce demo
Architecture — two services, one private network
ShopBot is two containers that trust each other privately and expose only what the public needs. The shape is deliberate: it mirrors how you'd actually deploy a bot that mustn't leak its backend.
The picture
Telegram Browser Paystack
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────────────────────────┐
│ n8n.oaoisme │ │ inventory.oaoisme.top │ (nginx, HTTPS,
│ .top │ │ dashboard + /api + webhook │ wildcard cert)
└──────┬──────┘ └───────────────┬──────────────┘
127.0.0.1:5678 127.0.0.1:3700
│ │
┌──────▼────────────────────────────────▼──────┐
│ docker network "shopnet" │
│ n8n ──HTTP──► http://inventory:3700/api │
│ (X-API-Key on writes) │
└───────────────────────────────────────────────┘
Why the ports are bound to 127.0.0.1
The box's firewall only allows 22/80/443 from the internet. A container that
published a port on 0.0.0.0 would punch straight through that firewall. So
every published port is bound to 127.0.0.1 — the loopback interface — and
nginx is the only way in, terminating TLS with the wildcard *.oaoisme.top
certificate and reverse-proxying to the loopback port. You get HTTPS for free
and nothing is exposed that shouldn't be.
Why n8n calls the backend privately
The two containers share a docker bridge network (shopnet), so n8n reaches the
backend as http://inventory:3700 — using docker's internal DNS, never
going out to the public internet and back. The public inventory.oaoisme.top
hostname is reserved for the two things that genuinely must be reachable from
outside: the dashboard (a person's browser) and the Paystack webhook
(Paystack's servers). Bot-to-backend traffic stays on the private wire.
The trust boundary
- Reads (list products, check availability, order status) are open — they leak nothing sensitive and the dashboard needs them.
- Writes that matter (create an order, create a payment link) require an
X-API-Keyheader. Only n8n knows the key (it's in the stack's.envand in an n8n credential). So a random internet visitor can browse the catalogue but can't place orders in someone's name. - The Paystack webhook is open but signature-verified — see Payments.
State & persistence
- The backend's SQLite database lives on a named docker volume
(
inventory_data), not a host bind-mount. That matters: the container runs as the non-rootnodeuser, and a root-owned host bind-mount would deny it write access (the first boot crashed withSQLITE_CANTOPENuntil this was changed). A named volume inherits the image'snode-owned/app/data, so writes work. - n8n keeps its own state (the workflow, credentials, encryption key) in the
n8n_datavolume. ItsN8N_ENCRYPTION_KEYmust stay stable or saved credentials become unreadable.
Files
/root/apps/shopbot/
├── docker-compose.yml # both services, shopnet, 127.0.0.1 ports, volumes
├── .env / .env.example # INVENTORY_API_KEY, N8N_ENCRYPTION_KEY, Paystack
├── design.md # the dashboard's visual identity
├── RUNBOOK.md # go-live steps (the three credentials)
├── inventory/ # the Fastify + SQLite service (see its page)
└── n8n/workflows/ # the importable Telegram-agent workflow JSON
Subpages