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

> Deploy GeoLibre with Docker and Nginx. Learn to build a static UI with Vite, serve it via Nginx, and proxy requests to the FastAPI sidecar for a robust production setup.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-03

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/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.

```dockerfile

# 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`](https://github.com/opengeos/GeoLibre/blob/main/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.

```nginx
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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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.

```bash
#!/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:

```bash

# 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`](https://github.com/opengeos/GeoLibre/blob/main/docker-compose.yml) configuration to orchestrate both services:

```yaml
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`](https://github.com/opengeos/GeoLibre/blob/main/docker/nginx.conf) configuration enables SPA routing with `try_files` and proxies `/sidecar/` requests to port 8765.
- The [`docker/entrypoint.sh`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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.