Upgrading
Update both the host checkout and the selected container images. API startup applies pending database migrations from its image. Plan a maintenance window and preserve recovery state before replacing services.
Refresh source datasets and indexes separately, following Preparing data.
A pre-update backup is optional, but recommended when the deployment contains state you cannot readily recover. The admin panel's update confirmation leaves this choice with the operator and enables it by default.
How an OpenMapX upgrade works
The host checkout supplies the CLI, service definitions, and configuration used to operate the stack. Application images are prebuilt and selected through a release manifest. A Git pull updates the checkout but leaves running containers unchanged; a release selection writes image pins but does not replace containers. Both operations are followed by an explicit service update.
Application release metadata selects seven digest-pinned images, including the running API, web, data-manager, ops-agent, and Transitous runner plus matching helper images. Some digests can be reused from an earlier release. Do not infer the selected release from one image's source revision or a mutable tag. See Product releases for publication and security scan behavior.
For a reproducible rollback reference, use the run-qualified manifest tag
ghcr.io/openmapx/release-manifest:<commit>-<run-id>-<attempt> (the identifier is
also stored in its JSON release field), or a manifest digest. Bare commit-SHA
aliases can move when a security refresh rebuilds the same source commit.
The release manifest acts as a release lockfile. Atomic release selection means an update chooses one complete set of image digests. Container replacements still happen in steps; it does not promise a simultaneous switch or automatic rollback of database changes. The docs website has its own publication channel and is not part of this application update.
1. Optional: create a backup
The backup command captures declared backup targets under
infra/docker/backups/: PostgreSQL as a compressed logical dump and other
included volumes as tar archives.
pnpm openmapx backup create --name pre-upgrade
Heavy region-derived index volumes (Nominatim, Pelias/Elasticsearch, Valhalla, MOTIS, Photon, TileServer, Overpass) are excluded from ordinary volume backups. Rebuilding them can take substantial time; keep an independent artifact strategy if recovery time requires it. Backups also include declared managed Dawarich state when selected; its location history and attachments require special care. Do not assume every bind-mounted file or generated secret is captured.
Preserve deployment configuration, generated secret files, and required recovery
state separately under a protected operator backup policy. A copy of .env alone
is not sufficient for recovery; keep encryption keys and the erasure journal
needed by restore. Do not include private subject-export artifacts. For a local
configuration safety copy:
cp -p infra/docker/.env infra/docker/.env.bak
chmod 600 infra/docker/.env.bak
A restore overwrites data and has version and erasure-journal checks. Inspect the snapshot and follow Backup and restore before restoring. When appropriate, stop clients through the restore workflow:
pnpm openmapx backup list # show available snapshots
pnpm openmapx backup restore pre-upgrade --stop-running
See Backup & restore for per-service
restore, the version-compatibility check, and --stop-running.
2. Pull the latest code
From the repo root, inspect git status and preserve local customizations before
fetching code. Use a fast-forward pull so unexpected branch divergence stops for
review, then reinstall the locked workspace. A new release
can change dependencies, the CLI, or the manifests, so always reinstall after a
pull:
(umask 022 && git pull --ff-only)
pnpm install --frozen-lockfile
The ops-agent refuses to start while a service manifest, an integration
manifest or their directories are writable by the group or by others. Some
distributions give user accounts a umask of 002, under which git pull
writes every changed file group-writable; pulling with umask 022, as above,
keeps them private. If the ops-agent stops with "Release service authority is
unavailable" or "Trusted configuration authority rejected" after a pull, clear
the group and other write bits and restart it:
git ls-files -z | xargs -0 chmod go-w
git ls-tree -rd --name-only -z HEAD | xargs -0 chmod go-w
The checkout has changed, but running containers still use their existing images. Continue with release selection and replacement.
Before upgrading, read the release notes for breaking changes, including a renamed environment variable, a new required secret, or a manifest change that needs attention. OpenMapX is under active development, so an occasional manual step between versions is expected.
3. Re-render the compose stack
Review changes to infra/docker/.env.example and your selected service manifests,
then render the new configuration:
pnpm openmapx compose render
This applies a new configuration generation (compose file and hardlink plan)
from your current manifests, infra/docker/.env and the settings saved in the
admin panel. The output is deterministic, and re-rendering when nothing changed
preserves the same stack content. The update command also renders, but this
separate step lets you inspect configuration before replacement.
4. Resolve the complete release and replace containers
Inspect the locally selected release without contacting the registry:
pnpm openmapx compose release --status
This reports the selected release and its seven image pins. It does not inspect running containers. Older overlays without selection metadata show an unknown release identity while retaining their available image pins.
Running pnpm openmapx compose release prints the previous and candidate release,
then marks each image changed, reused, or unknown by comparing digests,
and writes the selected lockfile as the Compose overlay. An existing overlay
continues to apply even if release resolution is disabled with
OPENMAPX_RELEASE=local; the status command reports that explicitly.
Select the release explicitly, then replace the five running application services. This operation can interrupt requests; it is not a database rollback transaction:
pnpm openmapx compose release
pnpm openmapx services update app-api app-web data-manager ops-agent transitous-runner
compose up, services start, and services update refuse to run release
services when no overlay exists and the release manifest cannot be resolved.
The CLI validates all seven image pins and matching privacy evidence. Use it
instead of writing a partial overlay by hand. main is the automatic development pointer; preview is the latest deliberately published named prerelease. Exact versions or metadata digests pin a specific selection. A missing preview or pin fails without choosing main.
All seven development aliases are verified before the main manifest pointer advances. Individual aliases can move separately; use the manifest for coherent updates. Named publication promotes the selected image digests without rebuilding them and advances preview only after immutable GitHub publication is verified. See Product releases.
The admin system updater checks the configured channel and stores its exact candidate ID and digest.
A check compares the manifest's digests with the running images and downloads
nothing. Apply uses that checked candidate rather than resolving the channel again:
it pulls every digest-pinned image while the services keep running, then writes
docker-compose.release.yml atomically. A configuration change requires a fresh check.
When the release changes the ops-agent image or its privacy-backup helper pin,
the agent is updated first. It refuses while other agent operations are running,
and refuses new ones until the handoff finishes.
It records the handoff in infra/docker/data/ops-agent/agent-handoff.json and
starts a helper container from its own image, which:
- writes the release overlay;
- recreates only
ops-agent; - waits up to three minutes for the new agent to be healthy and to run the pinned image and helper pin.
If the new agent does not start, the helper restores the previous overlay and agent. The update then stops with Host update required, before any other service changes. A new agent can fail to start when the release needs a newer host checkout, for example for a new service manifest. In that case, run the host commands shown in the panel.
The update then replaces API, web, data-manager and transitous-runner without restarting their dependencies. If that step fails, those four services return to the images they ran before the update; the verified new agent stays. System status includes all five running application services.
API and CLI service lifecycle commands automatically include an
existing release overlay, so restarting or re-rendering the stack retains the
digest-pinned release. Keep the overlay with the deployment;
clear it with pnpm openmapx compose release --clear only when intentionally
removing the local selection.
Forks and mirrored registries select their own channel with
OPENMAPX_RELEASE and OPENMAPX_RELEASE_MANIFEST_REPOSITORY (see Configuration).
services update preserves an existing release selection and stops before
container recreation if pulling that pinned release fails. Run compose release
first when deliberately selecting a newer release.
For local-image workflows, explicit OPENMAPX_RELEASE=local disables automatic release resolution. It does
not remove or bypass an existing release overlay. Clear the local selection
explicitly:
pnpm openmapx compose release --clear
This idempotent command removes only infra/docker/docker-compose.release.yml.
It makes no Docker calls and preserves running containers, .release-evidence
files that they may still mount, and .ops-agent-releases manifests and recorded
running state. It refuses to clear while the release store is locked or an update
transaction awaits completion or recovery. --clear cannot be combined with
--status.
Clearing does not change your environment. If automatic resolution remains
enabled, the next start/update selects a release again. To use local images,
separately set OPENMAPX_RELEASE=local in your deployment environment.
With no overlay and resolution disabled, service updates pull
the manifest tags and may continue after pull failures to allow local images.
This is not an offline mode; Compose can still pull application images.
compose pull resolves a coherent release when no overlay exists and product services are included; it preserves existing deliberate pins. Exact helper pull failures never build a replacement image.
To reconcile the entire enabled stack while retaining the selected app release, use the CLI:
pnpm openmapx compose pull
pnpm openmapx compose up
compose up refreshes the generated base file without overwriting the existing
release selection in docker-compose.release.yml. Heavy backend engines that did
not change are left running untouched.
There is no "rebuild the app" step in a normal upgrade. The application images are pulled by immutable digest from GHCR through the aggregate release manifest. You only build images yourself if you're doing local development against the source, which is outside the self-hosting flow.
DATA_MANAGER_URL validationapp-api now refuses a DATA_MANAGER_URL that is plain http:// on any host
other than localhost, 127.0.0.1, ::1 or the Compose service name
data-manager, and rejects URLs with credentials, a path prefix or a query
string. If you reach the data-manager over HTTP on a LAN address or a custom
service name, list that hostname in DATA_MANAGER_PLAINTEXT_HOSTS (see
Configuration); a path prefix must be moved to a
dedicated hostname or port.
5. Database migrations apply automatically
OpenMapX does not have a separate "run migrations" command for a self-host
upgrade. The app-api container applies pending Drizzle migrations on every
boot: at startup it runs the migration files bundled in the image against the
database, and the migrator is idempotent; it skips migrations that are
already applied and only runs the new ones.
So once the new app-api image starts (step 4), any schema changes that shipped
with the release are applied automatically. You can confirm it ran cleanly in the
API logs:
pnpm openmapx services logs app-api --tail 100
A successful upgrade logs Database migrations applied. If a migration fails,
the error is logged there. If you created an optional
pre-upgrade backup, restore it before attempting a rollback.
Drizzle migrations are forward-only. There is no automatic down-migration. Once a
new app-api has migrated the database, rolling back to an older app image is not
supported unless you also restore the matching database backup. For upgrades
that cross a schema change, consider taking the optional backup in step 1 when
the deployment contains state you cannot readily recreate.
6. Verify the upgrade
Check that every container came back healthy and the API is serving:
pnpm openmapx services status # container + health state for all services
curl --fail --silent --show-error https://maps.example.com/api/status
Replace maps.example.com with the actual domain. The CLI loads .env only into
its own process, so ${DOMAIN} is not automatically available in your shell.
compose release --status reports local pins, while services status reports
container/health state; neither alone proves matching running digests. Inspect
the running image identity when verifying an exact release.
Then load the app in a browser and exercise the core paths; search, directions, and any self-hosted engines you run. If a service is stuck, tail its logs:
pnpm openmapx services logs <id> --follow
For per-engine checks after an upgrade, see the verification steps in the relevant guides; routing engines, geocoders, transit engines, and OSM data queries.
OpenStreetMap contributions and OAuth token encryption
The release that adds OpenStreetMap place contributions also turns on encryption at rest for stored OAuth provider tokens, because a linked OpenStreetMap account can now hold write permissions.
Nothing is required of you to upgrade, and both contribution flags stay off until you set them.
Keep BETTER_AUTH_SECRET stable and protected. It is now the key material
for stored provider tokens as well as sessions. Rotating it deliberately is
supported, but every linked OAuth account will need to be re-linked afterwards;
Better Auth does not re-encrypt existing tokens under a new secret.
Martin's own database login
Martin used to log in as the postgres superuser and publish every table it
could see, including the OpenConditions records and crowd reports kept in the
same database. It now serves only the traffic-flow tiles and logs in as
openmapx_tiles, which can read no table. If Martin is enabled, set its
password before rendering, then restart PostGIS so it applies it:
echo "MARTIN_DB_PASSWORD=$(openssl rand -hex 32)" >> infra/docker/.env
pnpm openmapx compose render
pnpm openmapx services update postgis --no-deps
pnpm openmapx services update martin
Without the password, rendering fails with MARTIN_DB_PASSWORD must be set.
Reclaiming disk after an upgrade
Keep prior image digests and the matching database/configuration backup until you have verified the release and completed your rollback window. The normal prune command removes dangling images only:
docker image prune
It preserves images used by containers, but can remove untagged older images you
might want for rollback. It does not remove all unused tagged releases or clean
engine data. Avoid docker system prune --volumes as an upgrade cleanup step.
Upgrading community extensions
Community extensions have their own versions and update workflow. Use
Admin → Extensions to preview and apply an update. The CLI also provides
ext list, ext preview, and ext update, including explicit confirmation of
reviewed host ports. Those commands need an authenticated admin API endpoint;
their default http://localhost:3001 is not host-published by the managed
app-api manifest. A public HTTPS URL alone does not grant loopback admin
access. Follow Community extensions
for the supported access and confirmation workflow instead of assuming host CLI
invocation authenticates through the public proxy.
An accepted request queues work; check admin activity for completion before retrying an interrupted request.
After an interrupted extension update
The operations agent installs each service repository as a transaction: it
keeps the previous checkout, and what app-api needs to undo the change, until
the change is settled. The extension's database record is the commit point. If
app-api or the agent stops mid-update, the next app-api start, and every
later extension install or removal, settles what was left: a change the
database recorded is kept, and any other is rolled back, restoring the previous
checkout, service selection and running containers. The agent keeps its note
about a rolled-back change until the selection is restored, so a failure part
way through is finished by the next attempt. Integration artifacts use
custom_integrations/.extension-install-journal-*.json the same way.
If the agent is unreachable when app-api starts, app-api logs
Interrupted extension changes could not be recovered yet and serves normally;
the next extension job retries the recovery and fails, without changing
anything, until it succeeds. Do not delete .repository-*, .backup-* or
.stage-* entries under services/.community/ by hand. The agent removes
leftover staging directories itself when it starts.
Air-quality API compatibility
The legacy OpenAQ station route
GET /api/integrations/overlay-air-quality/air-quality/stations remains available through
1 March 2027, but its aqi field is now number | null. OpenMapX no longer labels one instantaneous
PM2.5 concentration as a US AQI value. A station receives a numeric index only when a complete,
locally applicable index can be validated; otherwise clients must display the pm25 concentration and
the observation time.
The route sends Deprecation: true, Sunset: Mon, 01 Mar 2027 00:00:00 GMT, and a
rel="successor-version" link to /api/integrations/air-quality/stations. Operators should migrate map
and API consumers to that canonical route. During the compatibility window, clients must accept
aqi: null, retain the per-station attribution/license, and treat 429/5xx responses as unavailable data,
not as zero or "good" air quality.
The legacy Open-Meteo route
GET /api/integrations/weather-open-meteo-air-quality/aqi sends a 28 February 2027 sunset (Sun, 28 Feb 2027 00:00:00 GMT). Migrate point
consumers to GET /api/integrations/air-quality/current; the canonical response preserves model provenance,
the locally selected standard, nullable primary evidence/index fields, raw pollutant values, and explicit
partial or unavailable status. Do not translate a missing canonical primary into the legacy route's
normalised scalar. The compatibility response sends Deprecation: true, that route-specific Sunset, and a
rel="successor-version" link to the canonical current route.
Where to go next
- Preparing data: refreshing OSM extracts, GTFS feeds, and rebuilding engine indexes (a separate cadence from code upgrades).
- Managing services: restarting, rebuilding, and the staggered-startup order for heavy engines.
- Configuration: the
infra/docker/.envreference, in case an upgrade introduces a new required variable.