Skip to main content

Service manifest reference

Every backend service in OpenMapX — first-party or community — is described by a single file: service.json. The manifest is the only thing the compose renderer reads to turn a service into a docker-compose entry. It declares the container image, its ports and mounts, the capabilities the service provides, the data it consumes and produces, how (if at all) it is reachable from outside, and the settings an operator can tune. There is no separate compose fragment to keep in sync; the manifest is the whole contract.

This page is the field-by-field reference. For the model behind it — services versus integrations, and how capabilities bind the two — see How it works. For operating services once they're written, see Managing services.

Where it lives

A service named valhalla lives at services/valhalla/service.json. The directory slug must equal the manifest's id, and any files the manifest bind-mounts (config templates, icons) are resolved relative to that directory. Community services install under services/.community/<repo-hash>/<slug>/ and follow the same layout.

The manifest is validated by a Zod schema in packages/core/src/services/manifest-schema.ts. A file that fails validation is dropped from the registry with an error; it never reaches the renderer. You can validate and inspect a manifest from the CLI:

pnpm openmapx services list # every manifest that validated
pnpm openmapx services get valhalla # the full parsed manifest

A complete example

Here is a realistic, annotated manifest — Valhalla, a routing engine that boots from an OSM extract and is reachable on loopback:

{
"id": "valhalla",
"name": "Valhalla",
"version": "1.0.0",
"description": "Multi-modal routing engine using OpenStreetMap data.",
"license": "MIT",
"quality": "built-in",
"homepage": "https://valhalla.github.io/valhalla/",
"documentation": "https://valhalla.github.io/valhalla/api/",

"container": {
"image": "ghcr.io/valhalla/valhalla-scripted",
"tag": "latest",
"expose": [8002],
"environment": {
"serve_tiles": "True",
"build_elevation": "True"
},
"memory": "16g",
"restart": "unless-stopped",
"healthcheck": {
"type": "http",
"path": "/status",
"port": 8002,
"interval": "30s",
"timeout": "10s",
"retries": 5
}
},

"provides": ["routing-engine"],
"consumes": [{ "type": "osm-pbf", "mountAt": "/custom_files", "required": true }],

"bindMounts": [
{ "source": "config/valhalla.json", "target": "/custom_files/valhalla.json", "readOnly": false }
],

"configSchema": {
"type": "object",
"properties": {
"build_elevation": { "type": "boolean", "default": true, "title": "Build elevation data" },
"build_admins": { "type": "boolean", "default": true, "title": "Build admin boundaries" }
}
},

"exposure": {
"hostPorts": [{ "container": 8002, "host": 8002, "bindAddress": "127.0.0.1" }]
},

"ui": { "category": "routing", "icon": "icons/valhalla.svg" }
}

Only id, name, version, quality, and container (with image + tag) are required. Everything else is optional and defaults to "not present."

Identity

The top-level descriptive fields.

FieldTypeRequiredNotes
idstringyesMust match ^[a-z0-9][a-z0-9-]*$. Becomes the compose service name and must equal the directory slug.
namestringyesHuman-readable label shown in the admin UI.
versionstringyesFree-form, semver by convention.
descriptionstringnoShort tagline surfaced in the catalog and admin detail page.
authorstringnoFree-form attribution.
licensestringnoSPDX identifier by convention (MIT, Apache-2.0).
homepageURLnoProject homepage; validated as a URL.
documentationURLnoAPI/docs URL; validated as a URL.
qualityenumyesbuilt-in, community-verified, or community. Drives security enforcement (see Quality tiers).
platformstringnoOptional platform-version constraint a community manifest can declare against the OpenMapX core.

container

The image and its runtime knobs. Most fields map one-to-one onto docker-compose service keys; the renderer copies them through.

