CODEX // oaoisme wiki

§ ShopBot — Telegram commerce demo

Architecture — two services, one private network

updated 2026-06-27

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-Key header. Only n8n knows the key (it's in the stack's .env and 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-root node user, and a root-owned host bind-mount would deny it write access (the first boot crashed with SQLITE_CANTOPEN until this was changed). A named volume inherits the image's node-owned /app/data, so writes work.
  • n8n keeps its own state (the workflow, credentials, encryption key) in the n8n_data volume. Its N8N_ENCRYPTION_KEY must 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