Build Command

The cc-deck build command provisions developer environments from a declarative manifest. It supports three targets: container images built with Podman, OpenShell sandbox images with security policies, and remote machines provisioned over SSH with Ansible. A single manifest captures your local tools, shell configuration, Claude Code plugins, and MCP servers, then generates the correct artifacts for whichever target you choose.

Prerequisites

Before using the build command, make sure the following tools are installed:

cc-deck CLI

Install via Homebrew: brew install cc-deck/tap/cc-deck.

Claude Code

Required for the /cc-deck.capture and /cc-deck.build slash commands that drive the capture and build workflows.

Ansible (SSH targets only)

Required for SSH provisioning. Install via Homebrew on macOS: brew install ansible. The build command checks for Ansible availability and reports a clear error if it is missing.

Podman (container targets only)

Required for building and running container images. See Installation for installation details.

Container Workflow

The container workflow generates a Containerfile from your manifest, builds an OCI image, and optionally pushes it to a registry.

1. Initialize the Build Directory

cd your-project
cc-deck build init --target container

This creates a .cc-deck/setup/ directory containing:

  • cc-deck-build.yaml (manifest template with the container target section enabled)

  • build-context/ directory for binaries and configuration files

  • .gitignore for generated artifacts

It also installs two Claude Code slash commands into .claude/commands/:

  • cc-deck.capture.md for environment discovery

  • cc-deck.build.md for artifact generation

2. Capture Your Environment

Run the capture command inside Claude Code:

/cc-deck.capture

The command scans your project for build files, CI configurations, version files, and shell settings. It discovers installed Claude Code plugins and MCP server configurations. For each section, it presents its findings and lets you accept, edit, or skip.

Capture is target-agnostic. It populates the shared sections of the manifest (tools, settings, plugins, MCP servers) without touching the target-specific sections.

3. Build the Image

Run the build command inside Claude Code:

/cc-deck.build --target container

Claude generates a Containerfile from the manifest, builds the image using Podman, and reports the result (image name and size). If the build fails, Claude reads the error output, fixes the Containerfile, and retries automatically (up to 3 attempts).

To push the image to the registry configured in targets.container.registry:

/cc-deck.build --target container --push

If a Containerfile already exists from a previous build, Claude shows a diff between the existing file and the newly generated version and asks you to choose which to use.

4. Verify the Image

cc-deck build verify --target container

The verify command runs checks inside the container image to confirm that expected tools are present. It tests for cc-deck, Claude Code, Zellij, and every tool listed in the manifest. Each tool gets a pass or fail result.

SSH Workflow

The SSH workflow generates Ansible playbooks from your manifest and runs them against a remote machine. After the initial provisioning converges, the playbooks can be re-run standalone without Claude Code involvement.

1. Initialize the Build Directory

cd your-project
cc-deck build init --target ssh

This creates the same .cc-deck/setup/ directory as the container workflow, plus Ansible role skeletons:

roles/
├── base/           # User creation, SSH keys, core packages
├── tools/          # Development tools from manifest
├── zellij/         # Zellij binary download
├── claude/         # Claude Code via official installer
├── cc_deck/        # cc-deck CLI and plugin installation
├── shell_config/   # Shell RC, credential sourcing, starship
└── mcp/            # MCP server configuration

Each role contains tasks/ and defaults/ directories.

2. Configure the SSH Target

Edit the targets.ssh section in .cc-deck/setup/cc-deck-build.yaml:

targets:
  ssh:
    host: dev@your-server
    port: 22
    identity_file: ~/.ssh/id_ed25519
    create_user: true
    user: dev
    workspace: ~/workspace

When create_user is set to true, the Ansible base role creates the specified user with sudo access. It reads the public key matching identity_file (appending .pub) and installs it as the new user’s authorized key.

3. Capture Your Environment

Run the capture command inside Claude Code, exactly as in the container workflow:

/cc-deck.capture

The capture command works the same way for both targets. It populates the shared manifest sections and leaves the SSH target configuration untouched.

