Skip to content

🌍 Blog#

Geotribu in English, translated or original blog posts around GIS and geo*.

What’s under the hood of the official QGIS Server Docker image?

Introduction

logo QGIS

Since the last Geotribu article about deploying QGIS Server dates back to 2010, the release of an official QGIS Server Docker image last year is a great opportunity to catch up!

As a quick reminder, QGIS Server is an open-source web mapping server solution—similar to GeoServer or MapServer—that allows you to serve maps and geospatial data on the web. It relies on OGC (Open Geospatial Consortium) standards to provide interoperable services. QGIS Desktop offers a graphical interface for users to create and edit their maps.

The official QGIS Server documentation explains in detail how to install QGIS Server natively, i.e. directly from your platform or distribution’s package repositories. After the official QGIS Server image was released last year, Anita Graser shared a great post about its usage. However, comprehensive resources on containerized deployment remain limited. That’s why in this article, we’ll take a look at how and why using the official QGIS Server Docker image makes deployment much easier.

Technical ecosystem

logo Docker

Yes, there has been an official QGIS Server Docker image since 2024 🎉... but what does that actually mean? Let’s start by first reviewing the underlying tech stack and a bit of vocabulary.

Docker

Docker is a platform that allows you to create, deploy, and run applications in containers, ensuring their portability and isolation. These applications can be distributed as images through specialized platforms or rebuilt from source code.

Dockerfile

A Dockerfile is a script that defines the steps to build a custom Docker image.

Docker Hub

Docker Hub is an online platform that allows you to store, share, and distribute Docker images, providing a centralized repository for applications and their components.

docker compose

Composition – typically via the Docker Compose tool – allows you to define and manage multi-container applications using a configuration file that describes the services, networks, and volumes required for the application.

Cluster cloud

Clustering – not covered in this article – refers to the management of multiple Docker instances distributed across several physical or virtual machines to increase the availability, resilience, and scalability of the application. It typically involves cloud environments, such as those based on Kubernetes.

Many QGIS Server Docker images are available online, each with its own specificities related to its use and configuration. However, since 2024, the image originally provided by OPENGIS.ch is now available as an official image on the QGIS.org repository on Docker Hub. To get it, it’s as simple as:

Downloading the official QGIS Server image
docker pull qgis/qgis-server:ltr

Study of the content

To deploy QGIS Server as an application, it is necessary to integrate third-party services into the container to ensure its proper functioning. As stated in the documentation, QGIS Server is an application:

  • Requiring a graphical server.
  • Based on the FastCGI communication protocol to interact with a web server.
  • Depending on the web server used (Apache, NGINX, etc.), a specific utility may be required to launch the underlying FCGI process.

To meet these requirements, several technical solutions can be considered, which partly explains the diversity of QGIS Server Docker images available online. The image provided by OPENGIS.ch relies on:

  • Xvfb (X virtual framebuffer) as the graphical server.
  • NGINX as the web sever.
  • spawn-fcgi as the utility to run the QGIS Server FastCGI application.

QGIS Server and FastCGI

logo X11

It is possible to easily test the QGIS Server application from the command line as long as a graphical server is running. To do this, you need to simulate the passing of environment variables to the FCGI process, as a web server would do. For example, you can send a request to QGIS Server using the REQUEST_URI environment variable:

Execution of a request by the QGIS Server FCGI process
# Starting a shell in a QGIS Server container
$ docker run -it qgis/qgis-server:ltr /bin/bash

# Running the virtual Xvfb graphical server in the background and redirecting
# standard output (file descriptor 1) and standard error (file descriptor 2)
# to /dev/null to suppress any output
$ /usr/bin/Xvfb :99 > /dev/null 2>&1 &

# Sending a request to QGIS Server and redirecting the logs to /dev/null
$ REQUEST_URI="MAP=fake.qgs" /usr/lib/cgi-bin/qgis_mapserv.fcgi 2>/dev/null
Content-Length: 195
Content-Type: text/xml; charset=utf-8
Server:  QGIS FCGI server - QGIS version 3.43.0-Master
Status:  500

<?xml version="1.0" encoding="UTF-8"?>
<ServerException>Project file error. For OWS services: please provide a SERVICE and a MAP parameter pointing to a valid QGIS project file</ServerException>

Here, we observe a 500 error code from QGIS Server indicating that the fake.qgs project specified via REQUEST_URI="MAP=fake.qgs" does not exist. The exception <ServerException>Project file error.</ServerException> is therefore returned by QGIS Server.

Note

Xvfb :99 starts a virtual X server with the display number :99, meaning that graphical applications running with this X server instance will not be displayed on a real monitor but will be rendered in memory. Once this virtual X server is started, the environment variable ENV DISPLAY :99 is set in the Dockerfile so that the QGIS Server application knows where to render in memory.

