
Deploy GeoServer | (Just Updated) WMS WFS Map Server with PostGIS, Admin Password Preset, Data Survives Redeploys
GeoServer WMS/WFS/WMTS map server with a PostGIS spatial database
Just deployed
/var/lib/postgresql
geoserver
Just deployed
/opt/geoserver/data_dir
Deploy and Host GeoServer on Railway
Built for an LLM agent to deploy and use. There is no setup form and nothing to fill in: the admin password is generated at deploy time, a PostGIS database is wired in on the private network, and the contract below lists every address, variable name and one script that proves the REST API, the database link and the WMS service work.
GeoServer is the open source Java server for publishing geospatial data as web services. It serves maps over OGC WMS, WMTS and WCS and vector features over WFS, reads PostGIS, GeoTIFF, shapefiles and GeoPackage, and has a web admin UI and a full REST API for configuration.
About Hosting GeoServer
This template runs two services. geoserver is the kartoza/geoserver 3.0.2 image (GeoServer 3.0.2 on Tomcat), pinned by digest, with its data directory on a volume at /opt/geoserver/data_dir. postgis is PostgreSQL 18 with PostGIS 3.6 on its own volume, reachable only on Railway's private network. Layers, styles, workspaces, stores and the admin password all live in the volume, so they survive redeploys.
The admin user is admin. Its password comes from GEOSERVER_ADMIN_PASSWORD, generated per deploy; the image applies it on every boot, so changing the variable and redeploying changes the password. Proxy headers are on, so the capabilities documents and the admin UI advertise the https:// address Railway serves. The JVM heap starts at 512 MB and tops out at 1 GB (INITIAL_MEMORY and MAXIMUM_MEMORY on the geoserver service raise it); the service measured about 750 MB resident when idle, so plan for at least 1 GB of memory.
Agent contract
- Public URL:
GEOSERVER_PUBLIC_URL(https://plus the service'sRAILWAY_PUBLIC_DOMAINplus/geoserver). The admin UI is at/web/under it. - Private URL:
http://geoserver.railway.internal:8080/geoserver, exposed asGEOSERVER_PRIVATE_URL. - Credentials: user
admin, password inGEOSERVER_ADMIN_PASSWORD. The database link usesPOSTGIS_HOSTandPOSTGIS_PASSWORD(userpostgres, databaserailway, port 5432, private network only). - Auth: HTTP basic on
/rest/*; anonymous REST calls get401. OGC services (/wms,/wfs,/ows) are open for published layers. - Health:
GET /geoserver/wmsreturns 200 without credentials. - API: REST under
/geoserver/rest/(workspaces, datastores, layers, styles); OGC under/geoserver/WORKSPACE/wms,/wfs,/wcs.
# run with GEOSERVER_PUBLIC_URL, GEOSERVER_ADMIN_PASSWORD, POSTGIS_HOST and POSTGIS_PASSWORD set
AUTH="admin:$GEOSERVER_ADMIN_PASSWORD"
W="agent$(date +%s)"
curl -s -o /dev/null -w "health: %{http_code}\n" "$GEOSERVER_PUBLIC_URL/wms"
curl -s -o /dev/null -w "anonymous rest: %{http_code}\n" "$GEOSERVER_PUBLIC_URL/rest/workspaces.json"
curl -s -o /dev/null -w "workspace: %{http_code}\n" -u "$AUTH" -H "Content-Type: application/json" \
-d "{\"workspace\":{\"name\":\"$W\"}}" "$GEOSERVER_PUBLIC_URL/rest/workspaces"
curl -s -o /dev/null -w "postgis store: %{http_code}\n" -u "$AUTH" -H "Content-Type: application/json" \
-d "{\"dataStore\":{\"name\":\"pg\",\"connectionParameters\":{\"entry\":[{\"@key\":\"dbtype\",\"\$\":\"postgis\"},{\"@key\":\"host\",\"\$\":\"$POSTGIS_HOST\"},{\"@key\":\"port\",\"\$\":\"5432\"},{\"@key\":\"database\",\"\$\":\"railway\"},{\"@key\":\"schema\",\"\$\":\"public\"},{\"@key\":\"user\",\"\$\":\"postgres\"},{\"@key\":\"passwd\",\"\$\":\"$POSTGIS_PASSWORD\"}]}}}" \
"$GEOSERVER_PUBLIC_URL/rest/workspaces/$W/datastores"
curl -s -o /dev/null -w "postgis reachable: %{http_code}\n" -u "$AUTH" "$GEOSERVER_PUBLIC_URL/rest/workspaces/$W/datastores/pg/featuretypes.json?list=available"
echo "capabilities: $(curl -s -G --data-urlencode service=WMS --data-urlencode version=1.3.0 --data-urlencode request=GetCapabilities "$GEOSERVER_PUBLIC_URL/$W/wms" | grep -m1 -o WMS_Capabilities)"
A fresh deploy prints health: 200, anonymous rest: 401, workspace: 201, postgis store: 201, postgis reachable: 200 and capabilities: WMS_Capabilities.
Why Deploy GeoServer on Railway?
Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.
By deploying GeoServer on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.
- Agent-ready - generated admin password, contract block and one working script; no setup form.
- PostGIS included - a spatial database next to GeoServer on the private network, not exposed to the internet.
- State survives redeploys - the data directory and the database each sit on a volume.
- Credentials from variables - the admin password is applied from the environment on every boot.
- Correct public URLs - proxy headers are on, so capabilities documents point at your HTTPS domain, not an internal address.
Common Use Cases
- Web maps for a front end - serve WMS or WMTS tiles to Leaflet, OpenLayers or MapLibre.
- Spatial APIs for agents and apps - publish PostGIS tables as WFS features and query them over HTTP.
- A shared map server for a team - publish shapefiles, GeoTIFFs and GeoPackages with styles managed in the admin UI.
Dependencies for GeoServer Hosting
- PostgreSQL 18 with PostGIS 3.6 and one Railway volume, created by the template.
- One Railway volume for the GeoServer data directory, created by the template.
Deployment Dependencies
Implementation Details
| Variable | Purpose |
|---|---|
GEOSERVER_ADMIN_PASSWORD | Password of the admin user, generated per deploy. |
GEOSERVER_PUBLIC_URL | HTTPS address for clients outside Railway. |
GEOSERVER_PRIVATE_URL | Address on the private network, for services in the same project. |
CSRF_WHITELIST | Public domain allowed to post to the admin UI behind Railway's proxy. |
POSTGIS_HOST | Private hostname of the postgis service. |
POSTGIS_PASSWORD | Password of the postgres user in the database, generated per deploy. |
The start command sets the admin user, the heap sizes and the proxy-header switches, then runs the image's own entrypoint. GeoServer is not wired to PostGIS automatically because a store needs a workspace of your choosing; the contract script shows the REST call that creates one.
Template Content
geoserver
kartoza/geoserver:3.0.2