Manage a workspace of packages

A workspace is a hull-workspace.yaml file plus a tree of member packages. One command installs, upgrades, diffs, or uninstalls the whole set, with hull computing a topological order from dependsOn and running independent members in parallel. Each member is its own release — a workspace orchestrates many independently-installable packages from one repository. (To compose one release from reusable pieces, use layers: instead; see Layers.)

The file-format reference is hull-workspace.yaml; the command reference is hull workspace. This guide covers the workflow.

When to reach for a workspace

You want a workspace when all of these hold:

  • Multiple packages in one repository.
  • A dependency graph between them (postgres before api, api before gateway).
  • A wish to install/upgrade/uninstall the whole graph with one command.
  • Each member is its own independently-versioned release.

If any of those is false, a workspace is overkill:

  • One release built from reusable blocks → use layers:.
  • Many releases from many sources → use hull-releases.yaml.

Lay out the workspace

my-platform/
├── hull-workspace.yaml
├── postgres/
│   ├── hull.yaml
│   ├── values.yaml
│   └── templates/
├── redis/
│   └── ...
├── api/
│   └── ...
├── worker/
│   └── ...
└── gateway/
    └── ...

hull-workspace.yaml sits at the root; member paths resolve relative to it.

Write the workspace file

# hull-workspace.yaml
apiVersion: hull/v1

defaults:
  namespace: my-platform
  atomic: true
  wait: true

members:
  - name: postgres
    path: ./postgres
    profile: ha-3node

  - name: redis
    path: ./redis

  - name: api
    path: ./api
    dependsOn: [postgres, redis]

  - name: worker
    path: ./worker
    dependsOn: [postgres]

  - name: gateway
    path: ./gateway
    dependsOn: [api]
    namespace: my-platform-edge      # overrides the default

Each member has a name, a path, and optionally namespace, profile, dependsOn, atomic, and wait. The defaults block sets namespace, profile, atomic, and wait for any member that does not override them.

Preview the order

Every hull workspace command reads hull-workspace.yaml from the directory given by --dir (default the current directory) — there is no positional path argument.

hull workspace plan
1. postgres (path=./postgres, ns=my-platform, profile=ha-3node)
2. redis (path=./redis, ns=my-platform, profile=)
3. api (path=./api, ns=my-platform, profile=)
4. worker (path=./worker, ns=my-platform, profile=)
5. gateway (path=./gateway, ns=my-platform-edge, profile=)

Add --levels to group members by dependency depth — members in the same level are mutually independent and can run in parallel:

hull workspace plan --levels
level 0 (2 members, parallelisable):
  - postgres (path=./postgres, ns=my-platform)
  - redis (path=./redis, ns=my-platform)
level 1 (2 members, parallelisable):
  - api (path=./api, ns=my-platform)
  - worker (path=./worker, ns=my-platform)
level 2 (1 members, parallelisable):
  - gateway (path=./gateway, ns=my-platform-edge)

Level 0 holds members with no dependsOn; a member lands at level N when every member it depends on sits at a lower level. To plan a workspace in another directory, point --dir at it:

hull workspace plan --dir ./my-platform --levels

Install, upgrade, uninstall

hull workspace install     # install all members in dependency order
hull workspace upgrade     # upgrade existing, install missing
hull workspace uninstall   # reverse order: dependents down first
hull workspace status      # per-member revision and status
hull workspace diff        # per-member preview of pending changes

Run members in parallel

--parallel N sets the per-level concurrency (default 1, i.e. sequential). Within a level, up to N members run at once; the next level starts only when every member of the current one has finished.

hull workspace install --parallel 4 --progress
workspace: 5 members across 3 level(s), parallel=4, op=install

[level 0/2] 2 member(s) starting concurrently
  → postgres (ns=my-platform) start
  → redis (ns=my-platform) start
  ✓ redis done in 8.1s
  ✓ postgres done in 12.4s

[level 1/2] 2 member(s) starting concurrently
  → api (ns=my-platform) start
  → worker (ns=my-platform) start
  ✓ worker done in 4.7s
  ✓ api done in 5.2s

[level 2/2] 1 member(s) starting concurrently
  → gateway (ns=my-platform-edge) start
  ✓ gateway done in 3.1s

All 5 member(s) succeeded.

--progress prints these live lines. Without it, a fully successful run prints nothing; failures are always reported.

Gate on readiness between levels

By default a level advances as soon as its apply calls return — pods may still be starting. --health-gate makes hull wait until every pod owned by a level is Ready before starting the next level, so dependents start against a dependency that is actually serving.

hull workspace install --parallel 4 --health-gate

Use it when a level genuinely depends on the previous one being functionally up (api cannot start until postgres accepts connections). Skip it when each member tolerates its dependencies being slow to come up, for a faster rollout. --health-gate-timeout (default 5m0s) caps each level’s wait.

Control failure handling

--atomic-workspace makes the run all-or-nothing: if any member fails, every member that already succeeded in this run is uninstalled.

hull workspace install --atomic-workspace

--continue-on-error does the opposite — a member’s failure does not abort the run; the rest keep going and all failures are reported at the end.

hull workspace install --continue-on-error

The two are mutually exclusive. Pair per-member atomic: true (the default) with --atomic-workspace for symmetric behaviour: each member rolls back its own resources on failure, and the workspace rolls back the whole set.

Preview changes before upgrading

hull workspace diff
=== postgres (ns=my-platform) ===
  no changes

=== api (ns=my-platform) ===
~ update  Deployment/api  (namespace my-platform)
      ~ spec.replicas
          - 2   (state)
          + 3

Summary: 0 added, 1 changed, 0 removed.

diff renders each member and compares it against the state hull recorded at its last apply, grouped by member in dependency order. Nothing is applied.

Check status

hull workspace status
MEMBER                         NAMESPACE            REVISION   STATUS
postgres                       my-platform          7          deployed
redis                          my-platform          2          deployed
api                            my-platform          12         deployed
worker                         my-platform          12         deployed
gateway                        my-platform-edge     -          not deployed

A member with no release shows - and not deployed.

Override settings per member

Most per-release settings can be customised on the member entry:

members:
  - name: worker
    path: ./worker
    namespace: worker-prod      # overrides the default namespace
    profile: prod               # selects worker's prod profile
    atomic: false               # leave a failed install in place to inspect
    wait: false                 # don't wait for Ready (e.g. CronJob members)

Share values across members

Workspace commands do not expose -f/--set: each member renders against its own values.yaml plus its selected profile. To propagate a common value — a shared registry, a shared domain — keep it under the conventional global key in each member’s values.yaml:

# api/values.yaml
global:
  imageRegistry: registry.example.com
  domain: example.com

Templates reference it the same way regardless of where the member sits: ${values.global.imageRegistry}.

See also