4. Build (Provision the Remote)

Run the build command inside Claude Code:

/cc-deck.build --target ssh

Claude generates Ansible playbooks from the manifest, including an inventory file, group variables, a site.yml entry point, and role task files. It then runs ansible-playbook against the remote host.

If a task fails, Claude reads the Ansible error output, fixes the relevant role, and retries (up to 3 attempts). Already-succeeded tasks are skipped on retry because Ansible playbooks are idempotent.

After convergence, you can re-run the playbooks from the command line without Claude:

cd .cc-deck/setup
ansible-playbook -i inventory.ini site.yml

The provisioning installs Zellij, Claude Code (via the official installer), the cc-deck CLI (from GitHub Releases), the cc-deck config plugin (via cc-deck config plugin install), and your shell configuration with credential sourcing enabled.

5. Verify the Remote

cc-deck build verify --target ssh

The verify command connects to the remote host via SSH and runs the same tool checks as the container verification. Each tool gets a pass or fail result.

6. Register an Environment

After provisioning, register the remote as a cc-deck environment:

cc-deck ws new my-remote --type ssh --host dev@your-server

The create command runs a lightweight probe (which zellij && which cc-deck && which claude) to confirm the host is provisioned. If any tool is missing, the command fails with a message directing you to run cc-deck build first.

See SSH Environments for details on attaching to SSH environments.

OpenShell Workflow

The OpenShell workflow builds a sandbox container image with a security policy that controls network access per binary. Unlike the container workflow, OpenShell images include a policy file (/etc/openshell/policy.yaml) that maps each tool to the endpoints it is allowed to reach.

The build uses a two-pass process to discover where tools are actually installed inside the image. This removes the need for a static lookup table and produces correct binary paths regardless of install method or base image.

1. Initialize the Build Directory

cd your-project
cc-deck build init --target openshell

This creates a .cc-deck/setup/ directory containing:

  • build.yaml (manifest template with the OpenShell target section enabled)

  • openshell/snippets/ (pre-rendered Containerfile fragments)

  • openshell/components/ (cached catalog policy components, fetched from the remote repository)

  • openshell/policy.yaml (assembled security policy)

It also installs the Claude Code slash commands into .claude/commands/.

2. Capture Your Environment

Run the capture command inside Claude Code:

/cc-deck.capture

Capture works the same way across all targets. It populates the shared manifest sections (tools, settings, plugins, MCP servers) and leaves the target-specific sections untouched.

3. Build the Image

Run the build command inside Claude Code:

/cc-deck.build --target openshell

Claude generates a Containerfile from the manifest snippets, builds the image using Podman, and reports the result. If the build fails, Claude reads the error output, fixes the Containerfile, and retries automatically.

Two-Pass Binary Probing

When the manifest includes tool-matched components (such as Python, Rust, Go, or Node), the build runs in two passes:

Pass 1

The image is built with all tools installed but without binary restrictions in the policy. Every network policy entry has its endpoints defined but its binaries field left empty. This allows the image to build successfully because the OpenShell supervisor is not running during the build.

Probe

A temporary container is created from the first-pass image. Inside this container, which <binary> runs for each binary listed in the component’s probe_binaries field. If which fails, a find / -name <binary> -type f -executable search runs as a fallback. Each binary probe times out after 30 seconds. The total probe step times out after 5 minutes.

Pass 2

The policy is regenerated with the probed binary paths and any runtime_globs patterns defined in the component YAML. The image is rebuilt with this corrected policy. Because all tool installation layers are cached from the first pass, only the policy COPY layer and later layers are rebuilt. The second pass typically completes in under 10 seconds.

The expected output looks like this:

Building OpenShell image: my-image:latest
  Pass 1: building with stripped binary restrictions...
  First pass: 45.2s
  Probing binary paths in image...
  Probe: 1.8s
  WARNING: binary "uv" not found in image for component pkg_python
  Pass 2: rebuilding with probed binary paths...
  Second pass: 3.1s

