Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/danizd/urbanviable/llms.txt

Use this file to discover all available pages before exploring further.

UrbanViable runs as two Docker containers orchestrated with Docker Compose on Oracle Cloud ARM infrastructure. urbanviable-tiles serves vector tiles from a local .mbtiles file via TileServer GL on port 8080, and urbanviable-web hosts the compiled React SPA, exposes the data-status endpoint, and serves tile data files from a shared read-only volume on port 3002. There is no database, no API server, and no server-side computation — all scouting logic runs in the browser.

Containers

ContainerImageHost → Container PortRole
urbanviable-tilesmaptiler/tileserver-gl:latest8080 → 8080Serves vector tile data from .mbtiles
urbanviable-webnginx:1.25-alpine3002 → 80Hosts React SPA, serves /api/status and /data/ from mounted tile data

Volumes

Host pathContainerContainer pathMount flagsPurpose
./tiles_dataurbanviable-tiles/dataMBTiles file and TileServer GL config.json
./nginx/conf.durbanviable-web/etc/nginx/conf.d:roNginx virtual host config
./frontend/buildurbanviable-web/usr/share/nginx/html:roCompiled React SPA static files
./certsurbanviable-web/etc/letsencrypt:roLet’s Encrypt TLS certificates
./tiles_dataurbanviable-web/data:roProvides last_update.json for the /api/status endpoint and static tile data
urbanviable-tiles binds port 8080 on the host. Internal tile requests within the Docker network go directly via urbanviable-net. TLS termination and public routing are handled by urbanviable-web (Nginx), which listens on host port 3002.

docker-compose.yml

services:
  urbanviable-tiles:
    image: maptiler/tileserver-gl:latest
    container_name: urbanviable-tiles
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./tiles_data:/data
    command: --config /data/config.json
    networks:
      - urbanviable-net
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  urbanviable-web:
    image: nginx:1.25-alpine
    container_name: urbanviable-web
    restart: unless-stopped
    depends_on:
      - urbanviable-tiles
    ports:
      - '3002:80'
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./frontend/build:/usr/share/nginx/html:ro
      - ./certs:/etc/letsencrypt:ro
      - ./tiles_data:/data:ro
    networks:
      - urbanviable-net

networks:
  urbanviable-net:
    driver: bridge

TileServer GL: tiles_data/config.json

TileServer GL is configured with a minimal config.json that registers the .mbtiles file under the dataset name galicia-scouting. This name is what the frontend references in its MapLibre source and what TileServer GL uses to serve tile requests at /data/galicia-scouting/{z}/{x}/{y}.pbf on port 8080.
{
  "options": {
    "paths": {
      "root": "/data",
      "mbtiles": "/data"
    }
  },
  "data": {
    "galicia-scouting": {
      "mbtiles": "galicia_scouting.mbtiles"
    }
  }
}

First-time setup

1

Clone the repository and configure environment variables

Clone the repository on the OCI server and create a .env file from the template:
git clone https://github.com/tu-usuario/urbanviable.git
cd urbanviable
cp .env.example .env
nano .env   # Set DOMAIN, CORS_ORIGIN, and REACT_APP_* variables
At minimum, replace all occurrences of tu-dominio.com with your real domain.
2

Copy the .mbtiles file to tiles_data/

The .mbtiles file is generated by the ETL pipeline on a developer workstation (never on the server). Copy it over via scp:
# Run on your local machine after completing an ETL run
scp etl/data/processed/galicia_scouting.mbtiles usuario@servidor:~/urbanviable/tiles_data/
scp etl/data/processed/last_update.json usuario@servidor:~/urbanviable/tiles_data/
The last_update.json file is served by Nginx at /api/status so the frontend can display data freshness information.
3

Build the React frontend

The frontend is a Vite project. Build-time variables (prefixed REACT_APP_) must be set in .env before building, as Vite bakes them into the JS bundle at compile time via the configured envPrefix:
cd frontend
npm install
npm run build
cd ..
The compiled output lands in frontend/build/, which is bind-mounted read-only into the Nginx container.
4

Start the containers

docker compose up -d
Docker Compose will pull the images on first run. The depends_on directive ensures urbanviable-tiles is healthy before urbanviable-web starts.
5

Verify the deployment

Check that both containers are running and the tile endpoint is reachable:
# Check container status
docker compose ps

# Verify TileServer GL is serving the dataset metadata
curl http://localhost:8080/data/galicia-scouting.json

# Verify Nginx is serving the status endpoint (Nginx listens on host port 3002)
curl http://localhost:3002/api/status

# Stream logs from TileServer GL
docker compose logs -f urbanviable-tiles
A successful response from the galicia-scouting.json endpoint will include minzoom, maxzoom, and bounds fields.

Updating MBTiles after a new ETL run

TileServer GL monitors its data directory and picks up a replaced .mbtiles file without requiring a container restart.
1

Run the ETL pipeline locally

Execute the full ETL pipeline on your development machine:
cd etl/
python download_data.py
python process_data.py
bash generate_tiles.sh
Review the output of process_data.py (polygon count, null values) before proceeding to verify the data is sound.
2

Upload the new files to the server

scp data/processed/galicia_scouting.mbtiles usuario@servidor:~/urbanviable/tiles_data/
scp data/processed/last_update.json usuario@servidor:~/urbanviable/tiles_data/
3

TileServer GL reloads in hot — no restart needed

TileServer GL detects the replaced file automatically. No container restart is required.If you need to force a reload manually:
docker compose restart urbanviable-tiles
4

Verify the new tileset is live

# TileServer GL is directly available on port 8080
curl http://localhost:8080/data/galicia-scouting.json | jq .minzoom
Cross-check the updated_at field in last_update.json against the new ETL run date:
curl http://localhost:3002/api/status | jq .updated_at

Useful commands

# Stream combined logs from both containers
docker compose logs -f

# Stream logs from a single service
docker compose logs -f urbanviable-tiles
docker compose logs -f urbanviable-web --tail=20

# Restart a single service (e.g. after editing nginx/conf.d/default.conf)
docker compose restart urbanviable-web

# Restart TileServer GL after editing config.json
docker compose restart urbanviable-tiles

# Check the size of the MBTiles file
du -sh tiles_data/galicia_scouting.mbtiles

# Verify TileServer GL health directly (internal network only)
curl http://localhost:8080/health

# Full stack stop and remove containers (data is preserved in volumes)
docker compose down

Build docs developers (and LLMs) love