Native app
*.nativeapp wraps an external web server - either a local OS process the platform spawns and supervises, or a remote HTTP(S) endpoint - as a first-class Dirigible artefact, reverse-proxied under /services/native-apps-proxy/v1/<basePath>/... with optional Dirigible-managed authentication and role-based access.
- File format. JSON descriptor.
- Synchronizer.
NativeAppSynchronizer(single-tenant - apps are platform-global). OrderSynchronizersOrder.NATIVE_APP = 440. - Engine.
engine-native-apps. Filter chain runs on top of Spring Cloud Gateway WebMvc. - URL.
/services/native-apps-proxy/v1/<basePath>/.... - Management REST.
/services/native-apps[/<id>][/start|/stop]- list / get / start / stop / delete. Restricted to DEVELOPER, ADMINISTRATOR, or OPERATOR.
Kinds
| Kind | Meaning |
|---|---|
remote | A third-party HTTP(S) URL Dirigible does not own. The artefact declares only config.url. |
local | An OS process Dirigible spawns and supervises. Carries config.lifecycle.start.commands[] and optional config.lifecycle.stop.commands[]. |
Start modes (local only)
| Mode | Meaning |
|---|---|
always | Spawned on ApplicationReadyEvent; kept alive by NativeAppMonitorJob (default interval 30 s, override with DIRIGIBLE_NATIVE_APP_MONITOR_INTERVAL_SECONDS). |
lazy | Spawned on the first proxy request that hits the app. |
File format - remote
{
"name": "http-bin-app",
"description": "Remote upstream; whitelist allows /get..delete with no scopes.",
"basePath": "http-bin-app",
"type": "remote",
"config": {
"url": "https://upstream.example.com",
"security": {
"authentication": null,
"exposedPaths": [
{ "path": "/get", "scopes": [] },
{ "path": "/post", "scopes": [] },
{ "path": "/delete", "scopes": [] }
]
}
}
}File format - remote with basic auth
{
"name": "http-bin-app-auth",
"basePath": "http-bin-app-auth",
"type": "remote",
"config": {
"url": "https://upstream.example.com",
"security": {
"authentication": {
"type": "basic",
"credentials": {
"user": "${SAMPLE_APP_USER}.{alice}",
"password": "${SAMPLE_APP_PASSWORD}.{wonderland}"
}
},
"exposedPaths": [
{ "path": "/get", "scopes": ["library-admin"] }
]
}
}
}Placeholders follow the ${KEY}.{DEFAULT} convention shared with Jobs and other artefacts; they are resolved at parse time and persisted.
File format - local process
{
"name": "library-app",
"basePath": "library",
"type": "local",
"config": {
"defaultPort": 4001,
"startMode": "always",
"lifecycle": {
"start": {
"commands": [
{ "os": "linux", "command": "npm start", "dir": "." },
{ "os": "mac", "command": "npm start", "dir": "." },
{ "os": "windows", "command": "npm.cmd start", "dir": "." }
]
},
"stop": {
"commands": [
{ "os": "linux", "command": "npm stop", "dir": "." }
]
}
},
"security": {
"exposedPaths": [
{ "path": "/api", "scopes": ["library-admin"] }
]
}
}
}Key rules:
- Port resolution is prefer-then-allocate. The declared
defaultPortis probed on the wildcard interface; on bind failure (or ifdefaultPortis null) the OS allocates an ephemeral port. The resolved port is exported to the spawned process asDIRIGIBLE_NATIVE_APP_PORT- the spawned process must read that env var rather than hardcode a port. command.dirresolves under the artefact's project root (/registry/public/<project>/). Null / blank /"."means the project root itself. Do not embed the project folder name incommand.dir- the platform already knows it.- Readiness probe. After spawn, the loopback port is polled every 200 ms up to
DIRIGIBLE_NATIVE_APP_READY_TIMEOUT_MS(default 30 s). Bump it for cold starts that need a first-timenpm install. - Stop subprocess env. The stop script receives both
DIRIGIBLE_NATIVE_APP_PORTandDIRIGIBLE_NATIVE_APP_PID. Always target$DIRIGIBLE_NATIVE_APP_PID, neverlsof | xargs killby port -lsof -ti tcp:<port>returns every PID with a TCP FD on the port (listeners and keep-alive clients), so a port-based kill from an author's stop script can reliably kill the Dirigible JVM itself.
Security: exposed paths and scopes
config.security.exposedPaths[] is a whitelist:
- A request whose path does not match any entry returns 404.
- A matching entry whose
scopeslist is non-empty requires the caller to hold one of those roles, otherwise 403. - Native-app scope semantics are intentionally strict - DEVELOPER / ADMINISTRATOR super-roles do not grant implicit access. Authors define their app's audience explicitly.
- If
scopesis empty orsecurityis null, any authenticated caller passes (authentication itself is still enforced by Spring Security on/services/**).
Proxy filter chain
/services/native-apps-proxy/v1/** runs through:
rewritePathstrips the absolute base.NativeAppLookupFilterresolvesbasePath→ app, rewrites the path to the upstream-relative form. Empty-basePathis a catch-all that matches when no named app catches first.ExposedPathFilterenforcessecurity.exposedPaths(404 on whitelist miss) andscopes(403 on miss).LazyStartFilterspawns LOCAL + LAZY apps on demand.NativeAppDispatchersets the downstream scheme / host / port: local →http://127.0.0.1:<resolvedPort>, remote →config.url.AuthInjectionFilterconsults the matchingAuthenticationInjector(currentlybasic).removeRequestHeader("Cookie")+ forward.
Lifecycle and teardown (local)
NativeAppProcessManager.stop runs three layered guarantees:
- Author's
lifecycle.stopcommand, best-effort, with the same env (DIRIGIBLE_NATIVE_APP_PORTandDIRIGIBLE_NATIVE_APP_PID). Process.destroy()on the held root, with a 5 s grace, thendestroyForcibly().- A descendants walk (captured before
destroy(), since the OS reparents orphans to init / launchd once the root dies). Each leftover descendant isdestroyForcibly()d.
Most apps do not need a custom stop script - Process.destroy() SIGTERMs the held PID and well-behaved apps handle SIGTERM gracefully (Node does by default).
Diagnostics
When the spawned process dies before its port opens, the thrown IllegalStateException carries: PID, exit code, elapsed runtime in ms, resolved port, working directory, and the last ~30 stderr lines. Live stderr is also written to org.eclipse.dirigible.nativeapps.<name>.stderr.
Every lifecycle transition logs PID + port for cross-reference with lsof -iTCP:<port> -sTCP:LISTEN -P -n and ps -p <pid>.
Configuration
| Env var | Default | Purpose |
|---|---|---|
DIRIGIBLE_NATIVE_APP_PORT | - | Exported to the spawned process; the resolved port. |
DIRIGIBLE_NATIVE_APP_PID | - | Exported to the stop subprocess; the held root PID. |
DIRIGIBLE_NATIVE_APP_READY_TIMEOUT_MS | 30000 | Readiness-probe timeout. Bump for cold starts. |
DIRIGIBLE_NATIVE_APP_MONITOR_INTERVAL_SECONDS | 30 | Interval at which NativeAppMonitorJob restarts dead ALWAYS apps. |