`homeboy deploy`

Synopsis

sh
homeboy deploy [<project_id>|<component_id>] [<component_ids...>] [-p|--project <id>] [-c|--component <id>]... [--all] [--outdated|--behind-upstream] [--head|--ref <git-ref-or-sha>] [--release-set <path>] [--check] [--dry-run] [--apply] [--json '<spec>']
# If no component IDs are provided, you must use --all, --outdated, --behind-upstream, or --check.

# Multi-project deployment
homeboy deploy --projects <project1>,<project2> <component_ids...>

# Fleet deployment
homeboy deploy <component_id> --fleet <fleet_id>

# Shared component deployment (auto-detect projects)
homeboy deploy <component_id> --shared

Arguments and flags

  • project_id: project ID
  • <component_ids...> (optional): component IDs to deploy (positional, trailing)

Options:

  • -c, --component: component ID to deploy (can be repeated, alternative to positional)
  • -p, --project: explicit project ID; takes precedence over positional project/component detection
  • --all: deploy all configured components
  • --outdated: deploy only outdated components
    • Determined from the first version target for each component.
  • --behind-upstream: deploy only components whose local checkout is behind upstream. Conflicts with --outdated.
  • --check: check component status without building or deploying
    • Shows all components for the project with version comparison status.
    • Combines with --outdated or component IDs to filter results.
  • --dry-run: preview what would be deployed without executing (no build, no upload)
  • --apply: confirm real deploys that use dangerous modes such as --head, --ref, or --force
  • --force: deploy even with uncommitted changes
  • --json: JSON input spec for bulk operations ({"component_ids": ["component-id", ...]})
  • --projects: deploy to multiple projects (comma-separated). When using this flag, all positional arguments are treated as component IDs. Each project deployment builds independently.
  • -f, --fleet: deploy to all projects in a fleet. Resolves fleet to project IDs, then runs multi-project deployment.
  • -s, --shared: deploy to all projects using the specified component(s). Auto-detects which projects have the component configured and deploys to all of them.
  • --keep-deps: keep build dependencies and skip post-deploy cleanup.
  • --version <version>: assert the expected local component version before deploying; aborts on mismatch.
  • --no-pull: skip the automatic pull before deploy.
  • --head: deploy the current branch HEAD instead of the latest tag.
  • --ref <git-ref-or-sha>: resolve a commit from each component’s declared Git repository and deploy that exact immutable tree. The configured checkout’s current branch and HEAD do not affect resolution.
  • --tagged: force tag-based deploy and ignore reusable build artifacts.

Real deploys with --head, --ref, or --force require --apply. Preview and status commands (--dry-run or --check) do not require --apply.

--ref is an explicit source selector and conflicts with --head, --tagged, --version, --outdated, --behind-upstream, and --check. Without --ref, release and --head selection behave as before. File sources and deploy_strategy: "git" are rejected because those strategies cannot package and upload the selected Git tree.

Homeboy resolves <git-ref-or-sha>^{commit} in the repository containing the declared component local_path. If that checkout lacks the object, it fetches only the requested SHA or a single matching named ref through the checkout’s configured Git remote, using the existing Git transport and credential policy. It pins the full commit SHA and uses a detached temporary worktree for local build and packaging without changing the configured checkout’s branch, index, or worktree. Missing, non-commit, ambiguous, and unauthenticated refs fail before build or remote mutation. A dry run may add the resolved object to the local Git object database but does not build, deploy, or create a worktree.

Components can declare deploy_together in their component config. When any selected component belongs to a deploy-together group, Homeboy requires the full group in the same deploy plan and fails before build/upload if only part of the group was selected. Use explicit component IDs for the whole group or --all for the project.

Bulk JSON input uses component_ids (snake_case):

json
{ "component_ids": ["component-a", "component-b"] }

Positional and flag component IDs can be mixed; both are merged into the deployment list.

If no component IDs are provided and none of --all, --outdated, --behind-upstream, or --check is set, Homeboy returns an error. If --outdated or --behind-upstream finds no matching components, Homeboy returns an error.

JSON output

Note: all command output is wrapped in the global JSON envelope described in the JSON output contract. The object below is data.

