Deployment Guide
AXIOM Exchange's API layer is stateless (auth is JWT/API-key, no server-side sessions), so the same container image runs unmodified on a laptop, on-prem behind nginx, or autoscaled behind a cloud API Gateway.
1. Local (docker-compose)
Covered on the Overview page. Two API replicas + nginx + Postgres/PostGIS
- Redis, all on one machine. Seed the demo dataset with
docker compose exec api1 python -m app.db.seed_demo.
2. On-prem / self-managed VM or bare-metal
- Provision PostgreSQL 16+ with PostGIS available
(
CREATE EXTENSION postgis;). Applyapi/app/db/schema.sql— it is the canonical DDL and has been verified to apply clean from scratch. - Build and push the API image:
docker build -t axiom-exchange-api ./api. - Run N containers of that image — each is stateless and identical.
- Point nginx's
upstream axiom_api { ... }block at all N instances. - Terminate TLS at nginx (add a
listen 443 sslblock) or at a proxy in front of it.
3. AWS
Compute: ECS Fargate service (or EKS) behind an Application Load Balancer. nginx can stay in the path as an internal LB layer, or be dropped entirely — the ALB already does health-check-aware balancing across tasks. Keep nginx if you want its rate limiting, gzip, and caching.
Database: Amazon Aurora PostgreSQL-Compatible with
CREATE EXTENSION postgis;. Aurora Serverless v2 suits spiky trading-hours
load. Read replicas can take the read-heavy /geo/* and /assets traffic
(the app does no read/write splitting itself — do it at the connection
string or via RDS Proxy with a read-only endpoint).
API Gateway: import /openapi.json into AWS API Gateway (HTTP API,
OpenAPI import) and point the integration at the ALB. Use a Cognito or
Lambda authorizer, then forward identity to AXIOM via
X-Axiom-Gateway-Identity + X-Axiom-Gateway-Secret (set to your
AXIOM_JWT_SECRET). For reseller scoping, have the authorizer also set
X-Axiom-Reseller-Id from the validated claims — the service then filters
every tenant-aware query to that reseller without trusting the client.
Config: AXIOM_DATABASE_URL → the Aurora cluster endpoint,
AXIOM_ENVIRONMENT=production, secrets via AWS Secrets Manager or SSM
injected as container env vars — never baked into the image.
4. Azure
Compute: Azure Container Apps (built-in autoscaling and revision-based rollout) or AKS. Same nginx-optional consideration as AWS.
Database: Azure Database for PostgreSQL — Flexible Server, with
postgis in the azure.extensions allowlist (Portal → Extensions, or
az postgres flexible-server parameter set --name azure.extensions --value postgis),
then CREATE EXTENSION postgis;.
API Gateway: Azure API Management. Import /openapi.json as an API
definition; APIM handles subscription keys/OAuth, quotas, and policies,
then forwards to the Container Apps ingress. Use the same
X-Axiom-Reseller-Id pattern for tenant scoping.
Config: AXIOM_DATABASE_URL → the Flexible Server endpoint, secrets
via Key Vault referenced from Container Apps secret bindings.
5. Kong
See gateway/kong.example.yaml in the repository for a working declarative
config: key-auth, rate-limiting, and cors plugins in front of the
nginx/API service. Apply with deck sync -s gateway/kong.example.yaml.
6. Scaling checklist
- API layer: stateless — scale horizontally.
WEB_CONCURRENCY(DockerfileENV) controls gunicorn workers per container; roughly2 × vCPU + 1. - Database: confirm the GiST indexes are actually used
(
EXPLAIN ANALYZEyour/geo/*queries at real data volume); add read replicas for geo traffic; consider partitioningexchange_transactionsbydelivery_startat high volume. - Tenancy at scale: the hot access paths are
(reseller_id, asset_type)and(customer_id, state), both indexed. If one reseller grows far larger than the rest, consider partitioningassetsbyreseller_id. - Cache: Redis is wired into
docker-compose.ymlandAXIOM_REDIS_URLbut not yet used — natural next steps are caching/geo/bboxresponses for popular viewports and sharing rate-limit counters across replicas (nginx'slimit_req_zoneis per-node). - Migrations: add Alembic before running against production data — the
auto-
create_allon startup is a local dev convenience only, gated behindAXIOM_ENVIRONMENT=local.
7. Environment variable reference
| Variable | Default | Notes |
|---|---|---|
AXIOM_ENVIRONMENT |
local |
local enables schema autocreate on boot. Use staging/production elsewhere. |
AXIOM_DATABASE_URL |
local postgres | postgresql+asyncpg://user:pass@host:5432/db |
AXIOM_REDIS_URL |
local redis | redis://host:6379/0 |
AXIOM_JWT_SECRET |
dev placeholder | Change in every non-local environment. Also doubles as the gateway-trust shared secret. |
AXIOM_JWT_EXPIRE_MINUTES |
60 |
|
AXIOM_API_KEYS |
["axiom-local-dev-key"] |
JSON array of valid static keys. |
AXIOM_CORS_ORIGINS |
["*"] |
Restrict in production. |
AXIOM_DEFAULT_SRID |
4326 |
WGS84 — matches ArcGIS/GeoJSON defaults. |