Startup script

Previously, we started the QGIS Server container in interactive mode using the -i option and the /bin/bash command. Without these options, the QGIS Server application starts normally based on the CMD or ENTRYPOINT instruction specified in the Dockerfile.

The startup script used, located in the container's filesystem, can be found at /usr/local/bin/start-xvfb-nginx.sh, and its path can be obtained by inspecting the image.

Inspecting the image to locate the startup script
# Retrieving the startup script path
$ docker inspect -f '{{.Config.Cmd}}' qgis/qgis-server:ltr
[/bin/sh -c /usr/local/bin/start-xvfb-nginx.sh]

# Displaying the contents of the startup script
$ docker run qgis/qgis-server:ltr cat /usr/local/bin/start-xvfb-nginx.sh

By examining the contents of this script, we can observe the startup sequence of the third-party utilities mentioned above:

  • the graphical server Xvfb
  • spawn-fcgi, which launches QGIS Server while specifying TCP port 9993 for communication between the web server and the FCGI process
  • NGINX, if needed, depending on the SKIP_NGINX environment variable set at runtime
Retrieving the path of the startup script
[...]
/usr/bin/Xvfb :99 -ac -screen 0 1280x1024x16 +extension GLX +render -noreset >/dev/null &
XVFB_PID=$(waitfor /usr/bin/Xvfb)

if [ -z "$SKIP_NGINX" ] || [ "$SKIP_NGINX" == "false" ] || [ "$SKIP_NGINX" == "0" ]; then
    nginx
    NGINX_PID=$(waitfor /usr/sbin/nginx)
fi

spawn-fcgi -n -u ${QGIS_USER:-www-data} -g ${QGIS_USER:-www-data} -d ${HOME:-/var/lib/qgis} -P /run/qgis.pid -p 9993 -- /usr/lib/cgi-bin/qgis_mapserv.fcgi &
[...]

NGINX configuration

logo nginx

The NGINX web server is configured by substituting its default configuration file /etc/nginx/nginx.conf with a customized one to ensure that the server behaves as desired.

