Targets and deployment destinations
BaseHarbor separates where an application is operated from what the application needs and which environment is selected.
A concrete deployment identity is:
target + application + environment
Examples:
docker-dev / demo / dev
podman-dev / demo / dev
laptop-k3s / demo / dev
prod-ocp / mailflow / prod
The target model
A BaseHarbor Target is a named deployment destination.
Target
├── Runtime Provider
├── Runtime Access Reference
└── Target Scope
The concepts stay separate:
Target
where BaseHarbor operates
Runtime Provider
which runtime implementation realizes workloads
Access
how BaseHarbor reaches/authenticates to that runtime
Scope
which logical area inside that runtime is selected
Target names carry no runtime semantics. A Target is not synonymous with local, remote, Docker, Podman, Kubernetes or OpenShift.
K3s, k3d, kind, minikube, MicroK8s and similar distributions are Kubernetes Targets rather than separate BaseHarbor Runtime Providers.
Representative examples:
Target Provider Access Scope
------------------------------------------------------------
docker-dev docker local-docker default
podman-dev podman local-podman default
laptop-k3s kubernetes laptop-k3s dev
homelab-k3s kubernetes homelab dev
homelab-k3s-test kubernetes homelab test
customer-prod openshift customer-a project-x
Multiple Targets may share one Access definition and select different Scopes.
Repository versus installed BaseHarbor state
The repository remains authoritative for portable application intent:
baseharbor.yaml
envs/<environment>/baseharbor.yaml
The current working directory may help identify the current application and environment, but it never defines which deployments the installed BaseHarbor instance knows.
cwd may answer "which application do I mean?"
cwd must not answer "which applications does BaseHarbor know?"
Config versus Target state
User configuration is global:
$XDG_CONFIG_HOME/baseharbor/config.yaml
~/.config/baseharbor/config.yaml
It contains Target definitions, Access definitions, defaults and prompt preferences. Target-owned provider lifecycle references may also live here; for managed OpenBao the optional target.openbao.recovery-file entry stores only the absolute path to the operator-held recovery file, never the recovery material itself.
Mutable runtime/deployment state is Target-scoped:
$XDG_DATA_HOME/baseharbor/targets/<target>/
~/.local/share/baseharbor/targets/<target>/
Conceptually:
~/.local/share/baseharbor/
└── targets/
├── docker-dev/
│ ├── runtime/
│ ├── provider-registry.json
│ └── deployments/
├── podman-dev/
│ ├── runtime/
│ ├── provider-registry.json
│ └── deployments/
└── laptop-k3s/
├── runtime/
├── provider-registry.json
└── deployments/
This makes independent parallel Targets possible without sharing mutable BaseHarbor runtime state accidentally. Shared providers belong to the Target/provider lifecycle rather than to an individual application. In particular, application destroy or rename must not remove the Target-level OpenBao recovery reference or the shared OpenBao provider instance.
For a local development Target, the same Target state also owns the development UX boundary: one configurable development domain (default baha.localhost), the target-wide canonical HTTPS gateway state and the local development management credential. None of these belong in baseharbor.yaml.
Canonical hosts follow provider placement and avoid repeating ownership information that the Target already knows:
application workload <app>.<dev-domain>
application UI <app>-<service>.<dev-domain>
shared provider UI <service>.<dev-domain>
external provider-owned URL
Semantic shared aliases keep the local UX short, for example auth.<domain>, auth-admin.<domain>, secrets.<domain> and metrics.<domain>.
Shared PostgreSQL and Valkey are environment-bounded Target providers by default. Their provider lifecycle is shared, while each application still receives isolated logical resources, credentials and bindings.
Internal loopback ports remain implementation detail and are not the normal developer-facing address.
A local Kubernetes/K3s Target is not special: it is simply a Kubernetes Target whose Access definition reaches a local cluster.
Target selection
The effective Target resolves in this order:
explicit --target
↓
activated BASEHARBOR_TARGET
↓
configured default target
↓
first-run/local target selection
Application and environment resolution stay independent.
Shell-local activation and prompt visibility
Targets are designed for shell-local activation, similar to Python virtual environments. Different terminals can therefore safely target different destinations at the same time.
eval "$(baha target activate docker-dev)"
baha target -o json
baha config prompt
source <(baha shell-init bash)
Use baha shell-init zsh or baha shell-init fish for the equivalent shell integration.
The optional prompt segment keeps the selected Target visible before a BaseHarbor command is entered:
[homelab] ~/projects/demo $
[prod-ocp PROD] ~/projects/mailflow $
Prompt style is configurable: compact or detailed, text-only or color-assisted, before/after the path, or right-prompt where supported.
Environment-aware colors may provide an additional signal:
- dev: soft green;
- test/stage: soft amber;
- prod: muted red.
Production must never rely on color alone.
Target lifecycle and safety
Changing the active/default Target never moves or relabels existing deployments.
A Target with registered deployments or owned runtime resources cannot be deleted implicitly.
Mutation and destructive operations identify the full effective deployment:
target + application + environment
Listing deployments
baha app list reads the deployment registry for the effective Target rather than repository-local state.
baha app list --all-targets presents the installation-wide view.
CLI, JSON and MCP expose the same Target/deployment identity.
This v0.4.15 foundation is tracked in issue #408 and is intentionally designed so later Runtime Provider work in #396 and Kubernetes/OpenShift implementations can consume it without changing portable application intent.
Custom prompt label
Prompt presentation is independent from the stable Target name.
For example:
Target: laptop-docker-12
Prompt label: ld12
Color: soft green
[ld12] ~/projects/demo $
The label is only a visual alias. It never changes Target identity, deployment keys, ownership or Target resolution.
The wizard therefore supports presets as well as:
- a free-form prompt label;
- an explicitly selected color;
- before-path, after-path or right-prompt placement where supported;
- live preview of the exact result.
Production remains unambiguous without relying on color alone by default.
Tenant binding for Connector enrollment
A Connector Target in the trusted Core configuration requires tenant-id
containing the canonical tenant UUID. Its access definition uses
provider: baseharbor-node-connector with the stable node-id as reference.
Only a resolved Editor membership in that tenant may create an enrollment
authorization; Core supplies the runtime and ownership binding. Unbound and
foreign Targets deny.
The CA is pinned to an explicitly selected local Core Target. HTTPS enrollment alone does not establish an executable remote connection. The optional public BaseHarbor Node Connector implements this outbound-initiated Target Access transport for remote non-Kubernetes hosts. It is not a control plane or Runtime Provider and exposes no unauthenticated inbound management listener.
Connector-local tests and CI do not establish BaseHarbor support by themselves. Live admission, renewal, revocation and Docker/Podman support require pinned Core -> Connector -> runtime evidence; see the Target Access contract and the platform support matrix.
Isolated state override
BASEHARBOR_STATE_DIR is the authoritative isolated installation data root. Target/deployment/provider/TLS state resides below it; Target configuration is stored at config/config.yaml inside it. Organization selection and cached configuration use config/organization/ and cache/organization/; protected operator sessions use cache/sessions/, so a foreign installation cannot change the isolated Target or supply its login. This override takes precedence over XDG data and configuration paths, so an isolated test cannot discover or rewrite the normal user's Target selection. Operator recovery material uses the sibling <state-root>-recovery/<target>/ and survives installation-state deletion. Explicit operator recovery paths remain unchanged. Use an absolute dedicated directory and never point the override at another installation's state.