Skip to main content

Documentation Index

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

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

The production deployment packages every GeoSentinel service into a dedicated Docker container coordinated by docker-compose.prod.yml. Unlike the local development setup — which runs PostgreSQL in Docker and everything else directly on the host — production runs all four application services as containers on a shared internal network (geosentinel_net) and connects them to a pre-existing PostgreSQL instance on the external database_network. Inbound HTTPS traffic is handled by Nginx Proxy Manager, which terminates SSL and forwards requests to the frontend container. The frontend’s bundled Nginx configuration then reverse-proxies /v1/ API calls to the backend container internally.

Architecture

The four production containers and their roles:
ServiceInternal PortContainer NameDescription
backend8000geosentinel-backendFastAPI application serving incidents, AOI, corrections, admin, and health endpoints
ais-relay8003geosentinel-aisWebSocket AISStream client with automatic mock fallback for vessel tracking
military-relay8002geosentinel-militaryFastAPI relay filtering military flights via OpenSky Network; auto-updates hex database at startup
frontend80 (external: 9090)geosentinel-frontendNginx serving the compiled React bundle; proxies /v1/, /docs, and /openapi.json to the backend
All services join geosentinel_net (a bridge network created by Compose). The backend and military-relay services additionally join database_network (an external network) to reach the PostgreSQL container. Only the frontend container publishes a host port (9090:80); all other inter-service communication is internal.

Prerequisites

Before running the production deployment, ensure the following are in place on your server:
  • PostgreSQL 16 + PostGIS 3.4 already running as a container named postgres on the Docker network database_network. The database_network must be declared as an external network in your PostgreSQL compose stack.
  • Nginx Proxy Manager managing SSL certificates and routing for your domain (e.g. geosentinel.movilab.es).
  • Docker Engine and Docker Compose v2 installed (docker compose version ≥ 2.0).
  • A populated .env.production file at the project root (loaded by all services via env_file: .env.production in the compose file). See the Environment Variables reference.
  • Your VITE_MAPBOX_TOKEN available as a shell variable at build time — it is baked into the React bundle during the Docker image build.

Deployment Steps

1

Create the GeoSentinel database (first time only)

Connect to the running PostgreSQL container and create the database. This step only needs to be done once:
docker exec -it postgres psql -U admin -c "CREATE DATABASE geosentinel;"
The PostgreSQL superuser in the production container is admin (not postgres as in the dev compose file). Adjust the -U flag if your setup differs.
2

Build and start all services

Pass VITE_MAPBOX_TOKEN as a shell variable so Compose forwards it as a Docker build argument to the frontend image. The --build flag ensures all images are rebuilt from the current source:
VITE_MAPBOX_TOKEN=pk.your_token_here docker compose -f docker-compose.prod.yml up -d --build
Compose will:
  1. Build docker/Dockerfile.backend (Python 3.12-slim + uv-installed dependencies)
  2. Build docker/Dockerfile.ais-relay (Python 3.12-slim + FastAPI + websockets)
  3. Build docker/Dockerfile.military-relay (Python 3.12-slim + FastAPI + the bundled data/military_hex.txt)
  4. Build docker/Dockerfile.frontend (Node 20-alpine build stage → nginx:alpine runtime; VITE_MAPBOX_TOKEN baked in at this step)
The frontend service has a depends_on: backend: condition: service_healthy guard — it will not start until the backend passes its healthcheck (GET /v1/health).
VITE_MAPBOX_TOKEN is a build-time variable injected as a Docker ARG. It gets compiled into the static JavaScript bundle by Vite. If you change the token, you must rebuild the frontend image with --build.
3

Run database migrations

Apply all Alembic migrations to the production database:
docker exec -it geosentinel-backend alembic upgrade head
This runs inside the backend container where Alembic and the database connection string from .env.production are already configured. Re-running is safe — Alembic skips already-applied revisions.
4

Seed initial data