Displaying the contents of the NGINX configuration file
$ docker run -it qgis/qgis-server:ltr cat /etc/nginx/nginx.conf
[...]
    location /ogc/ {
        rewrite ^/ogc/(.*)$ /qgis/qgis_mapserv.fcgi?map=/io/data/$1/$1.qgs;
    }
    # Direct access without map rewrite
    location /ows/ {
        rewrite ^/ows/$ /qgis/qgis_mapserv.fcgi;
    }
    location /wfs3/ {
        rewrite ^/wfs3/(.*)$ /qgis/qgis_mapserv.fcgi;
    }
    location /qgis/ {
        internal; # Used only by the OGC rewrite
        root /var/www/data;
        fastcgi_pass  localhost:9993;
[...]

In this configuration, we distinguish three public entry points and one internal entry point:

  • Access via /ogc/my_project, which specifically expects a QGIS project located at /io/data/my_project/my_project.qgs.
  • Access via /ows/ and /wfs3/.
  • An internal access point via /qgis, used by the other endpoints, enabling communication with the FCGI process via the socket localhost:9993.

Entry point number 1 therefore requires mounting the /io/data/ directory to work with a dedicated directory for each project.

Container startup

Now that we've explored the features of our image, we can test the different launch configurations.

Default configuration

It is possible to start the QGIS Server container with the default configuration using the parameters below:

  • Redirecting local port 8080 to port 80 of the container's web server (-p option).
  • Mounting the QGIS project directory to /io/data inside the container (-v option).
Starting a QGIS Server container
# Cloning the QGIS QGIS-Training-Data repository
git clone https://github.com/qgis/QGIS-Training-Data

# Preparing the world/world.qgs project directory for use as a mount point
# /io/data
cp -r QGIS-Training-Data/exercise_data/qgis-server-tutorial-data/ \
    QGIS-Training-Data/exercise_data/world

# Starting a container by mounting the QGIS project directory from the tutorial
docker run \
    -v ./QGIS-Training-Data/exercise_data/:/io/data \
    -p 8080:80 \
    qgis/qgis-server:ltr

Once the container is deployed, it is possible to send requests to QGIS Server through the NGINX entry points described in the previous section.

Entry points /ogc, /ows and /wfs3
# WMS request to /ogc on the NGINX web server of the container to access the project
# /io/data/world/world.qgs
curl "http://localhost:8080/ogc/world?SERVICE=WMS&REQUEST=GetCapabilities"

# WMS request to /ows, explicitly specifying the project path via the MAP parameter
curl "http://localhost:8080/ows/?MAP=/io/data/qgis-server-tutorial-data/world.qgs&SERVICE=WMS&REQUEST=GetCapabilities"

# OGC API Features request to /wfs3, explicitly specifying the project path via the
# MAP parameter
curl "http://localhost:8080/wfs3/collections.json?MAP=/io/data/qgis-server-tutorial-data/world.qgs"

The OGC API Features protocol, also known as WFS3, also provides HTML rendering, allowing direct access via your browser to a web page for exploring the underlying data.

URL of an HTML rendering page for OGC API Features
http://localhost:8080/wfs3/collections/countries/items/65.html?MAP=/io/data/qgis-server-tutorial-data/world.qgs

OGC API Features Landing Page

Composition with external NGINX

logo docker compose

As mentioned earlier, an environment variable SKIP_NGINX allows using the QGIS Server container without the integrated web server. In this case, the QGIS Server container operates solely as a graphical rendering backend. It is then possible to use composition to create a multi-container application with:

  • A QGIS Server container for graphical rendering.
  • An NGINX container as the web server that redirects requests to the FCGI process via socket 9993.

First, the NGINX configuration file describes an access point and how to communicate with QGIS Server:

NGINX configuration file nginx.conf
events {
    worker_connections  1024;
}

http {
    upstream qgis-fcgi {
        server qgis-server:9993;
    }
    server {
        location /qgisserver/ {
            fastcgi_pass  qgis-fcgi;
            fastcgi_param QUERY_STRING $query_string;
            include fastcgi_params;
        }
    }
}

Next, a configuration file for the docker compose tool must be written to describe our multi-container application:

docker-compose.yml configuration file
services:
  nginx:
    image: "nginx"
    volumes:
      # mounting the NGINX configuration file into the container
      - ./nginx.conf:/etc/nginx/nginx.conf
    ports:
      - "8080:80"
    depends_on:
      - qgis-server
  qgis-server:
    image: "qgis/qgis-server:ltr"
    environment:
      # disabling the internal NGINX server
      SKIP_NGINX: "true"
    volumes:
      # mounting the project directory
      - ./QGIS-Training-Data/exercise_data/:/io/data

Warning

The configuration scripts above are intentionally simplified for the reader’s understanding and should not be used in a production environment 💣

Finally, we just need to run our containers using the docker compose command, which will automatically read the configuration file named docker-compose.yml located in the current directory:

# Running the containers in detached mode
$ docker compose up -d
[+] Running 3/3
  Network tmp_default          Created
  Container tmp-qgis-server-1  Started
  Container tmp-nginx-1        Started

# Status of the running NGINX and QGIS Server containers
$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED             STATUS             PORTS                                               NAMES
30dce3dd878f   nginx                  "/docker-entrypoint.…"   3 seconds ago       Up 2 seconds       0.0.0.0:8081->80/tcp, [::]:8081->80/tcp             test-nginx-1
8b73de48d754   qgis/qgis-server:ltr   "/bin/sh -c /usr/loc…"   3 seconds ago       Up 2 seconds       80/tcp, 9993/tcp                                    test-qgis-server-1

# GET request to /qgisserver, explicitly specifying the project path using the MAP parameter
$ curl "http://localhost:8080/qgisserver/?MAP=/io/data/qgis-server-tutorial-data/world.qgs&SERVICE=WMS&REQUEST=GetCapabilities"

What about the plugins?

logo pyqgis

Since the beginning of this article, we’ve had fun (for sure! ✨!) exploring the official QGIS Server image through some reverse engineering. However, it is also possible to refer to the documentation or examine the Dockerfile used to build this image.

Looking at this file more closely, we can see the presence of the instruction ENV QGIS_PLUGINPATH /io/plugins. This implies that QGIS Server expects to have Python plugins in the specified directory. To test this mechanism, the wfsOutputExtension plugin from 3Liz can be deployed:

Deployment of the wfsOutputExtension plugin
# Creating a dedicated directory for plugins
mkdir plugins

# Cloning the wfsOutputExtension server plugin
git clone https://github.com/3liz/qgis-wfsOutputExtension plugins

# Starting a container by mounting the QGIS project and plugin directories
docker run \
    -v ./QGIS-Training-Data/exercise_data/:/io/data \
    -v ./plugins/:/io/plugins \
    -p 8080:80 \
    qgis/qgis-server:ltr

Thanks to the wfsOutputExtension plugin, it is possible to specify various additional formats through the OUTPUTFORMAT parameter of the WFS GetFeature request. For example, we can specify the csv format, which is not natively supported by QGIS Server:

Executing a WFS GetFeature request
$ curl "http://localhost:8080/ows/?MAP=/io/data/qgis-server-tutorial-data/world.qgs&SERVICE=WFS&REQUEST=GetFeature&TYPENAME=countries&FEATUREID=countries.1&OUTPUTFORMAT=csv"
gml_id,id,name
countries.1,1,Antigua and Barbuda

Conclusion

The official QGIS Server Docker image greatly simplifies the deployment of this map server solution, offering a ready-to-use configuration that is easily adaptable. With Docker, deploying QGIS Server becomes easy, portable, and isolated, without worrying about complex dependencies like graphic servers or web servers.

By integrating NGINX, Xvfb, and FastCGI, the image ensures smooth operation of QGIS Server in a containerized environment. It also provides the option to use an external web server, like NGINX, to separate functions and have better control over configurations

Share it:

Working with JSON in PostgreSQL

logo JSON

As part of a personal project, I wanted to store a large part of the INSEE's french census data in a PostgreSQL database with multi-millennial tables. The problem is that, within the same dataset, the fields can change over the years, which makes it impossible to create a fixed table structure. The solution? Use semi-structured data, i.e. store this data in JSON in a table field. This article is a summary of that experience.

Unscheduled obsolescence

This work was carried out before the release of PostgreSQL 17, which adds important features for JSON with JSON_TABLE, so it won't be mentioned here.

Since we're going to be talking about JSON and semi-structured data, I feel obliged to start this article with a warning.

The relational model is good, eat it up, and integrity constraints were invented for good reason.

This article is not intended to be an invitation to go into YOLO mode on data management: “all you have to do is put everything in JSON” (like a vulgar dev who would put everything in MongoDB, as the bad tongues would say).

Upcoming local FOSS4G in Bulgaria on 7th-8th March

QGIS.бг is an informal organization that brings together the Bulgarian FOSS geo enthusiasts. In the landscape dominated by proprietary software, the QGIS.bg is providing learning materials at beginner and advanced level, in Bulgarian language, for free to anyone who wants to know more about open source GIS and technologies.

QGIS.bg logo

Installing QGIS on Ubuntu: a simple and effective guide

Ubuntu logo

It may sound surprising, but installing the most widely used open-source GIS software on the most popular Linux distribution is still not as seamless as it should be. Even experienced users sometimes struggle with repository configurations, package dependencies, authentication keys and other system administration intricacies.

Geographer taming a penguin

The challenge isn't just technical. QGIS's official installation documentation, while thorough, can be difficult to navigate for those who aren't developers or seasoned Linux users. Plus, regular updates and changes in the software lifecycle can introduce unexpected hurdles for everyday users.

That said, there's no point in complaining, it’s free software and open-source contributors deserve appreciation, not frustration! And I speak from experience. 😉

Follow the Vendée Globe 2024 from a GIS

What is the Vendée Globe?

logo Vendée Globe

Before we start talking about GIS and technical aspects, let's talk about the Vendée Globe.

It is a solo sailing race, non-stop and without assistance, around the world. It has been held every 4 years since 1989. The start is in Les Sables d'Olonne. The course consists of going down the Atlantic, then passing successively under Africa and the Cape of Good Hope, under Australia and Cape Leeuwin and finally under South America and Cape Horn, to return to Vendée as quickly as possible. The record was set by Armel Le Cléac'h during the 2016-2017 edition with a journey of 74 days 3 hours and 35 minutes.

map

Creating a Python virtual environment for developing QGIS plugin with VS Code on Windows

Introduction

PyQGIS logo

Anyone who has tried it, knows that configuring a Python, PyQGIS, and PyQt environment on Windows for developing QGIS plugins is a real challenge. Often, it feels like a losing battle...

Well, not anymore! After scouring the depths of the internet and exploring tips provided by Julien, here is one method to have (almost) all the auto-completions for PyQGIS, PyQt and more in VS Code.

Introducing QChat: a chat room right into QGIS!

icône globe speech GIS Chat - Credits: Global Market by DARAYANI from Noun Project (CC BY 3.0)

We're excited to announce the release of a new feature integrated into our QTribu plugin for QGIS: QChat! This new addition allows you to collaborate in real time with your team or other GIS fellows directly from QGIS.

We're in 2024 (unless you're reading this in 2025, or 2026, or... well, you get the idea), and let's be honest, Teams or IRC have their cons. Plus, you can't even use the hashtag #GISchat there, you can't meet fellow GIS enthusiasts and you definitely can't win Geotribu stickers 😉... Honestly, it's time for something new.

Introducing a new way to chat directly in QGIS: QChat, a feature that lets you communicate with your peers within the best desktop GIS software around. The question is: why? And the answer: why not?