queue-api-proxy's Traefik rate limit was average=150/burst=50, live on the container since before this repo existed (created 2026-05-30) but never captured in docker-compose.yml - the container just kept running with labels set at creation, invisible in git. Root-caused via a real bulk-order test: 200 orders fired without client-side pacing hit 79 429s, all before reaching NATS. The gateway's per-request cost is cheap (validate + publish to JetStream), and the worker pool absorbs bursts asynchronously once a message is queued - the edge limit doesn't need to shape traffic, NATS already does that downstream. It only needs to catch actual flood-scale abuse. Raised to average=300/burst=400 so realistic bulk-order volume clears it comfortably. Documented the labels in git for the first time so this isn't invisible config drift going forward.
144 lines
5.1 KiB
YAML
144 lines
5.1 KiB
YAML
services:
|
|
# Queue API Proxy - Routes queue.workolik.com (Traefik on host:443) to Kubernetes LoadBalancer (FastAPI)
|
|
queue-api-proxy:
|
|
image: nginx:alpine
|
|
container_name: queue-api-proxy
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8202:8201" # Internal port for Traefik to reach nginx
|
|
volumes:
|
|
- ./conf/nginx-queue-proxy.conf:/etc/nginx/nginx.conf:ro
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- web
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=web"
|
|
# Router for queue.workolik.com with CORS (proxying to K8s LoadBalancer)
|
|
- "traefik.http.routers.queue-api.rule=Host(`queue.workolik.com`)"
|
|
- "traefik.http.routers.queue-api.tls=true"
|
|
- "traefik.http.routers.queue-api.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.queue-api.priority=100"
|
|
- "traefik.http.services.queue-api.loadbalancer.server.port=8201"
|
|
# Edge rate limit (per source IP, Traefik default). Predates this repo -
|
|
# the running container (created 2026-05-30) had average=150/burst=50
|
|
# baked in at creation time, undocumented here since labels aren't
|
|
# re-read on an existing container. Raised to fit real bulk-order
|
|
# traffic: NATS + the worker pool absorb bursts fine once a request
|
|
# reaches the gateway, so this only needs to block actual flood-scale
|
|
# abuse, not legitimate customers batching orders (2026-07-30).
|
|
- "traefik.http.middlewares.queue-rl.ratelimit.average=300"
|
|
- "traefik.http.middlewares.queue-rl.ratelimit.burst=400"
|
|
- "traefik.http.routers.queue-api.middlewares=queue-rl"
|
|
|
|
# Kubernetes dashboard proxy (kube.workolik.com ? K8s dashboard)
|
|
k8s-dashboard-proxy:
|
|
image: nginx:alpine
|
|
container_name: k8s-dashboard-proxy
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8083:8083"
|
|
volumes:
|
|
- ./conf/nginx-k8s-dashboard.conf:/etc/nginx/nginx.conf:ro
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- web
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.k8s-dashboard.rule=Host(`kube.workolik.com`)"
|
|
- "traefik.http.routers.k8s-dashboard.tls=true"
|
|
- "traefik.http.routers.k8s-dashboard.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.k8s-dashboard.loadbalancer.server.port=8083"
|
|
- "traefik.docker.network=web"
|
|
|
|
# Jupiter API Proxy (jupiter.nearle.app ? K8s Gateway)
|
|
jupiter-proxy:
|
|
image: nginx:alpine
|
|
container_name: jupiter-proxy
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8203:8203"
|
|
volumes:
|
|
- ./conf/nginx-jupiter.conf:/etc/nginx/nginx.conf:ro
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- web
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=web"
|
|
- "traefik.http.routers.jupiter-api.rule=Host(`jupiter.nearle.app`)"
|
|
- "traefik.http.routers.jupiter-api.tls=true"
|
|
- "traefik.http.routers.jupiter-api.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.jupiter-api.loadbalancer.server.port=8203"
|
|
|
|
# Fiesta API Proxy (fiesta.nearle.app ? K8s NodePort 30823)
|
|
fiesta-proxy:
|
|
image: nginx:alpine
|
|
container_name: fiesta-proxy
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8204:8204"
|
|
volumes:
|
|
- ./conf/nginx-fiesta.conf:/etc/nginx/nginx.conf:ro
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- web
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=web"
|
|
- "traefik.http.routers.fiesta-api.rule=Host(`fiesta.nearle.app`)"
|
|
- "traefik.http.routers.fiesta-api.tls=true"
|
|
- "traefik.http.routers.fiesta-api.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.fiesta-api.loadbalancer.server.port=8204"
|
|
|
|
# Atlantis API Proxy (atlantis.nearle.app ? K8s NodePort 30825)
|
|
atlantis-proxy:
|
|
image: nginx:alpine
|
|
container_name: atlantis-proxy
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8205:8205"
|
|
volumes:
|
|
- ./conf/nginx-atlantis.conf:/etc/nginx/nginx.conf:ro
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- web
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=web"
|
|
- "traefik.http.routers.atlantis-api.rule=Host(`atlantis.nearle.app`)"
|
|
- "traefik.http.routers.atlantis-api.tls=true"
|
|
- "traefik.http.routers.atlantis-api.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.atlantis-api.loadbalancer.server.port=8205"
|
|
|
|
# Doormile API Proxy (doormile.com -> K8s NodePort 30830)
|
|
doormile-proxy:
|
|
image: nginx:alpine
|
|
container_name: doormile-proxy
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8206:8206"
|
|
volumes:
|
|
- ./conf/nginx-doormile.conf:/etc/nginx/nginx.conf:ro
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- web
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=web"
|
|
- "traefik.http.routers.doormile-api.rule=Host(`doormile.com`)"
|
|
- "traefik.http.routers.doormile-api.tls=true"
|
|
- "traefik.http.routers.doormile-api.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.doormile-api.loadbalancer.server.port=8206"
|
|
|
|
networks:
|
|
web:
|
|
external: true
|
|
|