uMap on Ubuntu 24.04 on Azure User Guide
Overview
uMap is an open source web application for creating and sharing maps built on OpenStreetMap layers. You draw markers, lines and polygons directly in the browser, import your own geodata from GeoJSON, GPX, KML or CSV, choose icons, colours and styles, organise features into data layers, and share the finished map with a link or embed it in any web page. Because maps are drawn over standard OpenStreetMap tiles there is nothing heavy to host, and by default anyone can create a map without an account, editing it privately through a secret link, which is exactly how the public uMap service works.
uMap is a GeoDjango application, so the cloudimg image supplies the production stack around it: uMap 3.7.3 is installed at a pinned upstream release into a dedicated Python virtual environment, served by gunicorn and reverse proxied behind nginx, and backed by PostgreSQL 16 with the PostGIS spatial extension for map metadata and Redis for caching, all on the same instance. gunicorn is bound to loopback 127.0.0.1:8019 and is never exposed directly; nginx on port 80 is the only way in, plus an unauthenticated /healthz endpoint for load balancer probes. Map data layers are stored as GeoJSON files on disk and served efficiently by nginx.
The image is secure by default. No credential of any kind ships, and nothing is listening on the public port in the image at all, so there is no window in which a freshly launched VM is reachable with a known password. On the first boot of every VM a fresh Django secret key, a fresh PostgreSQL password, a fresh Redis password and a unique randomly generated administrator password are created, written to a root only file, and only then is the public site brought up. Backed by 24/7 cloudimg support.
What is included:
- uMap 3.7.3 installed in a dedicated Python virtual environment and running as the
umapgunicorn systemd service - Django 5.2 LTS on Python 3.12, within uMap's supported range
- The uMap web application on
:80, fronted by nginx with gunicorn bound to loopback only - A PostgreSQL 16 database with the PostGIS extension on loopback, holding map metadata, with a per VM database password generated on first boot
- Redis on loopback as the Django cache, with a per VM password generated on first boot
- Anonymous map creation enabled by default, the public by design uMap posture, with the Django admin reserved for a single instance administrator
- A ready made example map, Welcome to uMap by cloudimg, with markers, so the home page shows a real map the moment it boots
- No default account and no shipped secret: the administrator user and the Django secret key are both created on first boot with unique random values
- Host settings configured so the app works on the public IP address or a custom domain, with no baked in hostname
- An unauthenticated
/healthzendpoint for Azure Load Balancer health probes postgresql.service,redis-server.service,umap.service(gunicorn) andnginx.serviceas systemd units- 24/7 cloudimg support
Key facts
| Item | Value |
|---|---|
| Platform | Ubuntu 24.04 LTS (Gen2) |
| Default SSH user | azureuser |
| Application root | /srv/umap |
| Python virtual environment | /srv/umap/venv |
| Settings file | /etc/umap/umap.conf |
| Per VM environment file | /srv/umap/umap.env |
| Map data layers (GeoJSON) | /srv/umap/var/data |
| Service account | umap (non login system user) |
| Per VM credentials | /root/umap-credentials.txt |
| Recommended size | Standard_B2s (2 vCPU / 4 GiB RAM) |
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point. NSG inbound: allow 22/tcp from your management network, 80/tcp for uMap, and 443/tcp if you add TLS. The image serves plain HTTP on port 80; for production use, terminate TLS with your own domain and restrict access to trusted IP ranges (see Security Recommendations).
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for uMap by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create then Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name umap \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
Open port 80 to your network so you can reach uMap:
az vm open-port --resource-group <your-rg> --name umap --port 80
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
First boot generates the per VM secrets, creates the administrator account and then brings the public site up. It normally completes within a minute of the VM reaching Running. Check all four services:
systemctl is-active postgresql redis-server umap nginx
All four report active.

Confirm the health endpoint answers:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz
It returns 200.
Check that first boot completed. systemctl show reports the state and the outcome separately: a first boot that ran and succeeded shows ActiveState=active (it is a oneshot unit with RemainAfterExit=yes) together with Result=success.
systemctl show -p ActiveState -p Result -p ExecMainStatus umap-firstboot.service
The application server, the database and the cache are deliberately reachable only on the loopback interface. nginx on port 80 is the single public entry point:
sudo ss -tlnp | grep -E ':(80|8019|5432|6379)\b'