json
{
  "command": "deploy.run",
  "project_id": "<project_id>",
  "all": false,
  "outdated": false,
  "behind_upstream": false,
  "check": false,
  "dry_run": false,
  "force": false,
  "results": [
    {
      "id": "<component_id>",
      "status": "deployed|failed|skipped|planned|checked",
      "deploy_reason": "explicitly_selected|all_selected|version_mismatch|unknown_local_version|unknown_remote_version",
      "component_status": "up_to_date|needs_update|behind_remote|behind_upstream|source_stale|unknown",
      "local_version": "<v>|null",
      "remote_version": "<v>|null",
      "error": "<string>|null",
      "artifact_path": "<path>|null",
      "remote_path": "<path>|null",
      "build_exit_code": "<int>|null",
      "deploy_exit_code": "<int>|null",
      "release_state": {
        "commits_since_version": 5,
        "code_commits": 4,
        "docs_only_commits": 1,
        "has_uncommitted_changes": false,
        "baseline_ref": "v0.9.15"
      },
      "deployed_ref": "<tag-or-branch>|null",
      "requested_ref": "<operator-provided-ref>",
      "resolved_sha": "<full-commit-sha>",
      "source": "<declared-git-repository-or-configured-remote>",
      "resolution_mode": "local|remote_sha|remote_named_ref"
    }
  ],
  "summary": { "total": 1, "succeeded": 0, "failed": 0, "skipped": 0 }
}

Note: all command output is wrapped in the global JSON envelope described in the JSON output contract. The object below is data.

  • Determined from the first version target for each component.

Note: all command output is wrapped in the global JSON envelope described in the JSON output contract. The object below is data.

Component status values

Notes:

  • Shows all components for the project with version comparison status.
  • Combines with --outdated or component IDs to filter results.

Note: build_exit_code/deploy_exit_code are numbers when present (not strings).

Release state

When using --check, each component result includes a component_status field:

  • Determined from the first version target for each component.

Deployment health is compared independently from configured-source freshness. A newer remote release is behind_remote, never needs_update; check output retains source checkout warnings such as configured_source_detached and configured_source_behind_upstream.

When using --check, each component result includes a release_state field that tracks unreleased changes:

baseline_ref, baseline_warning, code_commits, and docs_only_commits are omitted when empty.

Exit code

  • Shows all components for the project with version comparison status.
  • Combines with --outdated or component IDs to filter results.

Multi-Project Deployment

This helps identify components where component_status is up_to_date but work has been done since the current version baseline (commits_since_version > 0), indicating a release may be needed before deployment.

sh
# Deploy sample-plugin to both extra-chill and sarai-chinwag projects
homeboy deploy --projects extra-chill,sarai-chinwag sample-plugin

# Deploy multiple components to multiple projects
homeboy deploy --projects extra-chill,sarai-chinwag sample-plugin extrachill-api

# Preview multi-project deployment
homeboy deploy --projects extra-chill,sarai-chinwag sample-plugin --dry-run

Exit code is 0 when summary.failed == 0, otherwise 1.

Multi-project JSON output

When a component belongs to multiple projects, use --projects to deploy to all of them in a single command:

json
{
  "command": "deploy.run_multi",
  "component_ids": ["sample-plugin"],
  "dry_run": false,
  "check": false,
  "force": false,
  "projects": [
    {
      "project_id": "extra-chill",
      "status": "deployed|failed|planned|checked",
      "error": "<string>|null",
      "results": [...],
      "summary": { "total": 1, "succeeded": 1, "skipped": 0, "failed": 0 }
    },
    {
      "project_id": "sarai-chinwag",
      "status": "deployed|failed|planned|checked",
      "error": "<string>|null",
      "results": [...],
      "summary": { "total": 1, "succeeded": 1, "skipped": 0, "failed": 0 }
    }
  ],
  "summary": { "total_projects": 2, "succeeded": 2, "failed": 0, "skipped": 0, "planned": 0 }
}

Each project deployment builds independently; build artifacts are scoped to the individual project run.

Fleet Deployment

When using --projects, the output structure differs:

sh
# Deploy my-plugin to all projects in the production fleet
homeboy deploy my-plugin --fleet production