Insert the sources_metadata records and three sample incidents (conflict, earthquake, wildfire) needed to validate the frontend is rendering correctly:
docker exec -it geosentinel-backend python -c \
  "from backend.api.database import engine; \
   from backend.api.routes.seed import seed; \
   from sqlalchemy.orm import Session; \
   session = Session(engine); seed(session)"
Alternatively, once the stack is reachable via your domain, you can call the seed endpoint directly:
curl https://geosentinel.movilab.es/v1/seed
5

Verify the deployment

Check that the backend is healthy from inside the container (no external network required):
docker exec geosentinel-backend python -c \
  "import urllib.request; print(urllib.request.urlopen('http://localhost:8000/v1/health').read().decode())"
Expected output: {"status":"healthy"}Verify the frontend Nginx container is serving the React app:
docker exec geosentinel-frontend wget -qO- http://localhost/ | head -1
Then open https://geosentinel.movilab.es in a browser to confirm end-to-end routing through NPM → frontend nginx → backend.

Nginx Proxy Manager Configuration

Create a Proxy Host entry in NPM pointing to the frontend container. Because the frontend container publishes port 9090 on the Docker host, NPM connects to the host via the Docker bridge gateway IP:
SettingValue
Domain Namesgeosentinel.movilab.es (or your domain)
Schemehttp
Forward Hostname / IP172.17.0.1 (Docker bridge gateway)
Forward Port9090
Cache AssetsOff
Block Common ExploitsOn
SSL CertificateLet’s Encrypt (request via NPM)
Force SSL✅ Enabled
HTTP/2 Support✅ Enabled
172.17.0.1 is the default Docker bridge gateway address. If you have customised Docker’s default bridge network or are using a different bridge, verify the correct address with ip route | grep docker or docker network inspect bridge.

Nginx Reverse Proxy

The frontend Docker image copies docker/nginx/geosentinel.conf into the Nginx container at /etc/nginx/conf.d/default.conf. This configuration handles all reverse-proxying internally, so no Custom Locations are needed in NPM:
LocationProxied ToPurpose
/v1/http://backend:8000All REST API calls (incidents, AOI, health, seed, military-flights)
/docshttp://backend:8000Swagger UI
/openapi.jsonhttp://backend:8000OpenAPI schema for Swagger
/Static files → index.htmlReact SPA with client-side routing
*.js, *.css, images, fontsStatic files1-year cache with immutable header
The proxy passes X-Real-IP, X-Forwarded-For, and X-Forwarded-Proto headers to the backend and allows a generous proxy_read_timeout of 300 seconds for long-running ingestor calls.

Useful Commands

# Stream logs from a specific service
docker compose -f docker-compose.prod.yml logs -f backend
docker compose -f docker-compose.prod.yml logs -f ais-relay
docker compose -f docker-compose.prod.yml logs -f military-relay
docker compose -f docker-compose.prod.yml logs -f frontend

# Restart a single service (e.g. after updating .env.production)
docker compose -f docker-compose.prod.yml restart backend

# Rebuild and redeploy only the frontend (e.g. after a UI change)
VITE_MAPBOX_TOKEN=pk.your_token_here \
  docker compose -f docker-compose.prod.yml up -d --build frontend

# Rebuild and redeploy all services
VITE_MAPBOX_TOKEN=pk.your_token_here \
  docker compose -f docker-compose.prod.yml up -d --build

# Check container health status
docker compose -f docker-compose.prod.yml ps

# Stop all services (data in postgres is preserved in its volume)
docker compose -f docker-compose.prod.yml down

# Stop all services and remove volumes (DESTRUCTIVE — deletes application images)
docker compose -f docker-compose.prod.yml down --volumes --rmi local
VITE_MAPBOX_TOKEN must be present as an environment variable in the shell when running any docker compose ... --build command that includes the frontend service. The token is injected as a Docker build argument (ARG VITE_MAPBOX_TOKEN) and compiled into the JavaScript bundle by Vite at image build time. It is not read from .env.production at runtime — if it is missing during the build, the map will fail to initialise with a token error in the browser console.

Build docs developers (and LLMs) love