Step 5 - Retrieve your per VM administrator password
Every VM generates its own administrator password on first boot and writes it to a root only file. Nothing is shared between deployments and no password is baked into the image.
sudo cat /root/umap-credentials.txt
The file lists UMAP_URL, UMAP_ADMIN_USER (which is umapadmin) and UMAP_ADMIN_PASSWORD, along with the map creator and Django admin addresses. Store the password in your password manager and change it after your first sign in.
The credentials file and the environment file that holds the Django secret key are both restricted, and the appliance ships a checker that proves no secret has leaked into the system journal, the authentication log, /var/backups, shell history or any other log:
sudo ls -l /root/umap-credentials.txt /srv/umap/umap.env
sudo bash /usr/local/sbin/umap-secret-leak-check.sh
The checker prints LEAKCHECK_OK.
Step 6 - Open uMap and the example map
Browse to http://<vm-public-ip>/. uMap opens on its home page: a search box, a summary of what uMap does, a Create a map button, and a feed of maps on the instance.

The image ships one example map, Welcome to uMap by cloudimg, so a real map is visible immediately. Open it from the feed. It shows markers for three cities and a route drawn between them, on OpenStreetMap tiles, with the Points of interest data layer listed in the panel.

Step 7 - Create a map with markers
Anonymous map creation is enabled by default, so you do not need an account to make a map. Choose Create a map on the home page. The editor opens with a whole world view, the drawing tools on the right (marker, line, polygon), and the map layers, import, settings and share controls.

Click the marker tool, click a point on the map to drop a marker, give it a name and a description, then choose Save. uMap creates the map, stores your marker as a GeoJSON data layer, and gives you a shareable URL for the map plus a secret edit link so you can come back and change it later. Set the visibility from Draft (private) to public in the map settings if you want it listed and viewable by others.
Step 8 - Sign in to the Django administration area
The Django admin is reserved for the single instance administrator and is used to moderate maps, manage licences and tile layers, and remove content. Browse to http://<vm-public-ip>/admin/ and sign in as umapadmin with the password from Step 5.

The uMap section lists Maps, Data layers, Licences, Pictograms, Teams and Tile layers. Use it to review or delete maps created on your instance, add custom tile layers, or manage the licences offered to map authors. Self service account sign up and third party sign in are switched off by default, so this administrator is the only account on the instance until you configure otherwise.
Step 9 - Verify the whole workflow yourself
The appliance ships the same end to end check that the image is built and tested with. It confirms PostGIS is present, proves a Redis cache round trip, creates a map with a marker through uMap's own models, re opens the map by its public URL and reads the data layer GeoJSON served through nginx to confirm the marker is there, proves an unauthenticated write to that data layer is rejected, and then removes the map it created so nothing is left behind:
sudo bash /usr/local/sbin/umap-verify-workflow.sh
It prints a single line, WORKFLOW_OK map=<id> marker_served=1 postgis=<version> redis=ok. Any failure prints BROKEN-<reason> instead and exits non zero.

You can check the installed versions at any time:
/srv/umap/venv/bin/python -c "import django, importlib.metadata as m; print('Django', django.get_version(), '| uMap', m.version('umap-project'))"

