How to Deploy GeoServer on OpenShift

This week, I attended the FOSS4G North America conference, in St. Louis. It’s a gathering of geospatial geeks to learn and collaborate on what’s happening in the community. A few of my colleagues and I were there representing Red Hat, a sponsor of the event. We were showing how the OpenShift Container Platform enables continuous delivery and containerization of geospatial applications.

With that thought on the brain, I’d like to show how to build a GeoServer container and deploy it on OpenShift. You’ll need access to an OpenShift install. If you don’t have it running already, try OpenShift Online, or create a Red Hat Developer account to download it for free.

First, we need to login to the cluster:

$ oc login
Authentication required for (openshift)
Username: nsabine
Login successful.

You don’t have any projects. You can try to create a new project, by running oc new-project

And create a project:

$ oc new-project geoserver
Now using project “geoserver” on server “".

Now that we have a sandbox to play in, let’s tell OpenShift how we want our container image to be built:

$ oc new-build --binary=true \
 --image-stream=jboss-webserver31-tomcat8-openshift:1.1 \

–> Found image bcf0df4 (8 weeks old) in image stream “openshift/jboss-webserver31-tomcat8-openshift” under tag “1.1” for “jboss-webserver31-tomcat8-openshift:1.1”

JBoss Web Server 3.1 Platform for building and running web applications on JBoss Web Server 3.1 - Tomcat v8 Tags: builder, java, tomcat8 * A source build using binary input will be created * The resulting image will be pushed to image stream “geoserver:latest” * A binary build was created, use ‘start-build –from-dir’ to trigger a new build

–> Creating resources with label build=geoserver … imagestream “geoserver” created buildconfig “geoserver” created –> Success

This command creates a new Build, and specifies the Source-to-Image (S2I) builder image that we plan to use. The builder image is a container that knows how to take a set of defined inputs, such as application source code, binary files, and configuration, and produce a runnable container image for that application. It’s basically a mini CI/CD process, self contained in a container. We chose a Java servlet container builder, and told it that we will be providing a binary WAR file as input.

Now that we’ve created our builder, we need to give it the GeoServer war file as input.

Download the GeoServer Web Archive package. You can get the latest from At the time of writing, the current release is 2.13.0.

$ wget
$ unzip geoserver*.zip

There are a number of files in the zip archive, but we’re really just interested in the war file. We need to rename it from geoserver.war to ROOT.war, so that the application server will deploy it as the default application, so we don’t have to type in /geoserver when we go to the web interface.

$ mv geoserver.war ROOT.war

Now let’s send it to OpenShift to start the build:

$ oc start-build geoserver --from-file=ROOT.war --follow

Uploading file “ROOT.war” as binary input for the build … build “geoserver-1” started Receiving source from STDIN as file ROOT.war Copying all war artifacts from /tmp/src directory into /opt/webserver/webapps for later deployment… ‘/tmp/src/ROOT.war’ -> ‘/opt/webserver/webapps/ROOT.war’ Copying all war artifacts from /tmp/src/deployments directory into /opt/webserver/webapps for later deployment…

Pushing image docker-registry.default.svc:5000/geoserver/geoserver:latest … Pushed 6/7 layers, 86% complete Pushed 7/7 layers, 100% complete Push successful

This uploads the war file and starts the build process. You’ll see it pause for a few moments while the file is uploaded. This may be pretty quick if your OpenShift is local, but could be a few minutes, depending on your available bandwidth. On my hotel WiFi, the build completed in two minutes.

Now that we have built a new container image, let’s run it:

$ oc new-app geoserver

–> Found image 5b17fc9 (2 minutes old) in image stream “geoserver/geoserver” under tag “latest” for “geoserver” Platform for building and running web applications on JBoss Web Server 3.1 - Tomcat v8 Tags: builder, java, tomcat8 * This image will be deployed in deployment config “geoserver” * Ports 8080/tcp, 8443/tcp, 8778/tcp will be load balanced by service “geoserver” * Other containers can access this service through the hostname “geoserver”

–> Creating resources … deploymentconfig “geoserver” created service “geoserver” created –> Success Run ‘oc status’ to view your app.

You’re now starting up GeoServer! If you log in to the web interface, it should look something like this:

GeoServer Deployment in OpenShift Console

GeoServer Deployment in OpenShift Console

It will take a minute for the application server to start up and deploy the app, so you can monitor progress by viewing the logs. First, find the name of the Kubernetes pod running the application, then tail the logs of that pod:

$ oc get pods
NAME                READY     STATUS      RESTARTS   AGE
geoserver-1-build   0/1       Completed   0          11m
geoserver-1-sk6fd   1/1       Running     0          7m

$ oc logs -f pods/geoserver-1-sk6fd

2018-05-16 02:32:53,475 [main] INFO org.apache.catalina.startup.Catalina- Server startup in 54708 ms

Ctrl-C to exit the logs once you see this last line, indicating the server has started.

Now that the server is running, we just need to create a route so we can access it. To do this, we expose a service, then query for the generated URL:

$ oc expose svc/geoserver
route “geoserver” exposed

$ oc get routes NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD geoserver geoserver 8080-tcp None

Open your favorite web browser to that URL, and you’ve got GeoServer!

Containerized GeoServer, deployed on OpenShift!

Containerized GeoServer, deployed on OpenShift!

One more thing to do… containers are immutable, so any configuration or data stored in GeoServer will be erased when the container is restarted. The pod needs persistent storage. Challenge accepted.

Create a Persistent Volume Claim. Note that this assumes your OpenShift has Persistent Volumes precreated or a Dynamic Volume Provisioner.

$ oc set volume dc/geoserver --add --name=geoserver-data -t pvc \
  --claim-name=geoserver-claim \
  --claim-size=50G \

deploymentconfig "geoserver" updated

Once the the container redeploys, it will have an empty persistent volume attached. We need to populate it with the initial data load before GeoServer will recognize it. To do this, we need to run a command in the container. First, get the pods, noting the name of the pod with status Running, then run the following exec command to copy the data:

$ oc get pods
NAME                READY     STATUS      RESTARTS   AGE
geoserver-1-build   0/1       Completed   0          1h
geoserver-5-qg7hd   1/1       Running     0          15m

$ oc exec geoserver-5-qg7hd -- bash -c 'cp -rp /deployments/ROOT/data/* /geoserver-data/'

Now that the persistent volume is populated, let’s tell GeoServer to use it:

$ oc set env dc/geoserver GEOSERVER_DATA_DIR=/geoserver-data
deploymentconfig "geoserver" updated

After the application finishes redeploying, GeoServer will preserve configuration and data across restarts. Map away!