BaseHarbor v0.4.17
BaseHarbor v0.4.17 adds standards-first managed application identity and the minimum operator-authentication boundary required before the v0.5 contract freeze.
The release keeps the normal developer path small: local dev remains trusted-local, while test and prod require an authenticated operator. Applications consume standard OIDC/OAuth2 interfaces rather than a BaseHarbor authentication SDK.
Managed application identity
Applications can declare provider-neutral managed identity. BaseHarbor resolves the provider, creates or reconciles the application/environment identity scope, materializes a standard OIDC binding and verifies the realized state.
The managed Keycloak reference provider supports:
- isolated application/environment realms and clients;
- OIDC discovery and JWKS;
- exposure-derived redirect and logout URIs;
- scopes and claims;
- portable MFA, TOTP, WebAuthn, passkey and passwordless requirements;
- automatic TLS/trust;
- ownership-aware observe, reconcile, verify and destroy.
External OIDC remains supported without requiring provider-side provisioning. Provider-native names and administration APIs do not become portable application intent.
Shared provider infrastructure does not imply shared application identities.
Operator authentication by environment
Operator access is scoped to the effective Target and Environment: dev uses trusted-local mode; test and prod require authenticated OIDC operator access.
For protected environments the CLI uses standards-compliant OIDC Authorization Code + PKCE and stores only a short-lived owner-only local session.
Commands: baha login -e test, baha whoami -e test, and baha logout -e test.
The first interactive use of an unconfigured protected Target/Environment can bootstrap either the managed Keycloak operator boundary or an external OIDC provider. The resulting configuration is Target/Environment-scoped and is reused by applications on that boundary.
Application identity and BaseHarbor operator identity remain logically separate even when one physical IdP provides both.
Advanced RBAC, JIT elevation, approvals and break-glass governance remain outside v0.4.17.
First-class shared provider lifecycle
v0.4.17 makes resource-efficient shared placement a core provider rule instead of a product-specific exception.
The bundled PostgreSQL and Valkey providers now support both shared and application placement. Their default is environment-bounded shared placement:
- PostgreSQL runs once per Target/environment sharing boundary and creates application-isolated databases and least-privilege roles.
- Valkey uses one Target-owned provider lifecycle and creates isolated per-application cache services/credentials so normal Redis/Valkey clients remain compatible without exposing sibling keys.
- Service Binding 1.1 projections remain application-scoped even when provider infrastructure is shared.
- Application destroy removes only the application's database/role/cache resources and bindings.
- The shared provider survives while sibling applications still use it and is removed at the provider/Target ownership boundary.
- Dedicated
applicationplacement remains supported for cases that require a separate provider instance.
This means ten applications requesting SQL no longer imply ten PostgreSQL provider instances by default. Shared infrastructure saves memory, CPU, image/runtime overhead and startup time without weakening application ownership.
The official v0.4.17 demo uses shared placement for every managed provider and keeps only the application workload application-scoped.
For PostgreSQL, shared lifecycle has a strict administration boundary: one internal baseharbor_admin identity is used only by BaseHarbor for provider lifecycle, while every Application + Environment + SQL instance receives its own deterministic role, own database and protected credential reference. The administrator is never emitted to application bindings. Reconcile revokes public database/schema access, verifies the application credential against its own database and checks sibling databases are denied.
Application backup/restore remains database-specific even on the shared provider. Destroy verifies protected registration plus live database ownership before terminating connections and dropping only the selected application's database/role. The Target owns the shared provider independently of application consumers. Destroying the final application removes its database/role/cache resources and credentials but leaves the provider state/runtime intact; explicit provider/Target destruction owns provider teardown.
Provider interfaces and optional management UIs
The guided application flow treats provider interfaces by semantic purpose instead of product name.
Optional management surfaces currently include:
- PostgreSQL administration through pgAdmin;
- cache administration through Redis Commander;
- shared object-storage administration through SeaweedFS Admin;
- the existing shared OpenBao web UI;
- managed identity login/account and separate Keycloak administration surfaces.
Selected development UIs are materialized automatically with managed TLS/trust and canonical HTTPS URLs. The effective Target owns one development domain, defaulting to baha.localhost, and one local HTTPS gateway. Internal random loopback ports remain runtime implementation detail and are no longer the normal developer-facing address. Docker binds the canonical development gateway to 443; rootless Podman uses deterministic port 8443 so the same hostname model works without privileged-port host configuration.
Canonical naming is deliberately short:
<app>.baha.localhost application workload/API
pgadmin.baha.localhost shared PostgreSQL UI
cache.baha.localhost shared cache UI
storage.baha.localhost shared object-storage UI
auth.baha.localhost shared OIDC/login surface
auth-admin.baha.localhost shared identity administration
secrets.baha.localhost shared OpenBao UI
metrics.baha.localhost shared Prometheus UI
Application-scoped provider surfaces, when explicitly selected, use <app>-<service>.<dev-domain>. External providers retain provider-owned URLs.
A shared provider creates one administration surface for the provider boundary rather than one UI per application. Test/prod administrative surfaces remain restricted/non-public by default.
The same canonical routing model applies to the normal application endpoint. An unambiguous development workload receives https://<app>.<dev-domain> automatically and Swagger/OpenAPI is exposed beneath the same host at /swagger/. status, doctor and structured status output report canonical browser URLs rather than random loopback ports.
Managed OIDC is placement-aware. With the default shared Keycloak provider, the issuer uses https://auth.<dev-domain>; an explicitly application-scoped identity provider uses an application-qualified host. Development workloads receive that same canonical issuer and the development-gateway trust binding, while provider-internal administration/readiness endpoints remain private implementation detail.
Unified development access
Development Targets also own one BaseHarbor-managed management login. The default username is developer; BaseHarbor generates a strong password and exposes it only through the explicit baha dev credentials command.
When managed Identity is present, BaseHarbor reconciles the same developer identity through managed OIDC. Provider surfaces that require native authentication reuse the same development credential through provider-specific adapters. Service credentials such as database passwords, S3 keys and runtime identities remain separate least-privilege credentials.
The development domain can be inspected or changed with baha dev domain [DOMAIN]. The wizard asks for the development domain and management access once for the Target instead of asking for hostnames or separate administrator credentials per capability.
This shared local convenience credential is never used for test/prod. Protected environments continue to use individual operator OIDC sessions, policy and stronger authentication.
Identity readiness, audit and recovery
status, doctor, JSON and evidence re-check the realized identity boundary instead of treating successful provisioning as permanent truth. Managed Keycloak verification covers issuer/discovery, owned realm/client state, redirect/logout drift, binding state and required authentication policy. External OIDC verification covers discovery and the materialized application binding.
Authenticated lifecycle audit events carry secret-safe operator identity and assurance context where available. Tokens, passwords, provider-admin credentials and authentication secrets are excluded.
Recovery treats portable identity intent separately from provider-held identity-directory data. The portable OIDC/client policy is reconstructed from application metadata. Provider-held users, credentials, MFA and passkey state are not claimed as recoverable without a scoped provider export; managed identity therefore remains an explicit durable recovery boundary, while external OIDC remains external.
Environment-isolated provider registry
Provider registry bindings are deployment-scoped by Application plus Environment.
This fixes a pre-freeze ownership defect where two environments of the same application on one Target could otherwise replace each other's provider-registry binding even though their runtime resources were isolated.
Existing v1 registry entries without an environment remain readable and are migrated to the current deployment environment on normal reconciliation.
Agent and read-only policy behavior
Read-only policy/evidence inspection no longer requires an installed Docker or Podman orchestration binary when repository Compose source can be analyzed statically.
The fallback remains fail-closed for security findings and is used only when runtime orchestration is genuinely unavailable. Runtime/provider errors are not silently converted into success.
Development working-tree fast path
Local development treats the current repository working tree as valid application source. A developer can edit application code and run baha up without committing or pushing first.
When the application is already READY and the portable baseharbor.yaml contract plus protected deployment-control state are unchanged, baha up uses a workload-only fast path. The existing workload engine fingerprints build and rendered service configuration, rebuilds only affected build-backed services, recreates only configuration-changed services, waits for workload HTTP/TLS readiness, and then re-checks overall application readiness.
Changes to baseharbor.yaml, protected deployment-control state, an unavailable/unready runtime, or any condition that cannot safely prove workload-only convergence continue through the full application reconciliation path.
baha app update keeps a separate purpose: it safely advances a Git-backed application from its configured upstream and therefore continues to require a clean, non-diverged working tree. It never stashes, resets, overwrites or discards local development work.
Final v0.4.17 release hardening
The final release pass tightens six release-blocking boundaries without changing Manifest v1.
- Shared provider lifecycle: shared PostgreSQL/Valkey infrastructure is Target-owned. Application destroy never tears down the provider merely because the application was the final consumer.
- OpenBao recovery: a stale/existing recovery output file is rejected before fresh control-plane mutation. Existing recovery material is never overwritten;
--recovery-file PATHis the explicit remediation. - Readiness truth: RUNNING is no longer treated as READY. Compose health, HTTP/TLS probes and TCP listener probes are positive readiness signals; a running worker without such a signal is reported RUNNING/UNVERIFIED.
- Developer gateway port: Docker prefers 443 and rootless Podman prefers 8443, but collisions select a deterministic free fallback from 18443. The selected port is Target state and every canonical URL/status/doctor path reuses it.
- Native TLS first: OpenBao, PostgreSQL, Keycloak, SeaweedFS S3, Prometheus and OTLP use native TLS. The single Target developer gateway remains the canonical browser router. Dedicated adapters remain only where they add a real security/protocol boundary, such as cache UI TLS or the currently coordinated Loki/Tempo ingestion edge.
- Dependency/security audit: go-oidc 3.21.0 and OpenBao 2.7.0 are adopted. OpenBao uses a dedicated least-privilege database/user in the existing PostgreSQL control-plane provider with
verify-full, native TLS reload and hybrid-PQC-first TLS; no legacystorage.fileor intermediate Raft compatibility path remains. Go-1.26-requiring x/crypto/x/sys updates and unrelated CI/toolchain majors remain deliberately deferred. #533 tracks the yaml.v3 replacement investigation.
Certificate rotation follows each native provider's supported reload model: Keycloak polls its certificate files, Prometheus re-reads web TLS material on requests, SeaweedFS uses a reloading certificate provider, OTLP uses a bounded TLS reload interval, and OpenBao 2.7 uses native tls_auto_reload.
Runtime Provider boundary
v0.4.17 establishes the provider-neutral Runtime Provider boundary required before future Kubernetes and OpenShift work.
The contract is versioned as:
baseharbor.runtime/v1
A Runtime Provider descriptor declares provider identity and version, adopted standards, workload-source compatibility, runtime realization and supported runtime capabilities. Capability negotiation is fail-closed and product-neutral.
The package structure now enforces that boundary:
internal/runtime/contract
provider-neutral runtime contract and model
internal/runtime/resolver
first-party provider selection and registration
internal/providers/runtime/docker
Docker provider implementation
internal/providers/runtime/podman
Podman/Quadlet provider implementation
Portable Core does not import concrete Docker or Podman provider packages. Static architecture tests enforce the dependency direction and prevent provider-specific implementation files from returning to the neutral runtime package.
Compose remains a first-class workload-source standard but is no longer the identity of a Runtime Provider.
Docker realizes the current Compose workload source through Docker Compose.
Podman realizes the same portable runtime semantics through native Quadlet units and rootless systemd --user. There is no podman compose fallback.
The Runtime Provider conformance coverage exercises both reference providers through the same versioned semantic contract.
Rootless Podman parity and release hardening
The final v0.4.17 validation closes the remaining Docker/Podman parity gaps discovered by the native Quadlet acceptance path:
- managed Quadlet networks retain native
.networkresource references and useNetworkAlias=rather than embedding legacy alias options in the network value; - external workload/provider networks use the same native Quadlet alias model, restoring rootless Podman DNS for runtime-executor, metrics scraping and canonical development routing;
- BaseHarbor preserves shared Podman networks while active sibling consumers still use them;
- rootless candidate-image build, verification and Quadlet execution share the same Podman storage context;
- Podman service-state observation reads published ports back from the running container so HTTP/TLS workload readiness has the same positive evidence available on Docker;
- runtime-executor readiness diagnostics expose the failing downstream dependency, and Podman readiness budgets account for the slower native Quadlet/systemd startup path without weakening fail-closed checks.
The targeted Podman guided acceptance verifies this path end to end before the full pre-release gate.
Runtime-neutral logs
Docker and Podman now converge behind one runtime-neutral log-source boundary.
BaseHarbor normalizes workload, application-provider and platform-provider log sources to the same semantic identity:
application
environment
source class
provider
service
Docker currently attaches container stdout/stderr through its syslog logging driver. Podman uses journald through the native Quadlet/systemd realization. Those transport details stay behind the runtime adapter.
Podman verification now checks the currently running project/service instead of accepting log configuration unconditionally. Invalid ownership, provider identity, signal kind or runtime target fails closed before Loki verification.
No proprietary logging protocol is introduced. Existing runtime logging, RFC5424 syslog, systemd journal semantics, Alloy and Loki remain the implementation standards.
Standards-first runtime architecture
The Runtime Provider review documents the adoption/delegation boundary for:
- OCI Image Specification;
- OCI Distribution Specification;
- OCI Runtime Specification;
- Compose Specification;
- Kubernetes API;
- CRI;
- CNI;
- CSI;
- Kubernetes Gateway API;
- Service Binding Specification;
- Crossplane composition/provider patterns.
BaseHarbor reuses or delegates to these standards where they already define the required boundary. BaseHarbor-specific semantics are limited to portable intent, provider negotiation, ownership, policy, lifecycle and semantic verification where the reviewed standards do not define the required application-facing behavior.
See docs/architecture/runtime-standards-audit.md, docs/architecture/runtime-log-source-contract.md and ADR 0007.
Intentionally deferred
v0.4.17 does not implement:
- Kubernetes Runtime Provider execution;
- OpenShift Runtime Provider execution;
- a proprietary replacement for OCI, CRI, CNI, CSI, Gateway API or Compose;
- advanced operator RBAC, JIT elevation, approval or break-glass governance;
- complete repository workload-source normalization remains tracked in #519; v0.4.20 covers Compose, native Quadlet and raw Kubernetes YAML, while Helm and Kustomize are intentionally deferred to later source adapters;
- an out-of-tree runtime process/distribution protocol before a real external-provider requirement proves the necessary transport.
The normal developer path remains intentionally small:
baha app init
baha up
baha status
Provider descriptors, negotiation and implementation packages remain infrastructure details below that workflow.
Compatibility
- Manifest v1 remains the portable application contract.
- OIDC/OAuth2 and Service Binding remain the application-facing integration boundary.
- Keycloak, pgAdmin, Redis Commander and SeaweedFS remain reference/provider implementation details.
- Runtime Provider identity is explicitly
dockerorpodman; Compose remains workload-source compatibility rather than provider identity. - Docker Compose and Podman/Quadlet retain equivalent logical application and provider ownership semantics.
- New implicit local Target state defaults to Docker. Podman requires native Quadlet plus a usable user-systemd session and fails closed when that environment is unavailable.
- Existing provider-registry v1 state remains readable; environment scope is additive deployment metadata.
The reference demo workload itself is HTTPS-only. Its Compose service declares io.baseharbor.workload.protocol: https; the development gateway uses the projected workload CA and the Compose service name as SNI when routing to it. Plaintext fallback is intentionally not part of the reference path.
Release validation
Release-boundary validation is prepared in two layers:
- local/Hugging Face source validation:
gofmt -l .,go vet ./..., focused provider/runtime tests andgo test ./...; - the mandatory GitHub Pre-Release gate against the exact release candidate and immutable
baseharbor-demorevision.
The final GitHub Release evidence will record the exact BaseHarbor candidate SHA, the pinned demo SHA and the successful Docker/Podman pre-release run.