Step 10 - List the maps on your instance
You can list maps from the command line with the instance environment already loaded:
sudo /usr/local/sbin/umap-manage shell -c "from umap.models import Map; print([(m.id, m.slug, m.name) for m in Map.objects.all()])"
The shipped example map, welcome-to-umap, appears in the list.
Step 11 - Optional: turn on account sign up or OAuth sign in
By default the only way to sign in is the Django admin as the instance administrator; anonymous visitors can still create maps. If you want visitors to register their own uMap accounts, or to sign in with an external provider such as OpenStreetMap, edit the settings.
To allow local account login, set ENABLE_ACCOUNT_LOGIN in the environment file:
sudo nano /srv/umap/umap.env
Add the line, then restart uMap:
ENABLE_ACCOUNT_LOGIN=1
sudo systemctl restart umap
To use OpenStreetMap or another OAuth provider, add the provider's backend and keys to /etc/umap/umap.conf following the uMap configuration documentation, then restart uMap. Leave both switched off if you only need anonymous map creation and a single administrator.
Step 12 - Add a domain and TLS
For production, put your own certificate in front of uMap. Point a DNS record at the VM's public IP, then install a certificate with your preferred tool and configure nginx to serve it. The vhost lives at /etc/nginx/sites-available/cloudimg-umap.
Once you are serving HTTPS, tell uMap its public address, restrict the hostnames it answers on and switch the session and CSRF cookies to secure only, by editing the environment file:
UMAP_SITE_URL=https://umap.example.com
UMAP_ALLOWED_HOSTS=umap.example.com
UMAP_CSRF_TRUSTED_ORIGINS=https://umap.example.com
UMAP_SECURE_COOKIES=true
Then restart the service:
sudo systemctl restart umap
Leave UMAP_SECURE_COOKIES at false while you are still on plain HTTP. A secure only cookie is never returned by the browser over HTTP, so turning it on too early stops sign in and map creation from working.
Server Components
| Component | Version | Purpose |
|---|---|---|
| uMap | 3.7.3 | The map creator: maps, data layers, imports, sharing and embedding |
| Django | 5.2 LTS | The GeoDjango web framework hosting uMap |
| Python | 3.12 | Runtime, in a dedicated virtual environment |
| gunicorn | 26.x | WSGI application server, bound to 127.0.0.1:8019 |
| nginx | 1.24 | Reverse proxy on port 80, serves static files and map data layers |
| PostgreSQL | 16 | Map metadata database, bound to 127.0.0.1:5432 |
| PostGIS | 3.4 | Spatial extension providing the geometry types uMap needs |
| Redis | 7.0 | Django cache, bound to 127.0.0.1:6379, password protected |
Filesystem Layout
| Path | Size | Purpose |
|---|---|---|
/ |
29 GB | Root filesystem |
/boot |
881 MB | Operating system kernel files |
/boot/efi |
105 MB | UEFI boot partition (Gen2 Hyper V) |
/mnt |
8 GB | Azure temporary resource disk (not persistent) |
Key directories
| Path | Purpose |
|---|---|
/srv/umap |
Application root, owned by the umap system user |
/srv/umap/venv |
Python virtual environment holding uMap and its dependencies |
/srv/umap/var/static |
Collected static files, served by nginx |
/srv/umap/var/data |
Map data layers stored as GeoJSON files |
/etc/umap/umap.conf |
The cloudimg appliance settings |
/srv/umap/umap.env |
Per VM environment file: secret key, database and Redis passwords, host settings |
/var/lib/postgresql/16/main |
PostgreSQL data directory |
/etc/nginx/sites-available/cloudimg-umap |
The nginx vhost |
/root/umap-credentials.txt |
Per VM administrator credentials, 0600 root:root |
Managing the services
systemctl status umap --no-pager --lines=0
sudo systemctl restart umap
sudo systemctl restart nginx
sudo systemctl restart postgresql
sudo systemctl restart redis-server
Follow the application log:
sudo journalctl -u umap -f
Run any uMap or Django management command with the instance environment already loaded:
sudo /usr/local/sbin/umap-manage showmigrations umap | tail -5
Scripts and Log Files
| Path | Purpose |
|---|---|
/usr/local/sbin/umap-firstboot.sh |
First boot: generates the per VM secrets, creates the administrator account, enables the public site |
/usr/local/sbin/umap-manage |
Runs a uMap or Django management command with the instance environment |
/usr/local/sbin/umap-verify-workflow.sh |
End to end workflow check (Step 9) |
/usr/local/sbin/umap-secret-leak-check.sh |
Proves no per VM secret reached the journal, auth log, backups or history |
/var/lib/cloudimg/umap-firstboot.done |
First boot sentinel; its presence stops first boot re-running |
journalctl -u umap |
Application and gunicorn access log |
journalctl -u umap-firstboot |
First boot log |
/var/log/nginx/access.log, /var/log/nginx/error.log |
nginx logs |
On Startup
umap-firstboot.service runs once, on the first boot of each VM, gated on the sentinel above. It resolves the VM's public address, rotates the PostgreSQL password and the Redis password, generates a unique Django secret key, applies any pending database migrations, creates the umapadmin administrator account with a random password, starts gunicorn on loopback, and only then enables the public nginx vhost. It finishes by proving the whole map workflow works and that no secret leaked into any log, and writes the sentinel.
Until that unit has completed, nothing is listening on port 80. This is deliberate: it means a newly launched VM is never reachable with a credential that anybody else could know. On later boots the unit is skipped and the normal service units start uMap.
Backups
Back up two things: the PostgreSQL database (map metadata, permissions, users) and the data directory (the GeoJSON files that hold your actual map features). Do not copy the environment file into a backup location, because it holds the Django secret key and the database password.
sudo install -d -o root -g root -m 0700 /var/backups/umap
sudo -i bash -c 'set -a; . /srv/umap/umap.env; set +a; umask 077; PGPASSWORD="$DB_PASSWORD" pg_dump -h 127.0.0.1 -U "$DB_USER" -d "$DB_NAME" | gzip > /var/backups/umap/umap-$(date -u +%Y%m%d).sql.gz'
sudo tar czf /var/backups/umap/umap-data-$(date -u +%Y%m%d).tar.gz -C /srv/umap/var data
sudo ls -l /var/backups/umap/
Copy both files off the VM to your own backup storage.
Troubleshooting
uMap does not answer on port 80. First boot may still be running. Check its state and its outcome:
systemctl show -p ActiveState -p Result -p ExecMainStatus umap-firstboot.service
ActiveState=active with Result=success means first boot finished. Result=exit-code or a non zero ExecMainStatus means it failed; read the log below.
sudo journalctl -u umap-firstboot --no-pager | tail -40
The public vhost is enabled by first boot, so if that unit failed, port 80 stays closed by design.
Sign in or map creation does not work in a browser but the site loads. Check that UMAP_SECURE_COOKIES is not set to true while you are still serving plain HTTP. A secure only cookie is never sent back over HTTP, so the session and the CSRF token are silently dropped.
A page reports a disallowed host. Add your hostname to UMAP_ALLOWED_HOSTS and your scheme plus hostname to UMAP_CSRF_TRUSTED_ORIGINS in /srv/umap/umap.env, then restart umap.
The administrator password is not accepted. Re-read it from /root/umap-credentials.txt. If the account has been lost entirely, create a replacement:
sudo DJANGO_SUPERUSER_PASSWORD='<new-password>' /usr/local/sbin/umap-manage createsuperuser --noinput --username newadmin --email you@example.com
Checking application errors.
sudo journalctl -u umap --no-pager | tail -60
Security Recommendations
- Change the
umapadminpassword after your first sign in. - Anonymous map creation is on by default. If your instance should be private, either restrict
80/tcpand443/tcpto trusted networks in the NSG, or turn anonymous creation off by settingUMAP_ALLOW_ANONYMOUS=0in/etc/umap/umap.confand restarting uMap. - Restrict
22/tcpto your management network in the NSG. - Terminate TLS with your own certificate and domain, then set
UMAP_SITE_URL,UMAP_ALLOWED_HOSTS,UMAP_CSRF_TRUSTED_ORIGINSandUMAP_SECURE_COOKIES=trueas described in Step 12. - Keep
/root/umap-credentials.txtand/srv/umap/umap.envas they are shipped. The environment file holds the Django secret key; anyone who reads it can forge sessions. - Unattended security upgrades are enabled on the image. Leave them on, and reboot when a kernel update requires it.
- Take regular database and data directory backups as described above, and store them off the VM.
Support
cloudimg provides 24/7 support for this image. Contact support@cloudimg.co.uk with your Azure subscription ID and the VM name.
uMap is open source software distributed under the GNU Affero General Public License version 3. uMap is a project of the OpenStreetMap community and OpenStreetMap is a trademark of the OpenStreetMap Foundation; cloudimg is not affiliated with, endorsed by or sponsored by either. The example map shown in this guide is included for illustration.