When no tool-matched components need probing (for example, if only always: true components are present, or all components have explicit binaries fields), the build runs as a single pass without the probe step.

For details on how policy components define probe_binaries and runtime_globs, see Policy Components.

4. Verify the Image

cc-deck build verify --target openshell

The verify command runs checks inside the built image to confirm that expected tools are present.

5. Create a Workspace

After a successful build, create a workspace from the image:

cc-deck ws new my-sandbox --type openshell --image my-image:latest

The security policy is extracted automatically from the image. See OCI Policy Extraction for details on how extraction works.

Dual-Target Workflow

To maintain multiple target types from the same project, initialize with the targets you need:

cc-deck build init --target container,ssh
cc-deck build init --target container,openshell
cc-deck build init --target container,ssh,openshell

This scaffolds the artifact directories for each target side by side.

Capture your environment once:

/cc-deck.capture

Then build for each target separately:

/cc-deck.build --target container
/cc-deck.build --target ssh
/cc-deck.build --target openshell

All targets draw from the same shared manifest sections. Each backend generates its own artifacts (Containerfile, Ansible playbooks, or Containerfile with policy), but all install the same set of tools and configuration.

Detecting Manifest Drift

After modifying your manifest (adding a tool, changing a setting), you can check what would change before rebuilding:

cc-deck build diff

The diff command compares the current manifest against the last-generated artifacts and reports additions and removals. If both container and SSH artifacts exist, it reports drift for both targets.

You can also scope the diff to a single target:

cc-deck build diff --target container
cc-deck build diff --target ssh

Troubleshooting

Ansible is not installed

If you run /cc-deck.build --target ssh without Ansible installed, the command fails immediately with a message telling you to install it. On macOS, install with brew install ansible.

SSH target is unreachable

When the remote host is unreachable, ansible-playbook fails with a connection error. Claude reports the error but does not retry, because connectivity problems cannot be resolved by a playbook fix. Check your SSH configuration, network access, and that the host is running.

Build retries exhausted

If the self-correction loop exhausts all 3 retry attempts, the command stops and reports the failing step with the error output. The generated artifacts (Containerfile or Ansible role files) remain in their last-edited state for manual inspection. Review the error, fix the artifact by hand, and re-run the build command.

Missing target section in manifest

If you run build for a target that is not configured in the manifest, the command fails with a message directing you to add the target section manually or re-run cc-deck build init --target <target>.

Conflict with previously generated artifacts

When you re-run /cc-deck.build after playbooks or a Containerfile have been modified, Claude shows a diff of the changed files and asks you to choose: use the newly generated content, keep the existing version, or stop. This prevents accidental overwrites of manual edits.

Probe step fails

If the probe container cannot start or the probe commands fail, the build stops with an error. The first-pass image is retained with a :probe-debug tag so you can inspect it:

podman run --rm -it my-image:probe-debug sh
which cargo
find / -name cargo -type f -executable

Common causes: the base image lacks which or find, or the container runtime cannot create containers from the first-pass image.

Binary not found during probing

When which and find both fail to locate a binary, the build logs a warning and continues:

WARNING: binary "uv" not found in image for component pkg_python

The tool’s policy entry receives only runtime glob patterns (if defined in the component YAML) and no exact path. This is normal for tools that are installed at runtime rather than at build time. If the tool should be present in the image, check that it is correctly installed in the Containerfile.

No two-pass output visible

If the build runs without showing "Pass 1" / "Probe" / "Pass 2" output, the single-pass path was used. This happens when no tool-matched components need probing, which means either:

  • The manifest has no tools that match any component’s match.tools field

  • All matched components already have explicit binaries defined in their YAML

To force two-pass behavior, ensure at least one tool in the manifest matches a component that has probe_binaries or match.tools defined.

Verify reports missing tools

If cc-deck build verify reports failures, check the manifest to confirm the tool is listed, then re-run the build command for the appropriate target. For SSH targets, you can also connect directly and inspect the remote machine:

ssh dev@your-server "which <tool-name>"