FieldTypeNotes
imagestringRequired. Lowercase, no tag suffix — matches ^[a-z0-9]([a-z0-9._\-/])*$. Put the version in tag.
tagstringRequired. The image tag (latest, 2.10.2, v3.6); matches ^[a-zA-Z0-9._-]+$.
containerNamestringPin the compose container to a fixed name (container_name) instead of the derived <project>-<service>-<n>. Required only when another service addresses this one by bare name over the Docker CLI (the data-manager does this for motis, motis-staging, motis-feed-proxy). It blocks scaling, so use it sparingly.
exposenumber[]Container ports published on the internal openmapx network. Required if exposure.proxy.enabled is true.
networkAliasesstring[]Extra DNS aliases on the openmapx network. Each must be a valid DNS label. Used so a service is reachable under a stable hostname independent of its id.
commandstring | string[]Container command.
entrypointstring | string[]Container entrypoint.
environmentobject (string→string)Environment variables. Supports ${VAR}, ${VAR:-default}, and ${VAR:?error message} interpolation, resolved at render time.
envFilestring[]env_file pass-through. Relative paths under infra/docker/ only. Intended for app-api/app-web; consumer containers should enumerate environment for auditability.
workingDirstringWorking directory inside the container.
userstringRun-as user, e.g. "${UID:-1000}:${GID:-1000}".
shmSizestringShared-memory size, e.g. "1g".
memorystringMemory limit, e.g. "16g", "512m".
restartenumno, on-failure, always, or unless-stopped.
capAddstring[]Linux capabilities to add. Restricted to a fixed allowlist (NET_ADMIN, SYS_ADMIN, IPC_LOCK, …); unknown strings are rejected.
capDropstring[]Capabilities to drop. Accepts allowlisted names plus the special "ALL".
devicesstring[]Host devices to pass through. Each entry must match /dev/<name>.
privilegedbooleanRun privileged. Forbidden for community services.
networkModeenumbridge or host. host is forbidden for community services.
dependsOnarrayStart-order dependencies — { "service": "<id>", "condition": "service_started" | "service_healthy" }.
loggingobjectCompose logging — { "driver": "...", "options": { ... } }.
healthcheckobjectSee below.

container.healthcheck

The renderer translates this into a docker-compose healthcheck block. http becomes a curl probe against http://localhost:<port><path>, tcp becomes a port probe, and exec runs command directly.

"healthcheck": {
"type": "http",
"path": "/status",
"port": 8002,
"interval": "30s",
"timeout": "10s",
"retries": 5,
"startPeriod": "600s"
}
FieldTypeNotes
typeenumRequired. http, tcp, or exec.
pathstringHTTP path to probe (http only).
portnumberPort to probe (http/tcp).
commandstring | string[]Command to run (exec only).
intervalstringProbe interval, e.g. "30s".
timeoutstringPer-probe timeout.
retriesnumberFailures before "unhealthy."
startPeriodstringGrace period during startup before failures count — useful for engines that build an index on first boot.

Capabilities and data flow

Three arrays express how a service fits into the stack: the abstract roles it plays (provides), the prepared data it needs (consumes), and the data it publishes for others (produces). The renderer matches a consumer's consumes entry to another service's produces entry of the same type and emits a hardlink-plan entry so the files are shared without copying.

provides

Capability strings that integrations and other services match on. The routing integration requires a routing-engine; whether that's Valhalla or OSRM is a deployment choice, not a code change.

"provides": ["transit-engine", "routing-engine"]

Each entry is either a bare string (the common case) or an object { "capability": "routing-engine", "metadata": { ... } }. The metadata slot is reserved for future runtime layers; the platform does not read it today.

Well-known capabilities: routing-engine, transit-engine, transit-engine-staging, geocoder, tile-server, osm-query, database, cache, proxy, and the data-delivery roles osm-data, gtfs-data, tile-asset-data. A community service may introduce new capabilities, but it should namespace them as <vendor>/<name> (for example acme/routing-engine). Non-conforming strings produce a non-blocking validation warning, not an error — the manifest still loads, and the flag shows up in the admin UI and in pnpm openmapx services capabilities.

consumes

Prepared data the service expects to find mounted at a path. Each entry pairs with a producer's produces entry of the same type.

