How to Configure Nginx Reverse Proxy for the GeoLibre Web Build

GeoLibre provides a production-ready Nginx template in docker/nginx.conf that serves the compiled SPA from the dist/ folder, supports deep linking via try_files, and optionally proxies /api/ requests to a FastAPI backend using environment variable substitution at container startup.

Configuring an Nginx reverse proxy for the GeoLibre web build ensures your single-page application handles deep links correctly while maintaining the flexibility to proxy API calls to the Python FastAPI sidecar. The opengeos/GeoLibre repository ships a Dockerized Nginx setup that uses template rendering to inject runtime variables without requiring image rebuilds. This guide walks through the configuration structure, key directives, and deployment workflow based on the actual source implementation.

Understanding the Nginx Template Architecture

The reverse proxy configuration is not a static file but a template processed at container startup. This architecture allows the same Docker image to run in different environments (development, staging, production) without rebuilding.

The Template File Location

The primary configuration template resides at docker/nginx.conf in the repository root. During the Docker build, this file is copied into the image alongside the compiled web assets from apps/geolibre-desktop/dist. The Dockerfile places the template at /etc/nginx/nginx.conf.template, while the final rendered configuration is written to /etc/nginx/conf.d/default.conf at runtime.

Runtime Variable Injection

The docker/entrypoint.sh script handles the template rendering process. Before Nginx starts, this script substitutes environment variables (such as ${CSP} and ${BACKEND_URL}) into the template file. This mechanism allows you to modify security policies or backend endpoints via container environment variables without touching the source configuration.

Core Reverse Proxy Components

The Nginx configuration handles three primary responsibilities: serving static assets, ensuring SPA routing works correctly, and optionally proxying API requests to the backend.

Static Asset Serving and SPA Fallback

The web build output from apps/geolibre-desktop/dist is served from /usr/share/nginx/html. The critical directive for SPA support is the try_files statement:

location / {
  root /usr/share/nginx/html;
  index index.html;
  try_files $uri $uri/ /index.html;
}

This configuration ensures that direct requests to routes like /projects/123 fall back to index.html, allowing the client-side router to handle the navigation. Without this directive, Nginx would return 404 errors for deep links.

API Proxy to FastAPI Sidecar

When running the optional Python FastAPI sidecar on port 8765, the reverse proxy forwards requests under the /api/ path:

location /api/ {
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_pass ${BACKEND_URL};
}

Set the BACKEND_URL environment variable (for example, http://host.docker.internal:8765 or http://api:8765 in a Docker Compose network) when starting the container to activate this proxying behavior.

Health Check Endpoint

To support Docker's HEALTHCHECK command without triggering the SPA fallback logic, a dedicated endpoint returns a simple 200 response:

location = /healthz {
  access_log off;
  return 200 'OK';
}

This endpoint bypasses the try_files directive and provides a lightweight way for orchestrators to verify that Nginx is alive and responsive.

Security and Performance Headers

The template includes production-hardened defaults for security headers and compression.

Content Security Policy Configuration

The configuration sets a strict Content-Security-Policy (CSP) that aligns with the values defined in apps/geolibre-desktop/vite.config.ts. The template uses variable substitution for policy directives:

add_header Content-Security-Policy "${CSP}" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;

Define the CSP environment variable to include your specific connect-src, img-src, and other directives required for your deployment environment (for example, allowing http://localhost:* for local development or your specific CDN domains).

Compression Settings

Text-based assets are compressed automatically to improve load times:

gzip on;
gzip_types text/plain text/css application/javascript image/svg+xml;

This reduces bandwidth usage for JavaScript bundles and CSS files served from the dist directory.

Implementation Steps

Follow this workflow to deploy the GeoLibre web build with the Nginx reverse proxy:

  1. Build the web assets using npm run build in the geolibre-desktop workspace to generate the dist/ folder.
  2. Build the Docker image, which copies both the built assets and the Nginx template (docker/nginx.conf) into the image.
  3. Set environment variables before running the container:
    • CSP: The full Content-Security-Policy string
    • BACKEND_URL: The URL for the FastAPI sidecar (optional)
  4. Start the container using the entrypoint script, which renders the template and launches Nginx as PID 1.
  5. Access the application at http://localhost (port 80), with all SPA routes and API proxying functional.

Example Docker run command:

docker run -p 80:80 \
  -e CSP="default-src 'self'; connect-src 'self' http://localhost:*" \
  -e BACKEND_URL="http://host.docker.internal:8765" \
  opengeos/geolibre:latest

Summary

  • The working configuration lives in docker/nginx.conf and acts as a template for runtime variable substitution.
  • docker/entrypoint.sh renders the final Nginx configuration by injecting environment variables like ${CSP} and ${BACKEND_URL}.
  • The try_files $uri $uri/ /index.html directive in the root location block enables proper SPA routing for deep links.
  • API requests to /api/ are proxied to the FastAPI sidecar when the BACKEND_URL variable is configured.
  • Security headers and gzip compression are pre-configured in the template to meet production standards.

Frequently Asked Questions

Where is the Nginx configuration file located in the repository?

The source template is located at docker/nginx.conf in the opengeos/GeoLibre repository. When the Docker image builds, this file is copied to /etc/nginx/nginx.conf.template inside the container. At runtime, docker/entrypoint.sh processes this template and writes the rendered output to /etc/nginx/conf.d/default.conf before starting the Nginx process.

How does the configuration handle client-side routing in the GeoLibre SPA?

The configuration uses the try_files $uri $uri/ /index.html directive within the root location block. When a user accesses a deep link like /projects/123, Nginx first attempts to serve the file or directory literally. If neither exists, it falls back to serving index.html, allowing the React or Vue router to handle the client-side navigation and render the correct view.

Can I proxy requests to a backend API without modifying the nginx.conf file?

Yes. The template supports environment variable substitution for the BACKEND_URL. Set the BACKEND_URL environment variable (for example, to http://api-service:8765) when starting the container. The entrypoint script automatically substitutes this value into the proxy_pass directive within the /api/ location block, enabling reverse proxying without rebuilding the image.

What is the purpose of the /healthz endpoint?

The location = /healthz block provides a lightweight health check endpoint that returns HTTP 200 with the body "OK". This endpoint is exempt from the SPA fallback logic and access logging, making it ideal for Docker HEALTHCHECK commands or Kubernetes liveness probes to verify that the Nginx container is running and responsive without triggering application logic or cluttering logs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →