# How the GeoLibre Docker Container Bundles Nginx and the Python Sidecar

> Discover how the GeoLibre Docker container uses multi-stage builds to bundle Nginx and a Python FastAPI sidecar. Learn how Nginx reverse proxies the sidecar at the /sidecar path.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: internals
- Published: 2026-08-01

---

**The GeoLibre Docker image uses a multi-stage build that combines a static nginx-served frontend with a Python FastAPI sidecar, reverse-proxied behind the same nginx instance at the `/sidecar` path.**

This article explains how the `opengeos/GeoLibre` repository packages a web-based geospatial application and its optional conversion service into a single production-ready container. The architecture separates build-time asset generation from runtime delivery, enabling efficient caching and minimal final image size.

## Multi-Stage Build Architecture

The `Dockerfile` implements a two-stage build process that isolates Node.js build dependencies from the Python runtime environment.

### Stage 1: Node Build Environment

The first stage uses `node:22-alpine` with platform awareness to compile the frontend assets.

```bash
FROM --platform=$BUILDPLATFORM node:22-alpine AS builder

```

Key build steps in this stage:

1. **Layer caching optimization** — All workspace [`package.json`](https://github.com/opengeos/GeoLibre/blob/main/package.json) files are copied before the main source tree, allowing `npm ci` to create a cached dependency layer that only invalidates when packages change.
2. **Full source copy** — After dependency installation, the complete source tree is copied into the build context.
3. **Production build** — Running `npm run build` generates optimized static assets in `apps/geolibre-desktop/dist`.

### Stage 2: Python Runtime Environment

The runtime stage uses `python:3.12-slim-bookworm` specifically for glibc compatibility with pre-built Python wheels.

**Core runtime dependencies:**

- `nginx` — Static file server and reverse proxy
- `libexpat1` — XML parsing support
- `openssl` — Cryptographic operations

**Sidecar installation:**

The FastAPI sidecar source from `backend/geolibre_server` is copied to `/opt/geolibre_server` and installed alongside **duckdb** and **rio-cogeo** wheels. Platform-specific handling includes `freestiler` and `whitebox-workflows` wheels for **amd64** only—they are skipped on **arm64** due to unavailable pre-built binaries.

## Nginx Configuration and Routing

The nginx setup bridges the frontend and sidecar services under a unified host.

### Configuration Template

The file [`docker/nginx.conf`](https://github.com/opengeos/GeoLibre/blob/main/docker/nginx.conf) serves as a template copied to `/etc/nginx/nginx.conf.template` during image build. This template supports runtime variable substitution for:

- **Sidecar authentication token** — Injected per container launch for security isolation
- **Optional Basic Auth** — Conditionally enabled via environment variables

### Runtime Configuration Generation

The [`docker/entrypoint.sh`](https://github.com/opengeos/GeoLibre/blob/main/docker/entrypoint.sh) script renders the template into [`/etc/nginx/conf.d/default.conf`](https://github.com/opengeos/GeoLibre/blob/main//etc/nginx/conf.d/default.conf) on container startup. This dynamic generation allows credential injection without rebuilding the image.

**Static asset serving:**

Built web assets from `apps/geolibre-desktop/dist` are placed in `/usr/share/nginx/html`, making the UI available at the container root (`/`).

**Sidecar proxying:**

All FastAPI endpoints are reverse-proxied through `/sidecar`, enabling same-origin access to conversion APIs without CORS configuration.

## Environment Configuration

The container accepts several environment variables to configure sidecar behavior:

| Variable | Purpose |
|----------|---------|
| `GEOLIBRE_CONVERSION_ROOTS` | Colon-separated paths the sidecar may access for file operations |
| `GEOLIBRE_CONVERSION_PYTHON` | Python interpreter path for conversion scripts |
| `GEOLIBRE_AUTH_USER` | Basic Auth username (optional) |
| `GEOLIBRE_AUTH_PASSWORD` | Basic Auth password hash (optional) |

## Health Check and Startup

The image includes a `HEALTHCHECK` directive that pings `/healthz` to verify sidecar responsiveness. This endpoint is explicitly exempt from Basic Auth requirements to ensure reliable orchestration health probes.

Container startup executes [`entrypoint.sh`](https://github.com/opengeos/GeoLibre/blob/main/entrypoint.sh), which:

1. Renders the nginx configuration template
2. Injects authentication credentials and sidecar token
3. Launches nginx as the primary process
4. Starts the sidecar via `uvicorn` as a background service

## Building and Running the Container

### Build the Image

```bash
docker build -t geolibre:latest .

```

### Run with Default Settings

Mount a local data directory to `/data` for file access:

```bash
docker run -d -p 8080:80 \
  -v "$(pwd)/data:/data" \
  --name geolibre \
  geolibre:latest

```

### Enable Basic Authentication

Provide credentials via environment variables at runtime:

```bash
docker run -d -p 8080:80 \
  -e GEOLIBRE_AUTH_USER=admin \
  -e GEOLIBRE_AUTH_PASSWORD=$(openssl passwd -apr1 secret) \
  -v "$(pwd)/data:/data" \
  --name geolibre \
  geolibre:latest

```

### Access the Sidecar API

Use the reverse-proxied path to reach conversion endpoints:

```bash
curl http://localhost:8080/sidecar/convert/vector

```

## Key Source Files

Understanding these files provides complete visibility into the bundling approach:

- **`Dockerfile`** — Multi-stage build orchestrating Node and Python environments
- **[`docker/nginx.conf`](https://github.com/opengeos/GeoLibre/blob/main/docker/nginx.conf)** — Server template with proxy rules and Content-Security-Policy headers
- **[`docker/entrypoint.sh`](https://github.com/opengeos/GeoLibre/blob/main/docker/entrypoint.sh)** — Runtime configuration generator and process supervisor
- **`backend/geolibre_server/`** — FastAPI implementation providing vector, raster, and Whitebox geoprocessing APIs
- **`apps/geolibre-desktop/dist/`** — Compiled static assets served by nginx

## Summary

- The **two-stage build** separates Node.js compilation from Python runtime, optimizing layer caching and final image size.
- **Nginx serves dual roles** as static file server for the frontend and reverse proxy for the sidecar at `/sidecar`.
- **Runtime configuration** via [`entrypoint.sh`](https://github.com/opengeos/GeoLibre/blob/main/entrypoint.sh) enables dynamic credential injection without image rebuilds.
- **Platform-aware builds** conditionally include `freestiler` and `whitebox-workflows` only on amd64 where wheels exist.
- **Health checks** verify sidecar availability while remaining accessible to container orchestrators.

## Frequently Asked Questions

### How does the sidecar communicate with the main application?

The sidecar runs as a separate process within the same container, accessed via nginx's reverse proxy at the `/sidecar` path. This same-origin routing eliminates CORS requirements and provides unified ingress through port 80.

### Can I run the nginx frontend without the Python sidecar?

The [`entrypoint.sh`](https://github.com/opengeos/GeoLibre/blob/main/entrypoint.sh) script starts both services by default. To disable the sidecar, you would need to override the container command or modify the entrypoint, though this is not the intended deployment pattern in the `opengeos/GeoLibre` source.

### Why use python:3.12-slim-bookworm instead of alpine for the runtime stage?

The slim-bookworm variant provides glibc compatibility required for pre-built wheels including duckdb and rio-cogeo. Alpine's musl libc often fails with many scientific Python packages that distribute only manylinux wheels.

### How is the sidecar token secured?

A unique token is generated on each container launch by [`entrypoint.sh`](https://github.com/opengeos/GeoLibre/blob/main/entrypoint.sh) and injected into the nginx configuration. This token must be included in sidecar requests, providing request authentication without persistent credential storage in the image.