"consumes": [
{ "type": "tile-mbtiles", "mountAt": "/data/mbtiles", "readOnly": true, "required": true },
{ "type": "osm-pbf", "mountAt": "/nominatim/data", "targetFilename": "data.osm.pbf" }
]
FieldTypeNotes
typestringRequired. The data type to bind.
mountAtstringRequired. Absolute container path; no ...
instancestringOptional producer-instance id for multi-instance types (e.g. one OSM extract per region). Omit to bind the default/only instance.
targetFilenamestringOptional fixed filename to expose inside the mount, for services with a hard-coded input name (Nominatim's data.osm.pbf). The producer dir must then hold exactly one file.
readOnlybooleanDefaults to false.
requiredbooleanWhen true, a missing producer makes the render fail. When absent/false, a missing producer just omits the mount.

produces

Data this service writes for consumers. sourceDir is relative to the producer's data root. In practice the built-in producer for these types is the data-manager.

"produces": [
{ "type": "osm-pbf", "sourceDir": "data/osm" },
{ "type": "gtfs", "sourceDir": "data/gtfs" },
{ "type": "tile-fonts", "sourceDir": "data/tile-fonts" }
]
FieldTypeNotes
typestringRequired. The data type produced.
sourceDirstringRequired. Directory (relative to the data root) the service writes to.
instancestringOptional instance id when the same type is produced more than once. Must match ^[a-z0-9][a-z0-9-]*$.

Well-known data types include osm-pbf, osm-pbf-bz2, osrm-graph, otp-graph, motis-data, motis-staging-data, motis-feed-proxy-config, gtfs, tile-mbtiles, tile-fonts, tile-styles, pelias-placeholder-data, and pelias-whosonfirst-data. New types validate fine as long as a producer and a consumer agree on the string. A single service may ship multiple produces entries of the same type only when each carries a distinct instance; duplicate (type, instance) pairs are rejected.

Storage

volumes

Named Docker volumes for state that must survive container restarts (a database on disk, an Overpass index).

"volumes": [
{ "name": "openmapx-pgdata", "mountAt": "/var/lib/postgresql" },
{ "name": "openmapx-overpass-db", "mountAt": "/db", "backup": true }
]
FieldTypeNotes
namestringRequired. Must match ^openmapx-[a-z0-9-]+$.
mountAtstringRequired. Absolute container path; no ...
readOnlybooleanDefaults to false.
backupbooleanWhen true, included in pnpm openmapx backup create/restore/list.

bindMounts

Mount files from the service's own directory — or, for built-in services only, a small whitelist of host references — into the container.

"bindMounts": [
{ "source": "config/config.json", "target": "/data/config.json" },
{ "source": "@infra:data/photon", "target": "/photon/data", "readOnly": false },
{ "source": "@service:pelias:config/pelias.json", "target": "/code/pelias.json" },
{ "source": "@docker-socket", "target": "/var/run/docker.sock" }
]
FieldTypeNotes
sourcestringRequired. See the source kinds below.
targetstringRequired. Absolute container path (or a Compose-variable reference); no ...
readOnlybooleanDefaults to true.
optionalbooleanWhen true, the mount is silently dropped at render time if the host source is absent. Used for operator-supplied secrets declared once but materialized only when the file is dropped in place. Not allowed with Compose-variable sources.

The source may be one of:

Source kindAvailable toBehavior
Relative path (config/foo.json)all qualitiesResolved against the service's own directory. No .., no absolute paths.
@docker-socketbuilt-in onlyMounts the host's /var/run/docker.sock.
@service:<slug>:<rel-path>built-in onlyMounts a path from another built-in service's directory (shared config). The renderer fails fast if the named service is unknown or the path escapes its directory.
@infra:<rel-path>built-in onlyResolves against infra/docker/ — the directory the compose file renders into. Used to bind the shared infra/docker/data/ tree.
${VAR} / ${VAR:-default}all qualitiesCompose-variable reference passed through verbatim; Docker substitutes it at up time.

exposure

By default a service is reachable only on the private openmapx network, by service id (http://valhalla:8002). Nothing is published to the host or the internet unless the manifest opts in, in one of two ways.

"exposure": {
"hostPorts": [
{ "container": 8080, "host": 8080, "bindAddress": "127.0.0.1" }
],
"proxy": {
"enabled": true,
"pathPrefix": "/tiles",
"stripPrefix": true,
"middleware": ["tiles-cache@file"]
}
}

exposure.hostPorts

Publishes a container port on the host.

FieldTypeNotes
containernumberRequired. Port inside the container.
hostnumberRequired. Port on the host.
protocolenumtcp (default) or udp.
bindAddressstringRecommended 127.0.0.1 (loopback). The admin install preview flags a non-loopback bind as publicly accessible before you confirm.

exposure.proxy

Attaches Traefik routing so a path prefix under your domain reaches the container — the common case for HTTP services. Enabling the proxy requires container.expose to declare at least one port (otherwise validation fails).

FieldTypeNotes
enabledbooleanRequired. When true, requires container.expose.
pathPrefixstringHTTP prefix Traefik routes here. Must match ^/[a-zA-Z0-9._\-/]*$.
stripPrefixbooleanIf true, the prefix is stripped before forwarding.
middlewarestring[]Traefik middleware names (e.g. tiles-cache@file).
prioritynumberHigher wins when rules overlap. Use a low value for catch-all routes.
authRequiredbooleanReserved for a future forward-auth integration.
additionalRoutesarrayExtra routers pointing at the same backend. Each entry sets exactly one of path (exact match) or pathPrefix, plus optional middleware.

configSchema

A JSON Schema object describing operator-facing settings. The admin panel renders a form from it, persists values to the database, and the resolver merges them into the rendered container.environment so they reach the running service as environment variables.

"configSchema": {
"type": "object",
"properties": {
"build_elevation": { "type": "boolean", "default": true, "title": "Build elevation data" },
"build_admins": { "type": "boolean", "default": true, "title": "Build admin boundaries" }
}
}

The form supports boolean (switch), enum (select), string/number/ integer (text input), and format: "url". A default populates the form when no value is persisted.

Each declared key resolves through a three-layer cascade at render time, highest priority first:

PrioritySourceWhere it lives
3Environment variableSERVICE_<ID>_<KEY> in infra/docker/.env
2Databasethe admin panel's per-service config form
1Schema defaultconfigSchema.properties.<key>.default

The env-var name is SERVICE_ + the uppercased service id (hyphens become underscores) + _ + the uppercased config key — for valhalla and key memory_limit, that's SERVICE_VALHALLA_MEMORY_LIMIT. Environment variables always win, which keeps Docker deployments predictable. Because the cascade runs at render time, a changed value takes effect only after a re-render and a recreate; see Managing services for the full workflow.

envVars

Documents environment variables an operator may need to provide. This is documentation surfaced in the admin UI — it is not enforced.

"envVars": [
{ "name": "POSTGRES_PASSWORD", "required": true, "description": "Database password" },
{ "name": "PHOTON_REGION", "required": false, "default": "planet" }
]

buildCommand

An optional operator-facing build hook for engines that need a prepared graph or tile set before they can boot. Built-in services declare the openmapx services build <id> command here, and the CLI runs the matching Dockerized build handler. Today this is used by osrm, otp, motis, pelias, and tileserver.

"buildCommand": "openmapx services build tileserver"

ui

Hints for the admin catalog.

"ui": { "category": "routing", "icon": "icons/valhalla.svg" }
FieldTypeNotes
categorystringGroups the service in the catalog. Used today: infrastructure, application, routing, transit, geocoding, tiles, osm-query.
iconstringPath to an icon under the service's directory.

Quality tiers

The quality field sets the trust level and the security constraints the validator enforces.

TierConstraintsWhere it lives
built-inTrusted. May use @docker-socket, @service:, @infra: sources, networkMode: "host", and privileged: true.This repo's services/
community-verifiedSame security model as community, but listed in a registry the project endorses. No enforcement difference today.A future curated registry
communitySandboxed: relative-path bind mounts only — no @-special sources, no host networking, not privileged.services/.community/<repo-hash>/<slug>/

A community manifest that requests @docker-socket, host networking, or privileged mode is rejected with a precise error before it reaches the renderer.

Validation at a glance

The schema enforces, among other rules:

  • id matches ^[a-z0-9][a-z0-9-]*$ and equals the directory slug.
  • image matches ^[a-z0-9]([a-z0-9._\-/])*$ (no tag suffix); tag matches ^[a-zA-Z0-9._-]+$.
  • Volume names match ^openmapx-[a-z0-9-]+$.
  • Every mountAt and target is absolute and free of ...
  • A bindMounts.source is a relative path, a known special source, or a Compose-variable reference — nothing else.
  • capAdd entries come from the fixed capability allowlist; devices match /dev/<name>.
  • exposure.proxy.enabled: true requires at least one container.expose port.
  • Duplicate (type, instance) pairs in produces are rejected.
  • Community services may not use host networking, privileged mode, or @-special bind sources.

Capability and data-type strings that are neither well-known nor namespaced produce warnings rather than errors, so the manifest still loads.

Where to go next

  • Managing services — enable, render, run, configure, and expose the services you've written.
  • How it works — the service/integration model and the capability binding that ties them together.