How to Deploy GeoLibre Using Docker and Nginx: Production Deployment Guide

Deploy GeoLibre using a multi-stage Docker build that compiles the Vite-based UI into static assets and serves them via Nginx, with built-in proxy support for the FastAPI sidecar on port 8765.

GeoLibre is a modern single-page geospatial web application maintained by the opengeos organization. You can deploy GeoLibre using Docker and Nginx to create a lightweight, production-ready container that requires no runtime Node.js or Python dependencies in the final image, resulting in a footprint of approximately 30 MB.

Overview of the Container Architecture

The deployment strategy relies on a multi-stage build defined in the Dockerfile at the repository root. This architecture separates the Node.js build environment from the Alpine Linux runtime, ensuring that only static assets and the Nginx binary exist in the final layer.

The build process follows three distinct phases:

  1. Build Stage – Uses node:22-alpine to execute npm ci and npm run build, producing optimized assets in apps/geolibre-desktop/dist/
  2. Serve Stage – Copies the dist/ directory into nginx:alpine alongside a custom configuration
  3. Runtime – The docker/entrypoint.sh script launches Nginx in the foreground to keep the container alive

Step 1: Configure the Multi-Stage Dockerfile

The root Dockerfile implements the complete build pipeline. The first stage installs dependencies and compiles the TypeScript/Vite application, while the second stage prepares the Nginx environment.


# Stage 1 – build the UI

FROM node:22-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build   # produces apps/geolibre-desktop/dist/

# Stage 2 – serve with Nginx

FROM nginx:alpine
COPY --from=builder /app/apps/geolibre-desktop/dist/ /usr/share/nginx/html/
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY docker/entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]

The COPY --from=builder instruction transfers only the compiled static files, discarding the Node.js toolchain and source code to minimize image size.

Step 2: Configure Nginx for Static Assets and API Proxying

The docker/nginx.conf file configures Nginx to serve the single-page application and proxy API requests to the FastAPI sidecar. This configuration handles client-side routing and backend communication through a single port.

server {
  listen 80;
  server_name _;
  
  # Serve static files

  root /usr/share/nginx/html;
  index index.html;

  # Fallback for SPA routing

  location / {
    try_files $uri $uri/ /index.html;
  }

  # Proxy API requests to the FastAPI side‑car

  location /sidecar/ {
    proxy_pass http://host.docker.internal:8765/;
    proxy_set_header Host $host;
  }
}

The try_files directive ensures that deep links in the single-page application resolve correctly by falling back to index.html. The /sidecar/ location forwards requests to the FastAPI backend running on port 8765.

Step 3: Create the Container Entrypoint

The docker/entrypoint.sh script ensures Nginx runs as the primary process in the container. This allows Docker to track the process lifecycle and stream logs to stdout.

#!/bin/sh

# Ensure Nginx stays in the foreground so Docker can track the process

exec nginx -g "daemon off;"

Using exec replaces the shell process with Nginx, while daemon off prevents Nginx from forking into the background. This pattern is essential for proper signal handling and container orchestration.

Building and Running the Container

Execute these commands from the repository root to build and start the application:


# Build the image

docker build -t geolibre:latest .

# Run the container, mapping host port 8080 to container port 80

docker run -d -p 8080:80 geolibre:latest

Access the application at http://localhost:8080. The container serves the optimized bundle from apps/geolibre-desktop/dist/ and proxies /sidecar/ requests to the backend.

Deploying with Docker Compose (Optional)

For environments requiring the FastAPI sidecar, use this docker-compose.yml configuration to orchestrate both services:

version: "3.8"
services:
  geolibre:
    build: .
    ports:
      - "8080:80"
    depends_on:
      - sidecar

  sidecar:
    image: ghcr.io/opengeos/geolibre-sidecar:latest
    ports:
      - "8765:8765"

The depends_on directive ensures the sidecar starts before the GeoLibre container attempts to proxy requests. This setup is ideal for local development and staging environments where both components must run simultaneously.

Summary

  • The multi-stage Dockerfile builds the Vite application in node:22-alpine and serves it via nginx:alpine, eliminating Node.js from the production image.
  • The docker/nginx.conf configuration enables SPA routing with try_files and proxies /sidecar/ requests to port 8765.
  • The docker/entrypoint.sh script executes nginx -g "daemon off;" to maintain the container process.
  • Final image size is approximately 30 MB, containing only static assets and the Nginx binary.
  • Docker Compose can orchestrate the GeoLibre container alongside the FastAPI sidecar for complete stack deployment.

Frequently Asked Questions

What is the final Docker image size?

The final production image is approximately 30 MB. Because the multi-stage build discards the Node.js toolchain and retains only the static assets from apps/geolibre-desktop/dist/ plus the Alpine Nginx base image, the deployment footprint remains minimal compared to full-stack Node.js containers.

How do I change the FastAPI sidecar proxy URL?

Modify the proxy_pass directive in docker/nginx.conf. Replace http://host.docker.internal:8765/ with your backend URL, such as http://api:8000/ when using Docker Compose internal networking, or a fully qualified external URL for remote backends. Rebuild the image after editing the configuration.

Can I deploy GeoLibre without the FastAPI sidecar?

Yes. The Nginx configuration functions independently of the sidecar. If the /sidecar/ location is not available, Nginx will return a 502 error for those specific routes, but the static GeoLibre UI will continue to serve normally. You can also remove the location /sidecar/ block from docker/nginx.conf if the proxy functionality is not required.

Why does the entrypoint script use "daemon off"?

Nginx defaults to running as a background daemon, which would cause the Docker container to exit immediately after starting. The daemon off; directive keeps Nginx in the foreground, allowing Docker to monitor the process, handle signals correctly, and capture logs through the container's stdout/stderr streams.

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 →