Deployment View
Describes the hardware and software environments in which the system runs, how software is mapped to infrastructure, and how components communicate across deployment nodes.
Development Environment — Docker Compose
During local development the entire infrastructure stack runs inside a single Docker bridge network (flowershop-dev). The API container connects to PostgreSQL, Redis, Mosquitto MQTT, RabbitMQ, and Keycloak within that network. The three portal processes run outside Docker (plain dotnet run) and reach the API on the exposed host port 8080. pgAdmin and Redis Commander are optional tooling containers activated with --profile tools and are never started by default.
flowchart TD
subgraph HostMachine["Developer Workstation (Windows 11)"]
subgraph DockerNetwork["Docker Bridge Network: flowershop-dev (172.21.0.0/16)"]
PG["postgres-dev\nPostgreSQL 15-alpine\n:5432\nDB: flowershop_iot_dev\n flowershop_iot_read_dev\n keycloak"]
REDIS["redis-dev\nRedis 7-alpine\n:6379\nAppendonly persistence"]
MQ["mqtt-dev\neclipse-mosquitto 2.0\n:1883 (MQTT)\n:9001 (WebSocket)"]
RMQ["rabbitmq-dev\nRabbitMQ 3-management\n:5672 (AMQP)\n:15672 (Mgmt UI)"]
KC["keycloak-dev\nKeycloak 23.0\n:8090→8080\nRealm: flowershop\nDB: postgres-dev/keycloak"]
API["api-dev\n.NET 8 / ASP.NET Core\n:8080\n(dotnet watch — hot reload)"]
end
subgraph Portals["Portal Processes (dotnet run — separate terminals)"]
ADM["AdminPortal :5001\nRazor Pages"]
VND["VendorPortal :5002\nRazor Pages"]
CUS["CustomerApp :5003\nASP.NET MVC"]
end
subgraph Tools["Optional tooling (--profile tools)"]
PGA["pgAdmin :5050"]
RCC["redis-commander :8081"]
end
end
API -->|TCP :5432| PG
API -->|TCP :6379| REDIS
API -->|MQTT :1883| MQ
MQ -->|MQTT| API
API -->|AMQP :5672| RMQ
API -->|HTTP :8080| KC
ADM -->|HTTP :8080| API
VND -->|HTTP :8080| API
CUS -->|HTTP :8080| API
PGA -->|:5432| PG
RCC -->|:6379| REDIS
Sub-domain Mapping — Docker Compose Containers
| Container | Business Sub-domain | Role in the platform |
|---|---|---|
api-dev (.NET 8 / ASP.NET Core :8080) |
All sub-domains | The single deployable backend unit: REST API, SignalR hub, MediatR pipeline, background services |
postgres-dev (PostgreSQL 15 :5432) |
All sub-domains | Persistent store for all aggregates, the outbox table, and the Keycloak realm database |
redis-dev (Redis 7 :6379) |
Identity & Tenancy, Customer Purchasing, Payments | Token blacklist, idempotency keys, query result cache |
mqtt-dev (Mosquitto :1883/:9001) |
IoT / MQTT | Message broker for SmartVase telemetry (heartbeats, sensor readings) |
rabbitmq-dev (RabbitMQ :5672) |
All sub-domains (async events — pending EP-09) | Future cross-service event bus; currently a stub — InMemoryEventBus is active |
keycloak-dev (Keycloak 23 :8090) |
Identity & Tenancy | OIDC identity provider issuing JWTs with vendor_id, vendor_type, customer_id claims |
AdminPortal (Razor Pages :5001) |
Platform Administration | Vendor and vase management UI; calls api-dev over HTTP |
VendorPortal (Razor Pages :5002) |
Vendor Management / Vendor Order Fulfilment | Bouquet catalogue and order fulfilment UI; calls api-dev over HTTP |
CustomerApp (ASP.NET MVC :5003) |
Customer Purchasing | Bouquet browsing, cart, and checkout UI; calls api-dev over HTTP |
Container Startup Dependencies
flowchart LR
PG["postgres-dev\n(healthcheck: pg_isready)"]
REDIS["redis-dev\n(healthcheck: redis-cli ping)"]
MQ["mqtt-dev\n(healthcheck: mosquitto_sub)"]
KC["keycloak-dev\n(depends: postgres-dev healthy)"]
API["api-dev\n(depends: postgres, redis, mqtt, keycloak healthy)"]
PG --> KC
PG --> API
REDIS --> API
MQ --> API
KC --> API
Sub-domain Mapping — Startup Dependency Chain
| Dependency | Business Sub-domain | Why it must be healthy first |
|---|---|---|
postgres-dev → keycloak-dev |
Identity & Tenancy | Keycloak stores its realm, users, and client configuration in PostgreSQL |
postgres-dev → api-dev |
All sub-domains | API cannot start without a reachable database — all aggregates and the outbox table live here |
redis-dev → api-dev |
Identity & Tenancy, Customer Purchasing | Token blacklist and idempotency middleware initialise at startup |
mqtt-dev → api-dev |
IoT / MQTT | MqttDeviceStartupService subscribes to vase topics on startup; broker must be available |
keycloak-dev → api-dev |
Identity & Tenancy | JWT validation fetches Keycloak OIDC discovery document on first request |
Port Map
| Service | Host Port | Container Port | Protocol |
|---|---|---|---|
| REST API + Swagger | 8080 | 8080 | HTTP |
| Admin Portal | 5001 | 5001 | HTTP |
| Vendor Portal | 5002 | 5002 | HTTP |
| Customer Web App | 5003 | 5003 | HTTP |
| Keycloak Admin Console | 8090 | 8080 | HTTP |
| PostgreSQL | 5432 | 5432 | TCP |
| Redis | 6379 | 6379 | TCP |
| MQTT (raw) | 1883 | 1883 | MQTT |
| MQTT (WebSocket) | 9001 | 9001 | WS |
| RabbitMQ AMQP | 5672 | 5672 | AMQP |
| RabbitMQ Management | 15672 | 15672 | HTTP |
| pgAdmin | 5050 | 80 | HTTP |
| Redis Commander | 8081 | 8081 | HTTP |
Logical Deployment Tiers
The production topology separates clients, application servers, infrastructure services, and external SaaS into four tiers. The SmartVase firmware is a Tier 1 client that communicates directly with the Mosquitto broker in Tier 3 over MQTT — it never touches the REST API. All web clients (mobile app, web portals) communicate exclusively with Tier 2 application servers. External SaaS providers (Stripe, Firebase, Nominatim, Twilio) are consumed from Tier 2 only.
flowchart TB
subgraph Tier1["Tier 1 — Client"]
MOB["Mobile App\n(React Native / Flutter)"]
WEB["Customer Web Browser\n(:5003)"]
VADM["Vendor Browser\n(:5002)"]
AADM["Admin Browser\n(:5001)"]
VASE["SmartVase Firmware\n(MQTT over TCP)"]
end
subgraph Tier2["Tier 2 — Application"]
API2["REST API\n(.NET 8 / Kestrel :8080)\n+ SignalR Hub (in-process)"]
ADM2["Admin Portal\n(Razor Pages :5001)"]
VND2["Vendor Portal\n(Razor Pages :5002)"]
CUS2["Customer App\n(MVC :5003)"]
end
subgraph Tier3["Tier 3 — Infrastructure"]
PG2["PostgreSQL 15\n(primary + read replica)"]
RD2["Redis 7\n(cache + blacklist + idempotency)"]
KC2["Keycloak 23\n(OIDC / JWT)"]
MQ2["Mosquitto MQTT\n(IoT broker)"]
RMQ2["RabbitMQ\n(event bus — pending EP-09)"]
end
subgraph Tier4["Tier 4 — External SaaS"]
STR["Stripe\n(payments)"]
FCM["Firebase FCM\n(push notifications)"]
TWI["Twilio\n(SMS)"]
NOM["Nominatim OSM\n(geocoding)"]
end
Tier1 --> Tier2
Tier2 --> Tier3
Tier2 --> Tier4
VASE --> MQ2
Sub-domain Mapping — Deployment Tiers
| Tier | Component | Business Sub-domain |
|---|---|---|
| Tier 1 — Client | Mobile App / Customer Web Browser | Customer Purchasing |
| Tier 1 — Client | Vendor Browser (:5002) |
Vendor Management / Vendor Order Fulfilment |
| Tier 1 — Client | Admin Browser (:5001) |
Platform Administration |
| Tier 1 — Client | SmartVase Firmware (MQTT) | IoT / MQTT |
| Tier 2 — Application | REST API + SignalR (:8080) |
All sub-domains |
| Tier 2 — Application | Admin / Vendor / Customer Portals | Platform Administration, Vendor Management, Customer Purchasing |
| Tier 3 — Infrastructure | PostgreSQL | All sub-domains (persistence) |
| Tier 3 — Infrastructure | Redis | Identity & Tenancy, Customer Purchasing, Payments (cache / blacklist / idempotency) |
| Tier 3 — Infrastructure | Keycloak | Identity & Tenancy |
| Tier 3 — Infrastructure | Mosquitto MQTT | IoT / MQTT |
| Tier 3 — Infrastructure | RabbitMQ | All sub-domains (async events — pending EP-09) |
| Tier 4 — External SaaS | Stripe | Payments |
| Tier 4 — External SaaS | Firebase FCM | Customer Purchasing (push notifications) |
| Tier 4 — External SaaS | Twilio | Customer Purchasing (SMS) |
| Tier 4 — External SaaS | Nominatim OSM | Customer Purchasing (geo-coding for bouquet search) |
Cloud Deployment — Dev/Staging Environment
The deployed dev/staging environment uses free-tier managed services to avoid running VMs. All application processes are containerised; infrastructure services are either managed PaaS or replaced by SaaS equivalents.
Hosting Decisions
| Component | Platform | URL | Deploy mechanism |
|---|---|---|---|
REST API (.NET 8) |
Fly.io | https://api.findmyflowers.pl |
flyctl deploy via fly.api.toml |
| Keycloak 23 | Fly.io | https://auth.findmyflowers.pl |
flyctl deploy via fly.keycloak.toml |
| Customer App (MVC) | Fly.io | https://app.findmyflowers.pl |
flyctl deploy via fly.customer-app.toml |
| Admin Portal (Razor Pages) | Render | https://admin.findmyflowers.pl |
Image pushed to GHCR → Render deploy API |
| Vendor Portal (Razor Pages) | Render | https://vendor.findmyflowers.pl |
Image pushed to GHCR → Render deploy API |
| MQTT broker | HiveMQ Cloud | <cluster>.s1.eu.hivemq.cloud:8883 |
Managed SaaS — no deploy step |
MQTT — HiveMQ Cloud Free Tier
Local development continues to use the Mosquitto container in docker-compose (plain TCP, port 1883, no TLS). In deployed environments Mosquitto is replaced by HiveMQ Cloud (free tier).
Free tier limits: 100 concurrent connections, 10 GB/month data transfer, no uptime SLA.
Protocol: MQTT 3.1.1 / 5.0, QoS 0/1/2, persistent sessions. TLS only — port 8883 (plain 1883 not available).
Required backend code changes before EP-15 (not yet implemented):
MqttConfigurationPOCO — addbool UseTlsproperty (defaultfalse)MqttService.cs— add.WithTlsOptions(o => o.UseTls())to theMqttClientOptionsBuilderwhenUseTls = trueappsettings.Production.json— setMQTT:BrokerHost,MQTT:BrokerPort = 8883,MQTT:UseTls = true; credentials injected via Fly.io secrets (MQTT__Username,MQTT__Password)- SmartVase firmware — must connect to HiveMQ Cloud on port 8883/TLS instead of Mosquitto on 1883 (firmware-side change)
CI/CD — GitHub Actions
All deployments trigger on push to main via .github/workflows/deploy-*.yml. Secrets required:
| Secret | Used by |
|---|---|
FLY_API_TOKEN |
All Fly.io deploys |
RENDER_API_KEY |
Render portal deploys |
RENDER_ADMIN_SERVICE_ID |
Admin Portal Render service |
RENDER_VENDOR_SERVICE_ID |
Vendor Portal Render service |
MQTT__Username / MQTT__Password |
Fly.io API app — injected at runtime via fly secrets set |
Environment Variables (key settings)
| Variable | Purpose |
|---|---|
ASPNETCORE_ENVIRONMENT |
Development / Production |
Authentication__UseKeycloak |
false → dev-token mode (no Keycloak required) |
ConnectionStrings__DefaultConnection |
PostgreSQL primary |
ConnectionStrings__ReadConnection |
PostgreSQL read replica |
ConnectionStrings__Redis |
Redis (host:port); if absent → AddDistributedMemoryCache() fallback |
MQTT__BrokerHost / MQTT__BrokerPort |
Mosquitto endpoint |
Authentication__Keycloak__Authority |
Keycloak realm URL |
JWT__SecretKey |
Dev-only JWT signing key |
Volumes (persistent data)
| Volume | Contents |
|---|---|
postgres_dev_data |
PostgreSQL data directory |
redis_dev_data |
Redis AOF log |
mosquitto_dev_data |
MQTT persistence |
rabbitmq_dev_data |
RabbitMQ mnesia database |
api_dev_logs |
ASP.NET Core log files |