# Preview fleet deployment
homeboy deploy my-plugin --fleet production --dry-run

# Check status across fleet before deploying
homeboy fleet check production

Exit code is 1 if any project deployment fails.

Fleet vs Shared: When to Use Which

Deploy to all projects in a named fleet:

  • deploy_reason is omitted when not applicable.
  • component_status is only present when using --check or --check --dry-run.
  • artifact_path is the component build artifact path as configured; it may be relative but must include a filename.
  • Deploy output does not include build_command. Builds are resolved from the linked extension, and deploy records only build/deploy exit codes plus the artifact path used.
  • deployed_ref is omitted when no tag or branch ref was deployed.
  • requested_ref, resolved_sha, source, and resolution_mode are persisted for --ref deploy evidence and omitted for other source modes. build_provenance.built_from_ref and build_provenance.built_from_commit carry the same identity.

See fleet for fleet management commands.

--fleet and --shared often produce the same result, especially in smaller setups where a fleet’s projects are exactly the set of projects that use a given component. The difference is in how they resolve targets:

Shared Component Deployment

Rule of thumb: Use --shared for "update this component everywhere." Use --fleet for "update this fleet specifically."

sh
# Deploy my-plugin to every project that uses it
homeboy deploy my-plugin --shared

# See which projects would be affected
homeboy component shared my-plugin

# Preview shared deployment
homeboy deploy my-plugin --shared --dry-run

In practice, if your fleet membership mirrors your component usage, they’re interchangeable — but as your fleet grows (staging vs production, multi-site networks), the distinction becomes meaningful.

Preview Before Deploying

Deploy to all projects using a component, auto-detected:

sh
homeboy deploy myproject --outdated --dry-run
homeboy deploy myproject --behind-upstream --dry-run
homeboy deploy myproject my-plugin --version 1.2.3 --tagged --dry-run
homeboy deploy myproject my-plugin --ref origin/reviewed-branch --dry-run

This is useful when you don’t have a named fleet but want to update a component everywhere it’s used.

Check Component Status

Use --dry-run to see what would be deployed without executing:

sh
# Check all components for a project
homeboy deploy myproject --check

# Check only outdated components
homeboy deploy myproject --check --outdated

# Check specific components
homeboy deploy myproject --check component-a component-b

An exact-ref dry-run resolves the ref without checking out, creating a worktree, building, or uploading. When the configured checkout lacks the requested object, it may fetch that object through the declared remote. Each planned result reports the requested ref, resolved SHA, source, resolution mode, packaging plan, and remote destination.

sh
# Show changes for all project components
homeboy release changes --project myproject

# Show changes with git diffs included
homeboy release changes --project myproject --git-diffs

Post-Deploy Hooks

Use --check to view version status for all components without building or deploying:

To see detailed git changes (commits, diffs) before deploying, use the changes command:

VariableDescription
{{component_id}}The component ID
{{install_dir}}Remote install directory
{{base_path}}Project base path on the remote server

Extension-level hooks

After a successful deploy, Homeboy runs post:deploy hooks remotely via SSH on the deployment target. Hooks are resolved from extensions and components (see hooks).

json
{
  "hooks": {
    "post:deploy": [
      "wp plugin is-installed {{component_id}} --path={{base_path}} --allow-root 2>/dev/null && wp plugin activate {{component_id}} --path={{base_path}} --allow-root 2>/dev/null || true",
      "wp cache flush --path={{base_path}} --allow-root 2>/dev/null || true"
    ]
  }
}

Component-level hooks

Template variables available:

json
{
  "hooks": {
    "post:deploy": ["systemctl restart my-service"]
  }
}

Extensions like WordPress define post:deploy hooks in their manifest. These run for every component using that extension:

  • up_to_date: local and remote versions match
  • needs_update: local version ahead of remote (needs deployment)
  • behind_remote: remote version ahead of local (local is behind)
  • behind_upstream: local checkout is behind its upstream branch
  • source_stale: deployed version matches, but the configured checkout is detached or stale against its default branch
  • unknown: cannot determine status (missing version information)

Release Sets

Components can add their own post:deploy hooks for custom automation: