Skip to main content

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.

Backup recommended

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.

Read the release notes

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.

Admin updater and later service commands

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:

  1. writes the release overlay;
  2. recreates only ops-agent;
  3. 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.

Pull vs. build

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.

Stricter DATA_MANAGER_URL validation

app-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.

Migrations move forward only

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/.env reference, in case an upgrade introduces a new required variable.