Operations guide

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.

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/readyz

Open 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/dataset

Mount 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:ro

For 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:ro

The 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 /healthz is process liveness and can succeed before the application is ready.
  • GET /readyz requires 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 ETag and one-hour browser/shared-cache lifetime avoid retransferring the approximately 117 MB file; matching revalidations return 304 before 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 --build

The 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:

  1. Pulls Git LFS data.
  2. Builds Linux AMD64 and ARM64 images.
  3. Embeds the tag and commit into /api/meta using Go linker values.
  4. Publishes semantic-version tags to ghcr.io/<repository-owner>/atom with 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/readyz

Imported 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/meta return 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 429 behavior 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_KEY plus BUILDINGS_API_KEY on the backend hop.

See Download and Use, System Architecture, and the OpenAPI contract.