Deployment
Container, reverse-proxy, readiness, capacity, scaling, and production-hardening guidance for A.T.O.M.
A.T.O.M is local-first and stateless on the server. The production image serves the compiled React workspace, Go API, and an immutable active dataset-pack snapshot from a single non-root container.
Recommended Local Deployment
git lfs install
git clone https://github.com/Berk-Unsal/urban-ray-tracer.git
cd urban-ray-tracer
git lfs pull
docker compose up --build -d atom
curl --fail http://localhost:8080/readyzOpen http://localhost:8080. Stop the service with
docker compose down.
Runtime Topology
| Component | Default | Responsibility |
|---|---|---|
atom container |
Required | Static frontend, REST API, RF engines, spatial index, active dataset pack |
| Browser IndexedDB | Required for projects | Local project/scenario history; not shared across users |
| OpenStreetMap tiles | External by default | Visual basemap only; RF computation remains local |
core-lab-adapter |
Optional profile | Deterministic 5G path state and optional Open5GS probes |
The Go server does not persist projects, jobs, reports, or measurements. Multiple API replicas can serve independent requests, but browser projects do not become collaborative storage.
Configuration
| Variable | Default | Purpose |
|---|---|---|
BIND_ADDRESS |
127.0.0.1 (0.0.0.0 in container) |
HTTP listener address; Compose publishes it on loopback by default |
PORT |
8080 |
Public HTTP listener inside the container |
FRONTEND_DIST_PATH |
/app/dist |
Compiled frontend bundle |
ATOM_DATASET_DIR |
/app/data-pipeline |
Directory containing manifest.json and referenced data
files |
ATOM_DATASETS_ROOT |
unset | Local directory whose own pack and immediate child pack directories are listed for safe runtime switching |
DATASET_ADMIN_API_KEY |
unset | Require this key to activate an installed dataset pack by ID |
MAX_CONCURRENT_RF_REQUESTS |
2 |
Process-wide expensive RF admission limit |
MAX_CONCURRENT_RF_REQUESTS_PER_CLIENT |
1 |
Maximum simultaneous expensive jobs from one client identity |
RF_REQUESTS_PER_MINUTE |
20 |
Per-client expensive-route request budget per process |
RF_REQUEST_TIMEOUT_SECONDS |
60 |
Hard computation deadline for each expensive RF request |
RF_API_KEY |
unset | Require this key on expensive RF routes as a bearer token or
X-API-Key when configured |
MAX_CONCURRENT_BUILDING_DOWNLOADS |
2 |
Process-wide admission limit for fresh full-dataset transfers |
MAX_CONCURRENT_BUILDING_DOWNLOADS_PER_CLIENT |
1 |
Maximum simultaneous full-dataset transfers from one client identity |
BUILDING_DOWNLOADS_PER_MINUTE |
2 |
Per-client fresh building-dataset transfer budget per process |
MAX_CONCURRENT_BUILDING_FEATURE_QUERIES |
4 |
Process-wide admission limit for bounded building viewport queries |
MAX_CONCURRENT_BUILDING_FEATURE_QUERIES_PER_CLIENT |
2 |
Maximum simultaneous bounded building queries from one client identity |
BUILDING_FEATURE_QUERIES_PER_MINUTE |
120 |
Per-client bounded building-query budget per process |
BUILDINGS_API_KEY |
unset | Require this key on full and bounded building-data routes when configured |
EXPERIMENT_WORKERS |
1 |
Concurrent batch-experiment workers |
EXPERIMENT_QUEUE_SIZE |
16 |
Accepted asynchronous experiment jobs waiting for a worker |
TRUSTED_PROXIES |
unset | Comma-separated proxy CIDRs whose forwarded client-IP headers are trusted |
REQUIRE_HTTPS |
false |
Reject non-HTTPS requests with 426; enable behind a TLS
proxy that supplies X-Forwarded-Proto |
CORE_LAB_ENABLED |
false |
Enables backend proxy routes for the optional adapter |
CORE_LAB_ADAPTER_URL |
http://localhost:8090 |
Internal adapter URL when Core Lab is enabled |
CORE_LAB_API_KEY |
unset | Required shared key for Core Lab scenario mutation when Core Lab is enabled |
The manifest-driven dataset directory is the authoritative data
boundary; individual building and tower paths are not configured
independently. ATOM_DATASETS_ROOT expands that boundary
only to installed child packs and never permits client-supplied
paths.
Dataset Mounts
Validate a pack before deployment:
cd backend-go
go run ./cmd/validate-dataset /absolute/path/to/datasetMount one pack read-only and set ATOM_DATASET_DIR:
services:
atom:
build: .
ports:
- "8080:8080"
environment:
ATOM_DATASET_DIR: /datasets/region
volumes:
- /absolute/path/to/dataset:/datasets/region:roFor a local catalog, make the initial pack a child of the mounted root and set both variables:
services:
atom:
environment:
ATOM_DATASET_DIR: /datasets/ankara
ATOM_DATASETS_ROOT: /datasets
DATASET_ADMIN_API_KEY: ${DATASET_ADMIN_API_KEY}
volumes:
- /absolute/path/to/installed-packs:/datasets:roThe Data tool and GET /api/datasets list unique valid
manifests. Activation submits only a manifest ID. The backend resolves
entries beneath the configured root, validates every referenced hash
plus tower/building usability, and atomically replaces the pack after
success; ongoing requests retain their captured old snapshot. Failed
candidates, duplicate IDs, and symlink escapes do not change the active
pack. When DATASET_ADMIN_API_KEY is configured, a
same-origin gateway must inject it because the browser does not store
administration credentials.
Missing files, hash mismatches, invalid EPSG:4326 bounds, duplicate
tower IDs, or unusable geometry keep initial readiness at
503. See Dataset Pack
Studio to build and validate arbitrary-region schema-v2 packs.
Health And Capacity
GET /healthzis process liveness and can succeed before the application is ready.GET /readyzrequires valid data, loaded tower/building indexes, and the frontend bundle.- Request bodies are limited to 1 MiB; oversized requests return
413. - Two RF jobs are admitted globally, but one client can run only one
at a time by default. Each client also receives 20 expensive-route
attempts per minute. Rejection returns
429,Retry-After, and rate-budget headers. - Fresh full-building-dataset responses are limited to two globally,
one concurrently per client, and two per minute per client. A strong
content-hash
ETagand one-hour browser/shared-cache lifetime avoid retransferring the approximately 117 MB file; matching revalidations return304before download admission. - Each expensive job has a 60-second computation deadline and uses no more than four workers. Ray, geometry, grid, measurement, and recommendation loops observe cancellation.
- Segmented-ray requests must satisfy
rays × ceil(radius_m / 25) ≤ 25,000; a shared worker budget also prevents building-intersection splits from collecting or serializing more than 25,000 response features. - HTTP timeouts are 5 seconds for headers, 15 seconds for reads, 120 seconds for writes, and 60 seconds idle.
Raise MAX_CONCURRENT_RF_REQUESTS or its per-client
counterpart only after measuring CPU, memory, and tail latency with
representative two-to-six-cell requests. Admission and request budgets
are process-local; use a gateway for a shared multi-replica budget.
Reverse Proxy Boundary
Compose binds the application to 127.0.0.1 by default.
Set ATOM_BIND_ADDRESS only when another interface must
accept traffic. For an internet-facing deployment, set strong
RF_API_KEY, BUILDINGS_API_KEY, and
DATASET_ADMIN_API_KEY values as applicable and place a
trusted proxy or gateway in front of port 8080 for:
- TLS termination and authentication.
- Request-rate and source policy.
- Access logging and security headers.
- A response timeout longer than the backend's 120-second RF write window.
Authenticated clients send either
Authorization: Bearer <key> or
X-API-Key: <key>. The bundled browser UI does not
store API keys, so deployments that enable either backend key should
terminate user authentication at a same-origin gateway and inject the
appropriate key there. Keep TRUSTED_PROXIES unset unless
the server is actually behind those proxies; otherwise forwarded IP
headers are ignored for per-client budgets.
The backend emits CSP, frame-ancestor, MIME-sniffing, referrer,
permissions, and opener-policy headers on every response. HSTS is
emitted for TLS requests. The container intentionally serves HTTP on its
internal port; terminate TLS at the gateway and set
REQUIRE_HTTPS=true after the gateway is configured to
supply X-Forwarded-Proto: https.
The Core Lab overlay keeps the adapter private to the service network, runs it as a non-root user with a read-only filesystem, drops all Linux capabilities, and prevents privilege escalation. Use an explicit, separately reviewed override if direct host access is required for diagnostics.
Optional 5G Core Lab
export CORE_LAB_API_KEY="$(openssl rand -hex 32)"
docker compose -f docker-compose.yml -f docker-compose.core-lab.yml \
--profile core-lab up --buildThe adapter does not deploy a complete Open5GS core. Without
configured Open5GS endpoints it returns a deterministic
simulated_overlay; with endpoints it briefly probes
external health/metrics while preserving the same
/api/core/* contracts. Both the backend mutation route and
the adapter require CORE_LAB_API_KEY; the backend forwards
its configured credential on the private adapter hop. Browser
deployments should inject the key at a same-origin gateway rather than
store it in frontend code.
Tagged Multi-Architecture Images
Pushing a v* Git tag triggers
.github/workflows/release.yml. The workflow:
- Pulls Git LFS data.
- Builds Linux AMD64 and ARM64 images.
- Embeds the tag and commit into
/api/metausing Go linker values. - Publishes semantic-version tags to
ghcr.io/<repository-owner>/atomwith SPDX SBOM and provenance attestations.
Every external action used by the quality and release workflows is pinned to a full commit SHA, with its release version retained in a comment for review. Container build stages retain readable version tags but are also pinned to immutable multi-architecture image digests. CI rejects mutable build inputs, reviews dependency changes on pull requests, builds the production and Core Lab adapter images, stores SPDX JSON SBOMs, and fails on fixable high or critical vulnerabilities. Checkout does not persist its Git credential, and weekly Dependabot updates keep reviewed action, module, Python, and container pins current.
Use immutable version tags in deployments. The running application
exposes its version, commit, model version, and dataset identity in Data
and /api/meta.
Upgrade And Rollback
Before an upgrade, export important .atom-project.json
files. Then:
git pull
git lfs pull
docker compose up --build -d atom
curl --fail http://localhost:8080/readyzImported projects include a schema version and dataset reference. A mismatch is surfaced rather than silently treating previous results as current. Roll back by deploying the previous immutable image/source tag and its matching dataset pack.
Verification Checklist
/healthz,/readyz, and/api/metareturn expected process, version, model, and dataset details.- The Data tool shows the intended pack provenance and hashes.
- A single sector run completes and report metadata matches
/api/meta. - A concurrent-capacity test returns bounded
429behavior instead of unbounded CPU growth. - Project export/import works in a clean browser profile.
- Public deployments enforce TLS and user authentication at the
gateway and set
RF_API_KEYplusBUILDINGS_API_KEYon the backend hop.
See Download and Use, System Architecture, and the OpenAPI contract.